private String handleDataMessage(DataMessage data, List<TLV> tlvs) throws OtrException { logger.finest( getSessionID().getAccountID() + " received a data message from " + getSessionID().getUserID() + "."); switch (this.getSessionStatus()) { case ENCRYPTED: logger.finest("Message state is ENCRYPTED. Trying to decrypt message."); // Find matching session keys. int senderKeyID = data.senderKeyID; int receipientKeyID = data.recipientKeyID; SessionKeys matchingKeys = this.getSessionKeysByID(receipientKeyID, senderKeyID); if (matchingKeys == null) { throw new OtrException("no matching keys found"); } // Verify received MAC with a locally calculated MAC. logger.finest("Transforming T to byte[] to calculate it's HmacSHA1."); byte[] serializedT; try { serializedT = SerializationUtils.toByteArray(data.getT()); } catch (IOException e) { throw new OtrException(e); } OtrCryptoEngine otrCryptoEngine = new OtrCryptoEngineImpl(); byte[] computedMAC = otrCryptoEngine.sha1Hmac( serializedT, matchingKeys.getReceivingMACKey(), SerializationConstants.TYPE_LEN_MAC); if (!Arrays.equals(computedMAC, data.mac)) { throw new OtrException("MAC verification failed, ignoring message"); } logger.finest("Computed HmacSHA1 value matches sent one."); // Mark this MAC key as old to be revealed. matchingKeys.setIsUsedReceivingMACKey(true); matchingKeys.setReceivingCtr(data.ctr); byte[] dmc = otrCryptoEngine.aesDecrypt( matchingKeys.getReceivingAESKey(), matchingKeys.getReceivingCtr(), data.encryptedMessage); String decryptedMsgContent; try { // Expect bytes to be text encoded in UTF-8. decryptedMsgContent = new String(dmc, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new OtrException(e); } logger.finest("Decrypted message: \"" + decryptedMsgContent + "\""); // FIXME extraKey = authContext.getExtraSymmetricKey(); // Rotate keys if necessary. SessionKeys mostRecent = this.getMostRecentSessionKeys(); if (mostRecent.getLocalKeyID() == receipientKeyID) this.rotateLocalSessionKeys(); if (mostRecent.getRemoteKeyID() == senderKeyID) this.rotateRemoteSessionKeys(data.nextDH); // Handle TLVs if (tlvs == null) { tlvs = new ArrayList<TLV>(); } int tlvIndex = decryptedMsgContent.indexOf((char) 0x0); if (tlvIndex > -1) { decryptedMsgContent = decryptedMsgContent.substring(0, tlvIndex); tlvIndex++; byte[] tlvsb = new byte[dmc.length - tlvIndex]; System.arraycopy(dmc, tlvIndex, tlvsb, 0, tlvsb.length); ByteArrayInputStream tin = new ByteArrayInputStream(tlvsb); while (tin.available() > 0) { int type; byte[] tdata; OtrInputStream eois = new OtrInputStream(tin); try { type = eois.readShort(); tdata = eois.readTlvData(); eois.close(); } catch (IOException e) { throw new OtrException(e); } tlvs.add(new TLV(type, tdata)); } } if (tlvs.size() > 0) { for (TLV tlv : tlvs) { switch (tlv.getType()) { case TLV.DISCONNECTED: this.setSessionStatus(SessionStatus.FINISHED); return null; default: for (OtrTlvHandler handler : tlvHandlers) { handler.processTlv(tlv); } } } } return decryptedMsgContent; case FINISHED: case PLAINTEXT: showError("Unreadable encrypted message was received."); injectMessage( new ErrorMessage( AbstractMessage.MESSAGE_ERROR, "You sent me an unreadable encrypted message")); throw new OtrException("Unreadable encrypted message received"); } return null; }