/// <summary> /// Construct a group session for sending messages. /// </summary> /// <param name="senderKeyName">The (groupId, senderId, deviceId) tuple. In this case, 'senderId' should be the /// caller.</param> /// <returns>A SenderKeyDistributionMessage that is individually distributed to each member of the group.</returns> public SenderKeyDistributionMessage create(SenderKeyName senderKeyName) { lock (GroupCipher.LOCK) { try { SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); if (senderKeyRecord.isEmpty()) { senderKeyRecord.setSenderKeyState(KeyHelper.generateSenderKeyId(), 0, KeyHelper.generateSenderKey(), KeyHelper.generateSenderSigningKey()); senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); } SenderKeyState state = senderKeyRecord.getSenderKeyState(); return(new SenderKeyDistributionMessage(state.getKeyId(), state.getSenderChainKey().getIteration(), state.getSenderChainKey().getSeed(), state.getSigningKeyPublic())); } catch (Exception e) when(e is InvalidKeyIdException || e is InvalidKeyException) { throw new Exception(e.Message); } } }
/// <summary> /// Encrypt a message. /// </summary> /// <param name="paddedPlaintext">The plaintext message bytes, optionally padded.</param> /// <returns>Ciphertext.</returns> /// <exception cref="NoSessionException"></exception> public byte[] encrypt(byte[] paddedPlaintext) { lock (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); } } }
/// <summary> /// Decrypt a SenderKey group message. /// </summary> /// <param name="senderKeyMessageBytes">The received ciphertext.</param> /// <param name="callback">A callback that is triggered after decryption is complete, but before the updated /// session state has been committed to the session DB. This allows some implementations to store the committed /// plaintext to a DB first, in case they are concerned with a crash happening between the time the session /// state is updated but before they're able to store the plaintext to disk.</param> /// <returns>Plaintext</returns> /// <exception cref="LegacyMessageException"></exception> /// <exception cref="InvalidMessageException"></exception> /// <exception cref="DuplicateMessageException"></exception> /// <exception cref="NoSessionException"></exception> public byte[] decrypt(byte[] senderKeyMessageBytes, DecryptionCallback callback) { lock (LOCK) { try { SenderKeyRecord record = senderKeyStore.loadSenderKey(senderKeyId); if (record.isEmpty()) { throw new NoSessionException("No sender key for: " + 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()); callback.handlePlaintext(plaintext); senderKeyStore.storeSenderKey(senderKeyId, record); return(plaintext); } catch (Exception e) when(e is InvalidKeyException || e is InvalidKeyIdException) { throw new InvalidMessageException(e); } } }
/// <summary> /// Construct a group session for receiving messages from senderKeyName. /// </summary> /// <param name="senderKeyName">The (groupId, senderId, deviceId) tuple associated with the SenderKeyDistributionMessage.</param> /// <param name="senderKeyDistributionMessage">A received SenderKeyDistributionMessage.</param> public void process(SenderKeyName senderKeyName, SenderKeyDistributionMessage senderKeyDistributionMessage) { lock (GroupCipher.LOCK) { SenderKeyRecord senderKeyRecord = senderKeyStore.loadSenderKey(senderKeyName); senderKeyRecord.addSenderKeyState(senderKeyDistributionMessage.getId(), senderKeyDistributionMessage.getIteration(), senderKeyDistributionMessage.getChainKey(), senderKeyDistributionMessage.getSignatureKey()); senderKeyStore.storeSenderKey(senderKeyName, senderKeyRecord); } }