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; }
void goEncrypted(OTRCallbacks callback) throws OTRException { // See if we're talking to ourselves byte[] theiry = auth.their_pub.serialize(); byte[] their_trim = new byte[theiry.length - 4]; System.arraycopy(theiry, 4, their_trim, 0, their_trim.length); byte[] oury = auth.our_dh.getPublicKey().serialize(); byte[] our_trim = new byte[oury.length - 4]; System.arraycopy(oury, 4, our_trim, 0, our_trim.length); if (prov.compareMPI(new MPI(their_trim), new MPI(our_trim)) == 0) { // Yes, we are. callback.handleMsgEvent(OTRCallbacks.OTRL_MSGEVENT_MSG_REFLECTED, this, null); throw new OTRException("Message reflected"); } // Find the fingerprint FingerPrint found_print = this.findFingerPrint(auth.their_fingerprint, true, callback); /* Is this a new session or just a refresh of an existing one? */ if (this.msgState.getCurState() == MsgState.ST_ENCRYPTED && Util.arrayEquals(this.activeFingerprint.fingerPrint, found_print.fingerPrint) && this.our_keyid - 1 == this.auth.our_keyid && prov.compareMPI( MPI.readMPI(new InBuf(this.our_old_dh_key.getPublicKey().serialize())), MPI.readMPI(new InBuf(this.auth.our_dh.getPublicKey().serialize()))) == 0 && ((this.their_keyid > 0 && this.their_keyid == auth.their_keyid && prov.compareMPI( MPI.readMPI(new InBuf(this.their_y.serialize())), MPI.readMPI(new InBuf(this.auth.their_pub.serialize()))) == 0) || (this.their_keyid > 1 && this.their_keyid - 1 == this.auth.their_keyid && this.their_old_y != null && prov.compareMPI( MPI.readMPI(new InBuf(this.their_y.serialize())), MPI.readMPI(new InBuf(this.auth.their_pub.serialize()))) == 0))) { /* This is just a refresh of the existing session. */ callback.stillSecure(this, auth.initiated); ignore_message = 1; return; } // Copy the information from the auth into the context System.arraycopy(auth.secure_session_id, 0, this.sessionId, 0, 20); this.sessionid_len = auth.sessionid_len; // Copy the keys this.their_keyid = auth.their_keyid; this.their_y = auth.their_pub; this.their_old_y = null; if (our_keyid - 1 != auth.our_keyid) { this.our_old_dh_key = auth.our_dh; this.our_dh_key = prov.getDHKeyPairGenerator().generateKeyPair(); this.our_keyid = auth.our_keyid + 1; } // Create the session keys from the DH keys this.sesskeys[0][0] = new DHSesskeys(prov); this.sesskeys[1][0] = new DHSesskeys(prov); this.sesskeys[0][0].computeSession(this.our_dh_key, this.their_y); this.sesskeys[1][0].computeSession(this.our_old_dh_key, this.their_y); this.generation++; this.activeFingerprint = found_print; int oldstate = msgState.getCurState(); msgState.processEvent(MsgState.EVT_AUTHENTICATED); callback.updateContextList(); if (oldstate == MsgState.ST_ENCRYPTED && Util.arrayEquals(this.activeFingerprint.fingerPrint, found_print.fingerPrint)) { callback.stillSecure(this, this.auth.initiated); } else { callback.goneSecure(this); } this.gone_encrypted = 1; }