/** Abort the SMP. Called when an unexpected SMP message breaks the normal flow. */ public void abortSmp(OTRCallbacks callback) throws OTRException { TLV sendtlv = new TLV(TLV.SMP_ABORT, new byte[0]); TLV[] tlvs = new TLV[1]; tlvs[0] = sendtlv; this.smstate.nextExpected = SM.EXPECT1; DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, tlvs); // Send the abort signal so our buddy knows we've stopped this.fragmentAndSend(new String(dm.getContent()), Policy.FRAGMENT_SEND_ALL, callback); }
/** * Put a connection into the PLAINTEXT state, first sending the other side a notice that we're * doing so if we're currently ENCRYPTED, and we think he's logged in. * * @throws OTRException */ public void disconnect(OTRCallbacks callback) throws OTRException { if (this.msgState.getCurState() == MsgState.ST_ENCRYPTED && this.their_keyid > 0 && callback.isLoggedIn(accountName, protocol, recName) == 1) { TLV[] tlvs = new TLV[1]; tlvs[0] = new TLV(TLV.DISCONNECTED, new byte[0]); DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, tlvs); callback.injectMessage(accountName, protocol, recName, new String(dm.getContent())); } forceFinished(); this.msgState.curState = MsgState.ST_UNENCRYPTED; callback.updateContextList(); }
// Send a message to the network, fragmenting first if necessary. // All messages to be sent to the network should go through this // method immediately before they are sent, ie after encryption. String fragmentAndSend(String message, int fragPolicy, OTRCallbacks callback) { int mms = callback.maxMessageSize(this); // Don't incur overhead of fragmentation unless necessary if (mms == 0 || message.length() <= mms) { // No fragmentation necessary if (fragPolicy == Policy.FRAGMENT_SEND_ALL) { callback.injectMessage(this.accountName, this.protocol, this.recName, message); return null; } else { // return the entire given message. return message; } } int fragment_count = ((message.length() - 1) / (mms - 19)) + 1; // like ceil(msglen/(mms - 19)) String[] frags = Proto.fragmentCreate(mms, fragment_count, message); String returnFragment = null; // Determine which fragments to send and which to return // based on given Fragment Policy. If the first fragment // should be returned instead of sent, store it. if (fragPolicy == Policy.FRAGMENT_SEND_ALL_BUT_FIRST) { returnFragment = frags[0]; } else { callback.injectMessage(accountName, protocol, recName, frags[0]); } // Prevent the demo receiver to receive all the messages in a single read try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 1; i < fragment_count - 1; i++) { callback.injectMessage(accountName, protocol, recName, frags[i]); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } // If the last fragment should be stored instead of sent, // store it if (fragPolicy == Policy.FRAGMENT_SEND_ALL_BUT_LAST) { returnFragment = frags[fragment_count - 1]; } else { callback.injectMessage(accountName, protocol, recName, frags[fragment_count - 1]); } return returnFragment; }
void initRespondSmp(String question, String secret, boolean initiating, OTRCallbacks callback) throws OTRException { if (msgState.getCurState() != MsgState.ST_ENCRYPTED) return; /* * Construct the combined secret as a SHA256 hash of: * Version byte (0x01), Initiator fingerprint (20 bytes), * responder fingerprint (20 bytes), secure session id, input secret */ byte[] our_fp = PrivKey.fingerprintRaw(us, this.accountName, this.protocol, prov); int combined_buf_len = 41 + this.sessionid_len + secret.length(); byte[] combined_buf = new byte[combined_buf_len]; combined_buf[0] = 1; if (initiating) { System.arraycopy(our_fp, 0, combined_buf, 1, 20); System.arraycopy(this.activeFingerprint.fingerPrint, 0, combined_buf, 21, 20); } else { System.arraycopy(this.activeFingerprint.fingerPrint, 0, combined_buf, 1, 20); System.arraycopy(our_fp, 0, combined_buf, 21, 20); } System.arraycopy(this.sessionId, 0, combined_buf, 41, this.sessionid_len); System.arraycopy(secret.getBytes(), 0, combined_buf, 41 + this.sessionid_len, secret.length()); byte[] combined_secret = prov.getSHA256().hash(combined_buf); byte[] smpmsg; if (initiating) { smpmsg = SM.step1(smstate, combined_secret, prov); } else { smpmsg = SM.step2b(smstate, combined_secret, prov); } // If we've got a question, attach it to the smpmsg if (question != null) { byte[] qsmpmsg = new byte[question.length() + 1 + smpmsg.length]; System.arraycopy(question.getBytes(), 0, qsmpmsg, 0, question.length()); System.arraycopy(smpmsg, 0, qsmpmsg, question.length() + 1, smpmsg.length); smpmsg = qsmpmsg; } // Send msg with next smp msg content TLV sendtlv = new TLV(initiating ? (question != null ? TLV.SMP1Q : TLV.SMP1) : TLV.SMP2, smpmsg); TLV[] tlvs = new TLV[1]; tlvs[0] = sendtlv; byte[] emptymsg = new byte[0]; DataMessage dm = Proto.createData(this, emptymsg, Proto.MSGFLAGS_IGNORE_UNREADABLE, tlvs); fragmentAndSend(new String(dm.getContent()), Policy.FRAGMENT_SEND_ALL, callback); this.smstate.nextExpected = initiating ? SM.EXPECT2 : SM.EXPECT3; }
/** * Handle a message just received from the network. It is safe to pass all received messages to * this routine. * * <p>If no Exception is thrown, and the return value is not null, replace the received message * with msg in the returned StringTLV, and deliver that to the user instead. * * <p>If the return value is null, then the message you received was an internal protocol message, * and no message should be delivered to the user. */ public StringTLV messageReceiving(String inMessage, OTRCallbacks callback) throws OTRException { OTRMessage message = OTRMessage.parse(inMessage); /* Check the policy */ int policy = callback.getOtrPolicy(this); /* Should we go on at all? */ if ((policy & Policy.VERSION_MASK) == 0) { return null; } // See if we have a fragment switch (Proto.fragmentAccumulate(this, new String(message.getContent()))) { case Proto.FRAGMENT_UNFRAGMENTED: // Do nothing break; case Proto.FRAGMENT_INCOMPLETE: // We've accumulated this fragment, but we don't have a // complete message yet return null; case Proto.FRAGMENT_COMPLETE: // We've got a new complete message, in unfragmessage. message = OTRMessage.parse(this.complete_msg); break; } byte msgtype = message.getType(); /* See if they responded to our OTR offer */ if ((policy & Policy.SEND_WHITESPACE_TAG) != 0) { if (msgtype != OTRMessage.MSG_NOTOTR) { otr_offer = OFFER_ACCEPTED; } else if (otr_offer == OFFER_SENT) { otr_offer = OFFER_REJECTED; } } gone_encrypted = 0; ignore_message = -1; switch (msgtype) { case OTRMessage.MSG_QUERY: /* Start AKE */ try { auth.startAKE(null); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } if (auth.havemsgp != 0) { this.lastMessage = auth.lastauthmsg; } /* Don't display the Query message to the user. */ if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DH_COMMIT: if ((policy & Policy.ALLOW_V2) != 0) { try { auth.handleCommit(message.getContent(), null); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DH_KEY: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ PrivKey privkey = us.getPrivKey(new Account(accountName, protocol), true); try { auth.handleKey(message.getContent(), privkey); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_REVEAL_SIGNATURE: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ PrivKey privkey = us.getPrivKey(new Account(accountName, protocol), true); try { auth.handleRevealsig(message.getContent(), privkey); this.goEncrypted(callback); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_SIGNATURE: if ((policy & Policy.ALLOW_V2) != 0) { /* Get our private key */ try { auth.handleSignature(message.getContent()); this.goEncrypted(callback); this.sendOrErrorAuth(false, callback); } catch (OTRException e) { this.sendOrErrorAuth(true, callback); } } if (ignore_message == -1) ignore_message = 1; break; case OTRMessage.MSG_DATA: switch (msgState.getCurState()) { case MsgState.ST_UNENCRYPTED: case MsgState.ST_FINISHED: callback.handleMsgEvent(OTRCallbacks.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE, this, null); ignore_message = 1; String err_msg = callback.errorMessage(this, OTRCallbacks.OTRL_ERRCODE_MSG_NOT_IN_PRIVATE); callback.injectMessage(accountName, protocol, recName, err_msg); break; case MsgState.ST_ENCRYPTED: byte[] res; StringTLV stlv = new StringTLV(); OTRTLV[] tlvs; try { DataMessage dm = (DataMessage) message; res = Proto.acceptData(this, dm, null); } catch (OTRException otre) { callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNREADABLE, this, null); return null; } int end = 0; for (; end < res.length && res[end] != 0; end++) {} stlv.msg = new String(res, 0, end); if (end != res.length && end + 1 != res.length) { end++; tlvs = new TLV().parse(res, end, res.length - end); stlv.tlvs = tlvs; /* If the other side told us he's disconnected his * private connection, make a note of that so we * don't try sending anything else to him. */ OTRTLV tlv = new TLV().find(tlvs, TLV.DISCONNECTED); if (tlv != null) { forceFinished(); } /* If TLVs contain SMP data, process it */ int nextMsg = this.smstate.nextExpected; tlv = new TLV().find(tlvs, TLV.SMP1Q); if (tlv != null && nextMsg == SM.EXPECT1) { /* We can only do the verification half now. * We must wait for the secret to be entered * to continue. */ byte[] question = tlv.getValue(); int qlen = 0; for (; qlen != question.length && question[qlen] != 0; qlen++) {} if (qlen == question.length) qlen = 0; else qlen++; byte[] input = new byte[question.length - qlen]; System.arraycopy(question, qlen, input, 0, question.length - qlen); SM.step2a(this.smstate, input, 1, prov); if (qlen != 0) qlen--; byte[] plainq = new byte[qlen]; System.arraycopy(question, 0, plainq, 0, qlen); if (this.smstate.smProgState != SM.PROG_CHEATED) { callback.handleSmpEvent( OTRCallbacks.OTRL_SMPEVENT_ASK_FOR_ANSWER, this, 25, new String(plainq)); } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP1); if (tlv != null) { if (nextMsg == SM.EXPECT1) { /* We can only do the verification half now. * We must wait for the secret to be entered * to continue. */ SM.step2a(this.smstate, tlv.getValue(), 0, prov); if (this.smstate.smProgState != SM.PROG_CHEATED) { callback.handleSmpEvent( OTRCallbacks.OTRL_SMPEVENT_ASK_FOR_SECRET, this, 25, null); } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } } tlv = new TLV().find(tlvs, TLV.SMP2); if (tlv != null && nextMsg == SM.EXPECT2) { byte[] nextmsg = SM.step3(this.smstate, tlv.getValue(), prov); if (this.smstate.smProgState != SM.PROG_CHEATED) { /* Send msg with next smp msg content */ OTRTLV sendtlv = new TLV(TLV.SMP3, nextmsg); OTRTLV[] stlvs = new OTRTLV[1]; stlvs[0] = sendtlv; DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, stlvs); byte[] senddata = dm.getContent(); this.fragmentAndSend(new String(senddata), Policy.FRAGMENT_SEND_ALL, callback); this.smstate.nextExpected = SM.EXPECT4; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP3); if (tlv != null && nextMsg == SM.EXPECT3) { byte[] nextmsg = SM.step4(this.smstate, tlv.getValue(), prov); /* Set trust level based on result */ if (this.smstate.smProgState == SM.PROG_SUCCEEDED) { this.setSmpTrust(callback, true); } else { this.setSmpTrust(callback, false); } if (this.smstate.smProgState != SM.PROG_CHEATED) { /* Send msg with next smp msg content */ OTRTLV[] stlvs = new TLV[1]; stlvs[0] = new TLV(TLV.SMP4, nextmsg); DataMessage dm = Proto.createData(this, new byte[0], Proto.MSGFLAGS_IGNORE_UNREADABLE, stlvs); byte[] senddata = dm.getContent(); this.fragmentAndSend(new String(senddata), Policy.FRAGMENT_SEND_ALL, callback); int succorfail = this.smstate.smProgState == SM.PROG_SUCCEEDED ? OTRCallbacks.OTRL_SMPEVENT_SUCCESS : OTRCallbacks.OTRL_SMPEVENT_FAILURE; callback.handleSmpEvent(succorfail, this, 100, null); this.smstate.nextExpected = SM.EXPECT1; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP4); if (tlv != null && nextMsg == SM.EXPECT4) { SM.step5(this.smstate, tlv.getValue(), prov); if (this.smstate.smProgState == SM.PROG_SUCCEEDED) { this.setSmpTrust(callback, true); } else { this.setSmpTrust(callback, false); } if (this.smstate.smProgState != SM.PROG_CHEATED) { int succorfail = this.smstate.smProgState == SM.PROG_SUCCEEDED ? OTRCallbacks.OTRL_SMPEVENT_SUCCESS : OTRCallbacks.OTRL_SMPEVENT_FAILURE; callback.handleSmpEvent(succorfail, this, 100, null); this.smstate.nextExpected = SM.EXPECT1; } else { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_CHEATED, this, 0, null); this.smstate.nextExpected = SM.EXPECT1; this.smstate.smProgState = SM.PROG_OK; } } else if (tlv != null) { callback.handleSmpEvent(OTRCallbacks.OTRL_SMPEVENT_ERROR, this, 0, null); } tlv = new TLV().find(tlvs, TLV.SMP_ABORT); if (tlv != null) { this.smstate.nextExpected = SM.EXPECT1; } } return stlv; } break; case OTRMessage.MSG_TAGGED_WHITESPACE: /* Start AKE */ if (msgState.getCurState() == MsgState.ST_UNENCRYPTED) { auth.startAKE(null); if (auth.havemsgp != 0) { this.lastMessage = new String(auth.lastauthmsg); fragmentAndSend(lastMessage, Policy.FRAGMENT_SEND_ALL, callback); StringTLV stlv = new StringTLV(); stlv.msg = ((TaggedPlaintextMessage) message).getStripped(); return stlv; } } else { StringTLV stlv = new StringTLV(); stlv.msg = ((TaggedPlaintextMessage) message).getStripped(); return stlv; } break; case OTRMessage.MSG_NOTOTR: if (this.msgState.getCurState() != MsgState.ST_UNENCRYPTED || (policy & Policy.REQUIRE_ENCRYPTION) != 0) { /* Not fine. Let the user know. */ callback.handleMsgEvent( OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNENCRYPTED, this, message.toString()); } ignore_message = 1; break; default: /* We received an OTR message we didn't recognize. Ignore * it, but make a log entry. */ callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_RCVDMSG_UNRECOGNIZED, this, null); ignore_message = 1; break; } return null; }
/** * Handle a message about to be sent to the network. It is safe to pass all messages about to be * sent to this routine. * * <p>tlvs is an array of OTRTLVs to append to the private message. It is usually correct to just * pass null here. */ public String messageSending(String message, OTRTLV[] tlvs, int fragPolicy, OTRCallbacks callback) throws OTRException { int policy = Policy.DEFAULT; if (message == null) throw new OTRException("MessageSending: Null argument"); // Check the policy policy = callback.getOtrPolicy(this); // Should we go on at all? if ((policy & Policy.VERSION_MASK) == 0) { throw new OTRException("Invalid protocol"); } // If this is an OTR Query message, don't encrypt it. OTRMessage otrmessage = OTRMessage.parse(message); if (otrmessage.getType() == OTRMessage.MSG_QUERY) { // Replace the "?OTR?" with a custom message QueryMessage bettermsg = defaultQueryMessage(accountName, policy); String ret = new String(bettermsg.getContent()); if (fragPolicy == Policy.FRAGMENT_SEND_SKIP) return ret; return this.fragmentAndSend(ret, fragPolicy, callback); } switch (msgState.getCurState()) { case MsgState.ST_UNENCRYPTED: if ((policy & Policy.REQUIRE_ENCRYPTION) != 0) { /* * We're trying to send an unencrypted message with a policy * that disallows that. Don't do that, but try to start up OTR * instead. */ callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_ENCRYPTION_REQUIRED, this, null); QueryMessage bettermsg = defaultQueryMessage(accountName, policy); lastMessage = new String(bettermsg.getContent()); lastSent = System.currentTimeMillis() % 1000; mayRetransmit = 2; String ret = new String(bettermsg.getContent()); if (fragPolicy == Policy.FRAGMENT_SEND_SKIP) return ret; return this.fragmentAndSend(ret, fragPolicy, callback); } else { if ((policy & Policy.SEND_WHITESPACE_TAG) != 0 && otr_offer != OFFER_REJECTED) { /* * See if this user can speak OTR. Append the * OTR_MESSAGE_TAG to the plaintext message, and see if he * responds. */ String taggedmsg = message + OTRL_MESSAGE_TAG_BASE + OTRL_MESSAGE_TAG_V2; otr_offer = OFFER_SENT; if (fragPolicy == Policy.FRAGMENT_SEND_SKIP) return taggedmsg; return this.fragmentAndSend(taggedmsg, fragPolicy, callback); } } break; case MsgState.ST_ENCRYPTED: // Create the new, encrypted message try { DataMessage dm = Proto.createData(this, message.getBytes(), (byte) 0, tlvs); lastMessage = new String(dm.getContent()); lastSent = System.currentTimeMillis() % 1000; String ret = new String(dm.getContent()); if (fragPolicy == Policy.FRAGMENT_SEND_SKIP) return ret; return this.fragmentAndSend(ret, fragPolicy, callback); } catch (OTRException e) { /* Uh, oh. Whatever we do, *don't* send the message in the * clear. */ callback.handleMsgEvent(OTRCallbacks.OTRL_ERRCODE_ENCRYPTION_ERROR, this, null); callback.errorMessage(this, OTRCallbacks.OTRL_ERRCODE_ENCRYPTION_ERROR); return null; } case MsgState.ST_FINISHED: callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_CONNECTION_ENDED, this, null); String ret = new String(new ErrorMessage("").getContent()); if (fragPolicy == Policy.FRAGMENT_SEND_SKIP) return ret; return this.fragmentAndSend(ret, fragPolicy, callback); } return null; }