001/** 002 * 003 * Copyright 2017 Paul Schaub 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smackx.omemo; 018 019import java.io.UnsupportedEncodingException; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.logging.Level; 023import java.util.logging.Logger; 024import javax.crypto.BadPaddingException; 025import javax.crypto.IllegalBlockSizeException; 026 027import org.jivesoftware.smack.util.StringUtils; 028import org.jivesoftware.smackx.omemo.element.OmemoElement; 029import org.jivesoftware.smackx.omemo.element.OmemoKeyElement; 030import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 031import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 032import org.jivesoftware.smackx.omemo.exceptions.MultipleCryptoFailedException; 033import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 034import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException; 035import org.jivesoftware.smackx.omemo.internal.CipherAndAuthTag; 036import org.jivesoftware.smackx.omemo.internal.CiphertextTuple; 037import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 038 039public abstract class OmemoRatchet<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> { 040 private static final Logger LOGGER = Logger.getLogger(OmemoRatchet.class.getName()); 041 042 protected final OmemoManager omemoManager; 043 protected final OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store; 044 045 /** 046 * Constructor. 047 * 048 * @param omemoManager omemoManager 049 * @param store omemoStore 050 */ 051 public OmemoRatchet(OmemoManager omemoManager, 052 OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> store) { 053 this.omemoManager = omemoManager; 054 this.store = store; 055 } 056 057 /** 058 * Decrypt a double-ratchet-encrypted message key. 059 * 060 * @param sender sender of the message. 061 * @param encryptedKey key encrypted with the ratchet of the sender. 062 * @return decrypted message key. 063 * 064 * @throws CorruptedOmemoKeyException 065 * @throws NoRawSessionException when no double ratchet session was found. 066 * @throws CryptoFailedException 067 * @throws UntrustedOmemoIdentityException 068 */ 069 public abstract byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey) 070 throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException, 071 UntrustedOmemoIdentityException; 072 073 /** 074 * Encrypt a messageKey with the double ratchet session of the recipient. 075 * 076 * @param recipient recipient of the message. 077 * @param messageKey key we want to encrypt. 078 * @return encrypted message key. 079 */ 080 public abstract CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey); 081 082 /** 083 * Try to decrypt the transported message key using the double ratchet session. 084 * 085 * @param element omemoElement 086 * @return tuple of cipher generated from the unpacked message key and the auth-tag 087 * @throws CryptoFailedException if decryption using the double ratchet fails 088 * @throws NoRawSessionException if we have no session, but the element was NOT a PreKeyMessage 089 */ 090 CipherAndAuthTag retrieveMessageKeyAndAuthTag(OmemoDevice sender, OmemoElement element) throws CryptoFailedException, 091 NoRawSessionException { 092 int keyId = omemoManager.getDeviceId(); 093 byte[] unpackedKey = null; 094 List<CryptoFailedException> decryptExceptions = new ArrayList<>(); 095 List<OmemoKeyElement> keys = element.getHeader().getKeys(); 096 097 boolean preKey = false; 098 099 // Find key with our ID. 100 for (OmemoKeyElement k : keys) { 101 if (k.getId() == keyId) { 102 try { 103 unpackedKey = doubleRatchetDecrypt(sender, k.getData()); 104 preKey = k.isPreKey(); 105 break; 106 } catch (CryptoFailedException e) { 107 // There might be multiple keys with our id, but we can only decrypt one. 108 // So we can't throw the exception, when decrypting the first duplicate which is not for us. 109 decryptExceptions.add(e); 110 } catch (CorruptedOmemoKeyException e) { 111 decryptExceptions.add(new CryptoFailedException(e)); 112 } catch (UntrustedOmemoIdentityException e) { 113 LOGGER.log(Level.WARNING, "Received message from " + sender + " contained unknown identityKey. Ignore message.", e); 114 } 115 } 116 } 117 118 if (unpackedKey == null) { 119 if (!decryptExceptions.isEmpty()) { 120 throw MultipleCryptoFailedException.from(decryptExceptions); 121 } 122 123 throw new CryptoFailedException("Transported key could not be decrypted, since no suitable message key " + 124 "was provided. Provides keys: " + keys); 125 } 126 127 // Split in AES auth-tag and key 128 byte[] messageKey = new byte[16]; 129 byte[] authTag = null; 130 131 if (unpackedKey.length == 32) { 132 authTag = new byte[16]; 133 // copy key part into messageKey 134 System.arraycopy(unpackedKey, 0, messageKey, 0, 16); 135 // copy tag part into authTag 136 System.arraycopy(unpackedKey, 16, authTag, 0,16); 137 } else if (element.isKeyTransportElement() && unpackedKey.length == 16) { 138 messageKey = unpackedKey; 139 } else { 140 throw new CryptoFailedException("MessageKey has wrong length: " 141 + unpackedKey.length + ". Probably legacy auth tag format."); 142 } 143 144 return new CipherAndAuthTag(messageKey, element.getHeader().getIv(), authTag, preKey); 145 } 146 147 /** 148 * Use the symmetric key in cipherAndAuthTag to decrypt the payload of the omemoMessage. 149 * The decrypted payload will be the body of the returned Message. 150 * 151 * @param element omemoElement containing a payload. 152 * @param cipherAndAuthTag cipher and authentication tag. 153 * @return decrypted plain text. 154 * @throws CryptoFailedException if decryption using AES key fails. 155 */ 156 static String decryptMessageElement(OmemoElement element, CipherAndAuthTag cipherAndAuthTag) 157 throws CryptoFailedException { 158 if (!element.isMessageElement()) { 159 throw new IllegalArgumentException("decryptMessageElement cannot decrypt OmemoElement which is no MessageElement!"); 160 } 161 162 if (cipherAndAuthTag.getAuthTag() == null || cipherAndAuthTag.getAuthTag().length != 16) { 163 throw new CryptoFailedException("AuthenticationTag is null or has wrong length: " 164 + (cipherAndAuthTag.getAuthTag() == null ? "null" : cipherAndAuthTag.getAuthTag().length)); 165 } 166 167 byte[] encryptedBody = payloadAndAuthTag(element, cipherAndAuthTag.getAuthTag()); 168 169 try { 170 String plaintext = new String(cipherAndAuthTag.getCipher().doFinal(encryptedBody), StringUtils.UTF8); 171 return plaintext; 172 173 } catch (UnsupportedEncodingException | IllegalBlockSizeException | BadPaddingException e) { 174 throw new CryptoFailedException("decryptMessageElement could not decipher message body: " 175 + e.getMessage()); 176 } 177 } 178 179 /** 180 * Return the concatenation of the payload of the OmemoElement and the given auth tag. 181 * 182 * @param element omemoElement (message element) 183 * @param authTag authTag 184 * @return payload + authTag 185 */ 186 static byte[] payloadAndAuthTag(OmemoElement element, byte[] authTag) { 187 if (!element.isMessageElement()) { 188 throw new IllegalArgumentException("OmemoElement has no payload."); 189 } 190 191 byte[] payload = new byte[element.getPayload().length + authTag.length]; 192 System.arraycopy(element.getPayload(), 0, payload, 0, element.getPayload().length); 193 System.arraycopy(authTag, 0, payload, element.getPayload().length, authTag.length); 194 return payload; 195 } 196 197}