OmemoRatchet.java

  1. /**
  2.  *
  3.  * Copyright 2017 Paul Schaub, 2021 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.omemo;

  18. import java.io.IOException;
  19. import java.security.InvalidAlgorithmParameterException;
  20. import java.security.InvalidKeyException;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.util.ArrayList;
  23. import java.util.List;
  24. import java.util.logging.Level;
  25. import java.util.logging.Logger;

  26. import javax.crypto.BadPaddingException;
  27. import javax.crypto.IllegalBlockSizeException;
  28. import javax.crypto.NoSuchPaddingException;

  29. import org.jivesoftware.smackx.omemo.element.OmemoElement;
  30. import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
  31. import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
  32. import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException;
  33. import org.jivesoftware.smackx.omemo.exceptions.MultipleCryptoFailedException;
  34. import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException;
  35. import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
  36. import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag;
  37. import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
  38. import org.jivesoftware.smackx.omemo.internal.OmemoDevice;

  39. public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
  40.     private static final Logger LOGGER = Logger.getLogger(OmemoRatchet.class.getName());

  41.     protected final OmemoManager omemoManager;
  42.     protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store;

  43.     /**
  44.      * Constructor.
  45.      *
  46.      * @param omemoManager omemoManager
  47.      * @param store omemoStore
  48.      */
  49.     public OmemoRatchet(OmemoManager omemoManager,
  50.                         OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) {
  51.         this.omemoManager = omemoManager;
  52.         this.store = store;
  53.     }

  54.     /**
  55.      * Decrypt a double-ratchet-encrypted message key.
  56.      *
  57.      * @param sender sender of the message.
  58.      * @param encryptedKey key encrypted with the ratchet of the sender.
  59.      * @return decrypted message key.
  60.      *
  61.      * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
  62.      * @throws NoRawSessionException when no double ratchet session was found.
  63.      * @throws CryptoFailedException if the OMEMO cryptography failed.
  64.      * @throws UntrustedOmemoIdentityException if the OMEMO identity is not trusted.
  65.      * @throws IOException if an I/O error occurred.
  66.      */
  67.     public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey)
  68.             throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException,
  69.             UntrustedOmemoIdentityException, IOException;

  70.     /**
  71.      * Encrypt a messageKey with the double ratchet session of the recipient.
  72.      *
  73.      * @param recipient recipient of the message.
  74.      * @param messageKey key we want to encrypt.
  75.      * @return encrypted message key.
  76.      */
  77.     public abstract CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey);

  78.     /**
  79.      * Try to decrypt the transported message key using the double ratchet session.
  80.      *
  81.      * @param element omemoElement
  82.      * @return tuple of cipher generated from the unpacked message key and the auth-tag
  83.      *
  84.      * @throws CryptoFailedException if decryption using the double ratchet fails
  85.      * @throws NoRawSessionException if we have no session, but the element was NOT a PreKeyMessage
  86.      * @throws IOException if an I/O error occurred.
  87.      */
  88.     CipherAndAuthTag retrieveMessageKeyAndAuthTag(OmemoDevice sender, OmemoElement element) throws CryptoFailedException,
  89.             NoRawSessionException, IOException {
  90.         int keyId = omemoManager.getDeviceId();
  91.         byte[] unpackedKey = null;
  92.         List<CryptoFailedException> decryptExceptions = new ArrayList<>();
  93.         List<OmemoKeyElement> keys = element.getHeader().getKeys();

  94.         boolean preKey = false;

  95.         // Find key with our ID.
  96.         for (OmemoKeyElement k : keys) {
  97.             if (k.getId() == keyId) {
  98.                 try {
  99.                     unpackedKey = doubleRatchetDecrypt(sender, k.getData());
  100.                     preKey = k.isPreKey();
  101.                     break;
  102.                 } catch (CryptoFailedException e) {
  103.                     // There might be multiple keys with our id, but we can only decrypt one.
  104.                     // So we can't throw the exception, when decrypting the first duplicate which is not for us.
  105.                     decryptExceptions.add(e);
  106.                 } catch (CorruptedOmemoKeyException e) {
  107.                     decryptExceptions.add(new CryptoFailedException(e));
  108.                 } catch (UntrustedOmemoIdentityException e) {
  109.                     LOGGER.log(Level.WARNING, "Received message from " + sender + " contained unknown identityKey. Ignore message.", e);
  110.                 }
  111.             }
  112.         }

  113.         if (unpackedKey == null) {
  114.             if (!decryptExceptions.isEmpty()) {
  115.                 throw MultipleCryptoFailedException.from(decryptExceptions);
  116.             }

  117.             throw new CryptoFailedException("Transported key could not be decrypted, since no suitable message key " +
  118.                     "was provided. Provides keys: " + keys);
  119.         }

  120.         // Split in AES auth-tag and key
  121.         byte[] messageKey = new byte[16];
  122.         byte[] authTag = null;

  123.         if (unpackedKey.length == 32) {
  124.             authTag = new byte[16];
  125.             // copy key part into messageKey
  126.             System.arraycopy(unpackedKey, 0, messageKey, 0, 16);
  127.             // copy tag part into authTag
  128.             System.arraycopy(unpackedKey, 16, authTag, 0, 16);
  129.         } else if (element.isKeyTransportElement() && unpackedKey.length == 16) {
  130.             messageKey = unpackedKey;
  131.         } else {
  132.             throw new CryptoFailedException("MessageKey has wrong length: "
  133.                     + unpackedKey.length + ". Probably legacy auth tag format.");
  134.         }

  135.         return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag, preKey);
  136.     }

  137.     /**
  138.      * Use the symmetric key in cipherAndAuthTag to decrypt the payload of the omemoMessage.
  139.      * The decrypted payload will be the body of the returned Message.
  140.      *
  141.      * @param element omemoElement containing a payload.
  142.      * @param cipherAndAuthTag cipher and authentication tag.
  143.      * @return decrypted plain text.
  144.      *
  145.      * @throws CryptoFailedException if decryption using AES key fails.
  146.      */
  147.     static String decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag)
  148.             throws CryptoFailedException {
  149.         if (!element.isMessageElement()) {
  150.             throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!");
  151.         }

  152.         if (cipherAndAuthTag.getAuthTag() == null || cipherAndAuthTag.getAuthTag().length != 16) {
  153.             throw new CryptoFailedException("AuthenticationTag is null or has wrong length: "
  154.                     + (cipherAndAuthTag.getAuthTag() == null ? "null" : cipherAndAuthTag.getAuthTag().length));
  155.         }

  156.         byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag());

  157.         try {
  158.             return cipherAndAuthTag.decrypt(encryptedBody);
  159.         } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchAlgorithmException
  160.                         | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
  161.             throw new CryptoFailedException("decryptMessageElement could not decipher message body", e);
  162.         }
  163.     }

  164.     /**
  165.      * Return the concatenation of the payload of the OmemoElement and the given auth tag.
  166.      *
  167.      * @param element omemoElement (message element)
  168.      * @param authTag authTag
  169.      * @return payload + authTag
  170.      */
  171.     static byte[] payloadAndAuthTag(OmemoElement element, byte[] authTag) {
  172.         if (!element.isMessageElement()) {
  173.             throw new IllegalArgumentException("OmemoElement has no payload.");
  174.         }

  175.         byte[] payload = new byte[element.getPayload().length + authTag.length];
  176.         System.arraycopy(element.getPayload(), 0, payload, 0, element.getPayload().length);
  177.         System.arraycopy(authTag, 0, payload, element.getPayload().length, authTag.length);
  178.         return payload;
  179.     }

  180. }