package org.whispersystems.libaxolotl.groups; import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.LegacyMessageException; import org.whispersystems.libaxolotl.NoSessionException; import org.whispersystems.libaxolotl.groups.ratchet.SenderChainKey; import org.whispersystems.libaxolotl.groups.ratchet.SenderMessageKey; import org.whispersystems.libaxolotl.groups.state.SenderKeyRecord; import org.whispersystems.libaxolotl.groups.state.SenderKeyState; import org.whispersystems.libaxolotl.groups.state.SenderKeyStore; import org.whispersystems.libaxolotl.protocol.SenderKeyMessage; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class GroupCipher { static final Object LOCK = new Object(); private final SenderKeyStore senderKeyStore; private final String senderKeyId; public GroupCipher(SenderKeyStore senderKeyStore, String senderKeyId) { this.senderKeyStore = senderKeyStore; this.senderKeyId = senderKeyId; } public byte[] encrypt(byte[] paddedPlaintext) throws NoSessionException { synchronized (LOCK) { try { SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId); SenderKeyState senderKeyState = record.getSenderKeyState(); SenderMessageKey senderKey = senderKeyState.getSenderChainKey().getSenderMessageKey(); byte[] ciphertext = getCipherText(senderKey.getIv(), senderKey.getCipherKey(), paddedPlaintext); SenderKeyMessage senderKeyMessage = new SenderKeyMessage(senderKeyState.getKeyId(), senderKey.getIteration(), ciphertext, senderKeyState.getSigningKeyPrivate()); senderKeyState.setSenderChainKey(senderKeyState.getSenderChainKey().getNext()); senderKeyStore.storeSenderKey(senderKeyId, record); return senderKeyMessage.serialize(); } catch (InvalidKeyIdException e) { throw new NoSessionException(e); } } } public byte[] decrypt(byte[] senderKeyMessageBytes) throws LegacyMessageException, InvalidMessageException, DuplicateMessageException { synchronized (LOCK) { try { SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId); SenderKeyMessage senderKeyMessage = new SenderKeyMessage(senderKeyMessageBytes); SenderKeyState senderKeyState = record.getSenderKeyState(senderKeyMessage.getKeyId()); senderKeyMessage.verifySignature(senderKeyState.getSigningKeyPublic()); SenderMessageKey senderKey = getSenderKey(senderKeyState, senderKeyMessage.getIteration()); byte[] plaintext = getPlainText(senderKey.getIv(), senderKey.getCipherKey(), senderKeyMessage.getCipherText()); senderKeyStore.storeSenderKey(senderKeyId, record); return plaintext; } catch (org.whispersystems.libaxolotl.InvalidKeyException | InvalidKeyIdException e) { throw new InvalidMessageException(e); } } } private SenderMessageKey getSenderKey(SenderKeyState senderKeyState, int iteration) throws DuplicateMessageException, InvalidMessageException { SenderChainKey senderChainKey = senderKeyState.getSenderChainKey(); if (senderChainKey.getIteration() > iteration) { if (senderKeyState.hasSenderMessageKey(iteration)) { return senderKeyState.removeSenderMessageKey(iteration); } else { throw new DuplicateMessageException("Received message with old counter: " + senderChainKey.getIteration() + " , " + iteration); } } if (senderChainKey.getIteration() - iteration > 2000) { throw new InvalidMessageException("Over 2000 messages into the future!"); } while (senderChainKey.getIteration() < iteration) { senderKeyState.addSenderMessageKey(senderChainKey.getSenderMessageKey()); senderChainKey = senderChainKey.getNext(); } senderKeyState.setSenderChainKey(senderChainKey.getNext()); return senderChainKey.getSenderMessageKey(); } private byte[] getPlainText(byte[] iv, byte[] key, byte[] ciphertext) throws InvalidMessageException { try { IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), ivParameterSpec); return cipher.doFinal(ciphertext); } catch (NoSuchAlgorithmException | NoSuchPaddingException | java.security.InvalidKeyException | InvalidAlgorithmParameterException e) { throw new AssertionError(e); } catch (IllegalBlockSizeException | BadPaddingException e) { throw new InvalidMessageException(e); } } private byte[] getCipherText(byte[] iv, byte[] key, byte[] plaintext) { try { IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), ivParameterSpec); return cipher.doFinal(plaintext); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException | java.security.InvalidKeyException e) { throw new AssertionError(e); } } }