diff options
Diffstat (limited to 'src/main/java/org/whispersystems/libaxolotl/groups/GroupCipher.java')
-rw-r--r-- | src/main/java/org/whispersystems/libaxolotl/groups/GroupCipher.java | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/src/main/java/org/whispersystems/libaxolotl/groups/GroupCipher.java b/src/main/java/org/whispersystems/libaxolotl/groups/GroupCipher.java new file mode 100644 index 00000000..43dac752 --- /dev/null +++ b/src/main/java/org/whispersystems/libaxolotl/groups/GroupCipher.java @@ -0,0 +1,146 @@ +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); + } + } + +} |