private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException { ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair(); int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); byte[] bobSignature = Curve.calculateSignature( bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKey.getPublicKey().serialize()); PreKeyBundle bobPreKeyBundle = new PreKeyBundle( 1, 1, bobUnsignedPreKeyId, bobUnsignedPreKey.getPublicKey(), bobSignedPreKeyId, bobSignedPreKey.getPublicKey(), bobSignature, bobStore.getIdentityKeyPair().getPublicKey()); bobStore.storeSignedPreKey( bobSignedPreKeyId, new SignedPreKeyRecord( bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature)); bobStore.storePreKey( bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey)); return bobPreKeyBundle; }
public byte[] encryptBytes(byte[] body) { try { ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey(); ECKeyPair ourKeyPair = Curve.generateKeyPair(); byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey()); MasterCipher masterCipher = getMasterCipherForSecret(secret); byte[] encryptedBodyBytes = masterCipher.encryptBytes(body); PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey()); byte[] publicKeyBytes = ourPublicKey.serialize(); return Util.combine(publicKeyBytes, encryptedBodyBytes); } catch (InvalidKeyException e) { throw new AssertionError(e); } }
public static AsymmetricMasterSecret getAsymmetricMasterSecret( @NonNull Context context, @Nullable MasterSecret masterSecret) { try { byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB); byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB); ECPublicKey djbPublicKey = null; ECPrivateKey djbPrivateKey = null; if (djbPublicBytes != null) { djbPublicKey = Curve.decodePoint(djbPublicBytes, 0); } if (masterSecret != null) { MasterCipher masterCipher = new MasterCipher(masterSecret); if (djbPrivateBytes != null) { djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes); } } return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey); } catch (InvalidKeyException | IOException ike) { throw new AssertionError(ike); } }
public ECPrivateKey decryptKey(byte[] key) throws org.whispersystems.libaxolotl.InvalidKeyException { try { return Curve.decodePrivatePoint(decryptBytes(key)); } catch (InvalidMessageException ime) { throw new org.whispersystems.libaxolotl.InvalidKeyException(ime); } }
public static AsymmetricMasterSecret generateAsymmetricMasterSecret( Context context, MasterSecret masterSecret) { MasterCipher masterCipher = new MasterCipher(masterSecret); ECKeyPair keyPair = Curve.generateKeyPair(); save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize()); save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey())); return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey()); }
public static SignedPreKeyRecord generateSignedPreKey( Context context, MasterSecret masterSecret, IdentityKeyPair identityKeyPair) { try { SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret); int signedPreKeyId = getNextSignedPreKeyId(context); ECKeyPair keyPair = Curve.generateKeyPair(); byte[] signature = Curve.calculateSignature( identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize()); SignedPreKeyRecord record = new SignedPreKeyRecord(signedPreKeyId, System.currentTimeMillis(), keyPair, signature); signedPreKeyStore.storeSignedPreKey(signedPreKeyId, record); setNextSignedPreKeyId(context, (signedPreKeyId + 1) % Medium.MAX_VALUE); return record; } catch (InvalidKeyException e) { throw new AssertionError(e); } }
private void initializeSessionsV2(SessionState aliceSessionState, SessionState bobSessionState) throws InvalidKeyException { ECKeyPair aliceIdentityKeyPair = Curve.generateKeyPair(); IdentityKeyPair aliceIdentityKey = new IdentityKeyPair( new IdentityKey(aliceIdentityKeyPair.getPublicKey()), aliceIdentityKeyPair.getPrivateKey()); ECKeyPair aliceBaseKey = Curve.generateKeyPair(); ECKeyPair aliceEphemeralKey = Curve.generateKeyPair(); ECKeyPair bobIdentityKeyPair = Curve.generateKeyPair(); IdentityKeyPair bobIdentityKey = new IdentityKeyPair( new IdentityKey(bobIdentityKeyPair.getPublicKey()), bobIdentityKeyPair.getPrivateKey()); ECKeyPair bobBaseKey = Curve.generateKeyPair(); ECKeyPair bobEphemeralKey = bobBaseKey; AliceAxolotlParameters aliceParameters = AliceAxolotlParameters.newBuilder() .setOurIdentityKey(aliceIdentityKey) .setOurBaseKey(aliceBaseKey) .setTheirIdentityKey(bobIdentityKey.getPublicKey()) .setTheirSignedPreKey(bobEphemeralKey.getPublicKey()) .setTheirRatchetKey(bobEphemeralKey.getPublicKey()) .setTheirOneTimePreKey(Optional.<ECPublicKey>absent()) .create(); BobAxolotlParameters bobParameters = BobAxolotlParameters.newBuilder() .setOurIdentityKey(bobIdentityKey) .setOurOneTimePreKey(Optional.<ECKeyPair>absent()) .setOurRatchetKey(bobEphemeralKey) .setOurSignedPreKey(bobBaseKey) .setTheirBaseKey(aliceBaseKey.getPublicKey()) .setTheirIdentityKey(aliceIdentityKey.getPublicKey()) .create(); RatchetingSession.initializeSession(aliceSessionState, 2, aliceParameters); RatchetingSession.initializeSession(bobSessionState, 2, bobParameters); }
public byte[] decryptBytes(byte[] combined) throws IOException, InvalidMessageException { try { byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE); PublicKey theirPublicKey = new PublicKey(parts[0], 0); ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey(); byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey); MasterCipher masterCipher = getMasterCipherForSecret(secret); return masterCipher.decryptBytes(parts[1]); } catch (InvalidKeyException e) { throw new InvalidMessageException(e); } }
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) { PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); List<PreKeyRecord> records = new LinkedList<>(); int preKeyIdOffset = getNextPreKeyId(context); for (int i = 0; i < BATCH_SIZE; i++) { int preKeyId = (preKeyIdOffset + i) % Medium.MAX_VALUE; ECKeyPair keyPair = Curve.generateKeyPair(); PreKeyRecord record = new PreKeyRecord(preKeyId, keyPair); preKeyStore.storePreKey(preKeyId, record); records.add(record); } setNextPreKeyId(context, (preKeyIdOffset + BATCH_SIZE + 1) % Medium.MAX_VALUE); return records; }
public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) { PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret); if (preKeyStore.containsPreKey(Medium.MAX_VALUE)) { try { return preKeyStore.loadPreKey(Medium.MAX_VALUE); } catch (InvalidKeyIdException e) { Log.w("PreKeyUtil", e); preKeyStore.removePreKey(Medium.MAX_VALUE); } } ECKeyPair keyPair = Curve.generateKeyPair(); PreKeyRecord record = new PreKeyRecord(Medium.MAX_VALUE, keyPair); preKeyStore.storePreKey(Medium.MAX_VALUE, record); return record; }
public class SimultaneousInitiateTests extends TestCase { private static final AxolotlAddress BOB_ADDRESS = new AxolotlAddress("+14151231234", 1); private static final AxolotlAddress ALICE_ADDRESS = new AxolotlAddress("+14159998888", 1); private static final ECKeyPair aliceSignedPreKey = Curve.generateKeyPair(); private static final ECKeyPair bobSignedPreKey = Curve.generateKeyPair(); private static final int aliceSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); private static final int bobSignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); public void testBasicSimultaneousInitiate() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); assertFalse(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } public void testLostSimultaneousInitiate() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.PREKEY_TYPE); byte[] responsePlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } public void testSimultaneousInitiateLostMessage() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); assertFalse(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); // byte[] responsePlaintext = bobSessionCipher.decrypt(new // WhisperMessage(aliceResponse.serialize())); // // assertTrue(new String(responsePlaintext).equals("second message")); // assertTrue(isSessionIdEqual(aliceStore, bobStore)); assertFalse(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } public void testSimultaneousInitiateRepeatedMessages() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); assertFalse(isSessionIdEqual(aliceStore, bobStore)); for (int i = 0; i < 50; i++) { CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); assertFalse(isSessionIdEqual(aliceStore, bobStore)); } CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } public void testRepeatedSimultaneousInitiateRepeatedMessages() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); for (int i = 0; i < 15; i++) { PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); assertFalse(isSessionIdEqual(aliceStore, bobStore)); } for (int i = 0; i < 50; i++) { CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); assertFalse(isSessionIdEqual(aliceStore, bobStore)); } CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } public void testRepeatedSimultaneousInitiateLostMessageRepeatedMessages() throws InvalidKeyException, UntrustedIdentityException, InvalidVersionException, InvalidMessageException, DuplicateMessageException, LegacyMessageException, InvalidKeyIdException, NoSessionException { AxolotlStore aliceStore = new TestInMemoryAxolotlStore(); AxolotlStore bobStore = new TestInMemoryAxolotlStore(); SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, BOB_ADDRESS); SessionBuilder bobSessionBuilder = new SessionBuilder(bobStore, ALICE_ADDRESS); SessionCipher aliceSessionCipher = new SessionCipher(aliceStore, BOB_ADDRESS); SessionCipher bobSessionCipher = new SessionCipher(bobStore, ALICE_ADDRESS); // PreKeyBundle aliceLostPreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobLostPreKeyBundle = createBobPreKeyBundle(bobStore); aliceSessionBuilder.process(bobLostPreKeyBundle); // bobSessionBuilder.process(aliceLostPreKeyBundle); CiphertextMessage lostMessageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); // CiphertextMessage lostMessageForAlice = bobSessionCipher.encrypt("sample // message".getBytes()); for (int i = 0; i < 15; i++) { PreKeyBundle alicePreKeyBundle = createAlicePreKeyBundle(aliceStore); PreKeyBundle bobPreKeyBundle = createBobPreKeyBundle(bobStore); aliceSessionBuilder.process(bobPreKeyBundle); bobSessionBuilder.process(alicePreKeyBundle); CiphertextMessage messageForBob = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAlice = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBob.getType() == CiphertextMessage.PREKEY_TYPE); assertTrue(messageForAlice.getType() == CiphertextMessage.PREKEY_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintext = aliceSessionCipher.decrypt(new PreKeyWhisperMessage(messageForAlice.serialize())); byte[] bobPlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(messageForBob.serialize())); assertTrue(new String(alicePlaintext).equals("sample message")); assertTrue(new String(bobPlaintext).equals("hey there")); assertTrue(aliceStore.loadSession(BOB_ADDRESS).getSessionState().getSessionVersion() == 3); assertTrue(bobStore.loadSession(ALICE_ADDRESS).getSessionState().getSessionVersion() == 3); assertFalse(isSessionIdEqual(aliceStore, bobStore)); } for (int i = 0; i < 50; i++) { CiphertextMessage messageForBobRepeat = aliceSessionCipher.encrypt("hey there".getBytes()); CiphertextMessage messageForAliceRepeat = bobSessionCipher.encrypt("sample message".getBytes()); assertTrue(messageForBobRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertTrue(messageForAliceRepeat.getType() == CiphertextMessage.WHISPER_TYPE); assertFalse(isSessionIdEqual(aliceStore, bobStore)); byte[] alicePlaintextRepeat = aliceSessionCipher.decrypt(new WhisperMessage(messageForAliceRepeat.serialize())); byte[] bobPlaintextRepeat = bobSessionCipher.decrypt(new WhisperMessage(messageForBobRepeat.serialize())); assertTrue(new String(alicePlaintextRepeat).equals("sample message")); assertTrue(new String(bobPlaintextRepeat).equals("hey there")); assertFalse(isSessionIdEqual(aliceStore, bobStore)); } CiphertextMessage aliceResponse = aliceSessionCipher.encrypt("second message".getBytes()); assertTrue(aliceResponse.getType() == CiphertextMessage.WHISPER_TYPE); byte[] responsePlaintext = bobSessionCipher.decrypt(new WhisperMessage(aliceResponse.serialize())); assertTrue(new String(responsePlaintext).equals("second message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage finalMessage = bobSessionCipher.encrypt("third message".getBytes()); assertTrue(finalMessage.getType() == CiphertextMessage.WHISPER_TYPE); byte[] finalPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(finalMessage.serialize())); assertTrue(new String(finalPlaintext).equals("third message")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); byte[] lostMessagePlaintext = bobSessionCipher.decrypt(new PreKeyWhisperMessage(lostMessageForBob.serialize())); assertTrue(new String(lostMessagePlaintext).equals("hey there")); assertFalse(isSessionIdEqual(aliceStore, bobStore)); CiphertextMessage blastFromThePast = bobSessionCipher.encrypt("unexpected!".getBytes()); byte[] blastFromThePastPlaintext = aliceSessionCipher.decrypt(new WhisperMessage(blastFromThePast.serialize())); assertTrue(new String(blastFromThePastPlaintext).equals("unexpected!")); assertTrue(isSessionIdEqual(aliceStore, bobStore)); } private boolean isSessionIdEqual(AxolotlStore aliceStore, AxolotlStore bobStore) { return Arrays.equals( aliceStore.loadSession(BOB_ADDRESS).getSessionState().getAliceBaseKey(), bobStore.loadSession(ALICE_ADDRESS).getSessionState().getAliceBaseKey()); } private PreKeyBundle createAlicePreKeyBundle(AxolotlStore aliceStore) throws InvalidKeyException { ECKeyPair aliceUnsignedPreKey = Curve.generateKeyPair(); int aliceUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); byte[] aliceSignature = Curve.calculateSignature( aliceStore.getIdentityKeyPair().getPrivateKey(), aliceSignedPreKey.getPublicKey().serialize()); PreKeyBundle alicePreKeyBundle = new PreKeyBundle( 1, 1, aliceUnsignedPreKeyId, aliceUnsignedPreKey.getPublicKey(), aliceSignedPreKeyId, aliceSignedPreKey.getPublicKey(), aliceSignature, aliceStore.getIdentityKeyPair().getPublicKey()); aliceStore.storeSignedPreKey( aliceSignedPreKeyId, new SignedPreKeyRecord( aliceSignedPreKeyId, System.currentTimeMillis(), aliceSignedPreKey, aliceSignature)); aliceStore.storePreKey( aliceUnsignedPreKeyId, new PreKeyRecord(aliceUnsignedPreKeyId, aliceUnsignedPreKey)); return alicePreKeyBundle; } private PreKeyBundle createBobPreKeyBundle(AxolotlStore bobStore) throws InvalidKeyException { ECKeyPair bobUnsignedPreKey = Curve.generateKeyPair(); int bobUnsignedPreKeyId = new Random().nextInt(Medium.MAX_VALUE); byte[] bobSignature = Curve.calculateSignature( bobStore.getIdentityKeyPair().getPrivateKey(), bobSignedPreKey.getPublicKey().serialize()); PreKeyBundle bobPreKeyBundle = new PreKeyBundle( 1, 1, bobUnsignedPreKeyId, bobUnsignedPreKey.getPublicKey(), bobSignedPreKeyId, bobSignedPreKey.getPublicKey(), bobSignature, bobStore.getIdentityKeyPair().getPublicKey()); bobStore.storeSignedPreKey( bobSignedPreKeyId, new SignedPreKeyRecord( bobSignedPreKeyId, System.currentTimeMillis(), bobSignedPreKey, bobSignature)); bobStore.storePreKey( bobUnsignedPreKeyId, new PreKeyRecord(bobUnsignedPreKeyId, bobUnsignedPreKey)); return bobPreKeyBundle; } }