OmemoMessageBuilder.java

  1. /**
  2.  *
  3.  * Copyright 2017 Paul Schaub, 2019-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.util;

  18. import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYLENGTH;
  19. import static org.jivesoftware.smackx.omemo.util.OmemoConstants.Crypto.KEYTYPE;

  20. import java.io.IOException;
  21. import java.security.InvalidAlgorithmParameterException;
  22. import java.security.InvalidKeyException;
  23. import java.security.NoSuchAlgorithmException;
  24. import java.util.ArrayList;
  25. import javax.crypto.BadPaddingException;
  26. import javax.crypto.IllegalBlockSizeException;
  27. import javax.crypto.KeyGenerator;
  28. import javax.crypto.NoSuchPaddingException;

  29. import org.jivesoftware.smack.util.RandomUtil;
  30. import org.jivesoftware.smackx.omemo.OmemoRatchet;
  31. import org.jivesoftware.smackx.omemo.OmemoService;
  32. import org.jivesoftware.smackx.omemo.element.OmemoElement;
  33. import org.jivesoftware.smackx.omemo.element.OmemoElement_VAxolotl;
  34. import org.jivesoftware.smackx.omemo.element.OmemoHeaderElement_VAxolotl;
  35. import org.jivesoftware.smackx.omemo.element.OmemoKeyElement;
  36. import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
  37. import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
  38. import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException;
  39. import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException;
  40. import org.jivesoftware.smackx.omemo.internal.CiphertextTuple;
  41. import org.jivesoftware.smackx.omemo.internal.OmemoAesCipher;
  42. import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
  43. import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
  44. import org.jivesoftware.smackx.omemo.trust.OmemoTrustCallback;


  45. /**
  46.  * Class used to build OMEMO messages.
  47.  *
  48.  * @param <T_IdKeyPair> IdentityKeyPair class
  49.  * @param <T_IdKey>     IdentityKey class
  50.  * @param <T_PreKey>    PreKey class
  51.  * @param <T_SigPreKey> SignedPreKey class
  52.  * @param <T_Sess>      Session class
  53.  * @param <T_Addr>      Address class
  54.  * @param <T_ECPub>     Elliptic Curve PublicKey class
  55.  * @param <T_Bundle>    Bundle class
  56.  * @param <T_Ciph>      Cipher class
  57.  * @author Paul Schaub
  58.  */
  59. public class OmemoMessageBuilder<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {

  60.     private final OmemoDevice userDevice;
  61.     private final OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> ratchet;
  62.     private final OmemoTrustCallback trustCallback;

  63.     private byte[] messageKey;
  64.     private final byte[] initializationVector;

  65.     private byte[] ciphertextMessage;
  66.     private final ArrayList<OmemoKeyElement> keys = new ArrayList<>();

  67.     /**
  68.      * Create an OmemoMessageBuilder.
  69.      *
  70.      * @param userDevice our OmemoDevice
  71.      * @param callback trustCallback for querying trust decisions
  72.      * @param ratchet our OmemoRatchet
  73.      * @param aesKey aes message key used for message encryption
  74.      * @param iv initialization vector used for message encryption
  75.      * @param message message we want to send
  76.      *
  77.      * @throws NoSuchPaddingException if the requested padding mechanism is not availble.
  78.      * @throws BadPaddingException if the input data is not padded properly.
  79.      * @throws InvalidKeyException if the key is invalid.
  80.      * @throws NoSuchAlgorithmException if no such algorithm is available.
  81.      * @throws IllegalBlockSizeException if the input data length is incorrect.
  82.      * @throws InvalidAlgorithmParameterException if the provided arguments are invalid.
  83.      */
  84.     public OmemoMessageBuilder(OmemoDevice userDevice,
  85.                                OmemoTrustCallback callback,
  86.                                OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> ratchet,
  87.                                byte[] aesKey,
  88.                                byte[] iv,
  89.                                String message)
  90.             throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException,
  91.             IllegalBlockSizeException,
  92.             InvalidAlgorithmParameterException {
  93.         this.userDevice = userDevice;
  94.         this.trustCallback = callback;
  95.         this.ratchet = ratchet;
  96.         this.messageKey = aesKey;
  97.         this.initializationVector = iv;
  98.         setMessage(message);
  99.     }

  100.     /**
  101.      * Create an OmemoMessageBuilder.
  102.      *
  103.      * @param userDevice our OmemoDevice
  104.      * @param callback trustCallback for querying trust decisions
  105.      * @param ratchet our OmemoRatchet
  106.      * @param message message we want to send
  107.      *
  108.      * @throws NoSuchPaddingException if the requested padding mechanism is not availble.
  109.      * @throws BadPaddingException if the input data is not padded properly.
  110.      * @throws InvalidKeyException if the key is invalid.
  111.      * @throws NoSuchAlgorithmException if no such algorithm is available.
  112.      * @throws IllegalBlockSizeException if the input data length is incorrect.
  113.      * @throws InvalidAlgorithmParameterException if the provided arguments are invalid.
  114.      */
  115.     public OmemoMessageBuilder(OmemoDevice userDevice,
  116.                                OmemoTrustCallback callback,
  117.                                OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> ratchet,
  118.                                String message)
  119.             throws NoSuchPaddingException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException,
  120.             InvalidAlgorithmParameterException {
  121.         this(userDevice, callback, ratchet, generateKey(KEYTYPE, KEYLENGTH), generateIv(), message);
  122.     }

  123.     /**
  124.      * Encrypt the message with the aes key.
  125.      * Move the AuthTag from the end of the cipherText to the end of the messageKey afterwards.
  126.      * This prevents an attacker which compromised one recipient device to switch out the cipherText for other recipients.
  127.      *
  128.      * @see <a href="https://conversations.im/omemo/audit.pdf">OMEMO security audit</a>.
  129.      *
  130.      * @param message plaintext message
  131.      *
  132.      * @throws NoSuchPaddingException if the requested padding mechanism is not availble.
  133.      * @throws InvalidAlgorithmParameterException if the provided arguments are invalid.
  134.      * @throws InvalidKeyException if the key is invalid.
  135.      * @throws BadPaddingException if the input data is not padded properly.
  136.      * @throws IllegalBlockSizeException if the input data length is incorrect.
  137.      */
  138.     private void setMessage(String message)
  139.             throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
  140.             InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
  141.         if (message == null) {
  142.             return;
  143.         }

  144.         // Encrypt message body
  145.         byte[] ciphertext = OmemoAesCipher.encryptAesGcmNoPadding(message, messageKey, initializationVector);

  146.         byte[] clearKeyWithAuthTag = new byte[messageKey.length + 16];
  147.         byte[] cipherTextWithoutAuthTag = new byte[ciphertext.length - 16];

  148.         moveAuthTag(messageKey, ciphertext, clearKeyWithAuthTag, cipherTextWithoutAuthTag);

  149.         ciphertextMessage = cipherTextWithoutAuthTag;
  150.         messageKey = clearKeyWithAuthTag;
  151.     }

  152.     /**
  153.      * Move the auth tag from the end of the cipherText to the messageKey.
  154.      *
  155.      * @param messageKey source messageKey without authTag
  156.      * @param cipherText source cipherText with authTag
  157.      * @param messageKeyWithAuthTag destination messageKey with authTag
  158.      * @param cipherTextWithoutAuthTag destination cipherText without authTag
  159.      */
  160.     static void moveAuthTag(byte[] messageKey,
  161.                             byte[] cipherText,
  162.                             byte[] messageKeyWithAuthTag,
  163.                             byte[] cipherTextWithoutAuthTag) {
  164.         // Check dimensions of arrays
  165.         if (messageKeyWithAuthTag.length != messageKey.length + 16) {
  166.             throw new IllegalArgumentException("Length of messageKeyWithAuthTag must be length of messageKey + " +
  167.                     "length of AuthTag (16)");
  168.         }

  169.         if (cipherTextWithoutAuthTag.length != cipherText.length - 16) {
  170.             throw new IllegalArgumentException("Length of cipherTextWithoutAuthTag must be length of cipherText " +
  171.                     "- length of AuthTag (16)");
  172.         }

  173.         // Move auth tag from cipherText to messageKey
  174.         System.arraycopy(messageKey, 0, messageKeyWithAuthTag, 0, 16);
  175.         System.arraycopy(cipherText, 0, cipherTextWithoutAuthTag, 0, cipherTextWithoutAuthTag.length);
  176.         System.arraycopy(cipherText, cipherText.length - 16, messageKeyWithAuthTag, 16, 16);
  177.     }

  178.     /**
  179.      * Add a new recipient device to the message.
  180.      *
  181.      * @param contactsDevice device of the recipient
  182.      *
  183.      * @throws NoIdentityKeyException if we have no identityKey of that device. Can be fixed by fetching and
  184.      *                                processing the devices bundle.
  185.      * @throws CorruptedOmemoKeyException if the identityKey of that device is corrupted.
  186.      * @throws UndecidedOmemoIdentityException if the user hasn't yet decided whether to trust that device or not.
  187.      * @throws UntrustedOmemoIdentityException if the user has decided not to trust that device.
  188.      * @throws IOException if an I/O error occurred.
  189.      */
  190.     public void addRecipient(OmemoDevice contactsDevice)
  191.             throws NoIdentityKeyException, CorruptedOmemoKeyException, UndecidedOmemoIdentityException,
  192.             UntrustedOmemoIdentityException, IOException {

  193.         OmemoFingerprint fingerprint;
  194.         fingerprint = OmemoService.getInstance().getOmemoStoreBackend().getFingerprint(userDevice, contactsDevice);

  195.         switch (trustCallback.getTrust(contactsDevice, fingerprint)) {

  196.             case undecided:
  197.                 throw new UndecidedOmemoIdentityException(contactsDevice);

  198.             case trusted:
  199.                 CiphertextTuple encryptedKey = ratchet.doubleRatchetEncrypt(contactsDevice, messageKey);
  200.                 keys.add(new OmemoKeyElement(encryptedKey.getCiphertext(), contactsDevice.getDeviceId(), encryptedKey.isPreKeyMessage()));
  201.                 break;

  202.             case untrusted:
  203.                 throw new UntrustedOmemoIdentityException(contactsDevice, fingerprint);

  204.         }
  205.     }

  206.     /**
  207.      * Assemble an OmemoMessageElement from the current state of the builder.
  208.      *
  209.      * @return OMEMO element
  210.      */
  211.     public OmemoElement finish() {
  212.         OmemoHeaderElement_VAxolotl header = new OmemoHeaderElement_VAxolotl(
  213.                 userDevice.getDeviceId(),
  214.                 keys,
  215.                 initializationVector
  216.         );
  217.         return new OmemoElement_VAxolotl(header, ciphertextMessage);
  218.     }

  219.     /**
  220.      * Generate a new AES key used to encrypt the message.
  221.      *
  222.      * @param keyType Key Type
  223.      * @param keyLength Key Length in bit
  224.      * @return new AES key
  225.      *
  226.      * @throws NoSuchAlgorithmException if no such algorithm is available.
  227.      */
  228.     public static byte[] generateKey(String keyType, int keyLength) throws NoSuchAlgorithmException {
  229.         KeyGenerator generator = KeyGenerator.getInstance(keyType);
  230.         generator.init(keyLength);
  231.         return generator.generateKey().getEncoded();
  232.     }

  233.     /**
  234.      * Generate a 12 byte initialization vector for AES encryption.
  235.      *
  236.      * @return initialization vector
  237.      */
  238.     public static byte[] generateIv() {
  239.         byte[] iv = new byte[12];
  240.         RandomUtil.fillWithSecureRandom(iv);
  241.         return iv;
  242.     }
  243. }