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