예제 #1
0
  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;
  }
예제 #2
0
  /**
   * 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;
  }
예제 #3
0
  /**
   * 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;
  }
예제 #4
0
  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;
  }