import 'dart:collection'; import 'dart:convert'; import 'dart:typed_data'; import 'package:fixnum/fixnum.dart'; import 'package:libsignal_protocol_dart/src/ecc/curve.dart'; import 'package:libsignal_protocol_dart/src/invalid_key_exception.dart'; import 'package:libsignal_protocol_dart/src/protocol/ciphertext_message.dart'; import 'package:libsignal_protocol_dart/src/protocol/pre_key_signal_message.dart'; import 'package:libsignal_protocol_dart/src/protocol/signal_message.dart'; import 'package:libsignal_protocol_dart/src/session_builder.dart'; import 'package:libsignal_protocol_dart/src/session_cipher.dart'; import 'package:libsignal_protocol_dart/src/signal_protocol_address.dart'; import 'package:libsignal_protocol_dart/src/state/pre_key_bundle.dart'; import 'package:libsignal_protocol_dart/src/state/pre_key_record.dart'; import 'package:libsignal_protocol_dart/src/state/signal_protocol_store.dart'; import 'package:libsignal_protocol_dart/src/state/signed_pre_key_record.dart'; import 'package:libsignal_protocol_dart/src/untrusted_identity_exception.dart'; import 'package:test/test.dart'; import 'test_in_memory_identity_key_store.dart'; import 'test_in_memory_signal_protocol_store.dart'; void main() { const aliceAddress = SignalProtocolAddress('+14151111111', 1); const bobAddress = SignalProtocolAddress('+14152222222', 1); test('testBasicPreKeyV2', () async { final aliceStore = TestInMemorySignalProtocolStore(); final aliceSessionBuilder = SessionBuilder.fromSignalStore(aliceStore, bobAddress); final bobStore = TestInMemorySignalProtocolStore(); final bobPreKeyPair = Curve.generateKeyPair(); final bobPreKey = PreKeyBundle( await bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.publicKey, 0, null, null, await bobStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); try { await aliceSessionBuilder.processPreKeyBundle(bobPreKey); throw AssertionError('Should fail with missing unsigned prekey!'); } on InvalidKeyException { // Good! return; } }); Future runInteraction( SignalProtocolStore aliceStore, SignalProtocolStore bobStore) async { final aliceSessionCipher = SessionCipher.fromStore(aliceStore, bobAddress); final bobSessionCipher = SessionCipher.fromStore(bobStore, aliceAddress); const originalMessage = 'smert ze smert'; final aliceMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(originalMessage))); assert(aliceMessage.getType() == CiphertextMessage.whisperType); var plaintext = await bobSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(aliceMessage.serialize())); assert(String.fromCharCodes(plaintext) == originalMessage); final bobMessage = await bobSessionCipher .encrypt(Uint8List.fromList(utf8.encode(originalMessage))); assert(bobMessage.getType() == CiphertextMessage.whisperType); plaintext = await aliceSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(bobMessage.serialize())); assert(String.fromCharCodes(plaintext) == originalMessage); for (var i = 0; i < 10; i++) { final loopingMessage = '''What do we mean by saying that existence precedes essence? We mean that man first of all exists, encounters himself, surges up in the world--and defines himself aftward. $i'''; final aliceLoopingMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(loopingMessage))); final loopingPlaintext = await bobSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(aliceLoopingMessage.serialize())); assert(String.fromCharCodes(loopingPlaintext) == loopingMessage); } for (var i = 0; i < 10; i++) { final loopingMessage = '''What do we mean by saying that existence precedes essence? We mean that man first of all exists, encounters himself, surges up in the world--and defines himself aftward. $i'''; final bobLoopingMessage = await bobSessionCipher .encrypt(Uint8List.fromList(utf8.encode(loopingMessage))); final loopingPlaintext = await aliceSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(bobLoopingMessage.serialize())); assert(String.fromCharCodes(loopingPlaintext) == loopingMessage); } final Set<(String, CiphertextMessage)> aliceOutOfOrderMessages = HashSet<(String, CiphertextMessage)>(); for (var i = 0; i < 10; i++) { final loopingMessage = '''What do we mean by saying that existence precedes essence? We mean that man first of all exists, encounters himself, surges up in the world--and defines himself aftward. $i'''; final aliceLoopingMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(loopingMessage))); aliceOutOfOrderMessages.add((loopingMessage, aliceLoopingMessage)); } for (var i = 0; i < 10; i++) { final loopingMessage = '''What do we mean by saying that existence precedes essence? We mean that man first of all exists, encounters himself, surges up in the world--and defines himself aftward. $i'''; final aliceLoopingMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(loopingMessage))); final loopingPlaintext = await bobSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(aliceLoopingMessage.serialize())); assert(String.fromCharCodes(loopingPlaintext) == loopingMessage); } for (var i = 0; i < 10; i++) { final loopingMessage = 'You can only desire based on what you know: $i'; final bobLoopingMessage = await bobSessionCipher .encrypt(Uint8List.fromList(utf8.encode(loopingMessage))); final loopingPlaintext = await aliceSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(bobLoopingMessage.serialize())); assert(String.fromCharCodes(loopingPlaintext) == loopingMessage); } for (final aliceOutOfOrderMessage in aliceOutOfOrderMessages) { final outOfOrderPlaintext = await bobSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(aliceOutOfOrderMessage.$2.serialize())); assert(String.fromCharCodes(outOfOrderPlaintext) == (aliceOutOfOrderMessage.$1)); } } test('testBasicPreKeyV3', () async { var aliceStore = TestInMemorySignalProtocolStore(); var aliceSessionBuilder = SessionBuilder.fromSignalStore(aliceStore, bobAddress); final bobStore = TestInMemorySignalProtocolStore(); var bobPreKeyPair = Curve.generateKeyPair(); var bobSignedPreKeyPair = Curve.generateKeyPair(); var bobSignedPreKeySignature = Curve.calculateSignature( await bobStore .getIdentityKeyPair() .then((value) => value.getPrivateKey()), bobSignedPreKeyPair.publicKey.serialize()); var bobPreKey = PreKeyBundle( await bobStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.publicKey, 22, bobSignedPreKeyPair.publicKey, bobSignedPreKeySignature, await bobStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); await aliceSessionBuilder.processPreKeyBundle(bobPreKey); assert(await aliceStore.containsSession(bobAddress)); assert(await aliceStore .loadSession(bobAddress) .then((value) => value.sessionState.getSessionVersion()) == 3); const originalMessage = "L'homme est condamné à être libre"; var aliceSessionCipher = SessionCipher.fromStore(aliceStore, bobAddress); var outgoingMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(originalMessage))); assert(outgoingMessage.getType() == CiphertextMessage.prekeyType); final incomingMessage = PreKeySignalMessage(outgoingMessage.serialize()); await bobStore.storePreKey( 31337, PreKeyRecord(bobPreKey.getPreKeyId()!, bobPreKeyPair)); await bobStore.storeSignedPreKey( 22, SignedPreKeyRecord(22, Int64(DateTime.now().millisecondsSinceEpoch), bobSignedPreKeyPair, bobSignedPreKeySignature)); final bobSessionCipher = SessionCipher.fromStore(bobStore, aliceAddress); var plaintext = await bobSessionCipher.decryptWithCallback(incomingMessage, (plaintext) async { final result = utf8.decode(plaintext, allowMalformed: true); assert(originalMessage == result); assert(!await bobStore.containsSession(aliceAddress)); }); assert(await bobStore.containsSession(aliceAddress)); assert(await bobStore .loadSession(aliceAddress) .then((value) => value.sessionState.getSessionVersion()) == 3); assert(originalMessage == utf8.decode(plaintext, allowMalformed: true)); final bobOutgoingMessage = await bobSessionCipher .encrypt(Uint8List.fromList(utf8.encode(originalMessage))); assert(bobOutgoingMessage.getType() == CiphertextMessage.whisperType); final alicePlaintext = await aliceSessionCipher.decryptFromSignal( SignalMessage.fromSerialized(bobOutgoingMessage.serialize())); assert( utf8.decode(alicePlaintext, allowMalformed: true) == originalMessage); await runInteraction(aliceStore, bobStore); aliceStore = TestInMemorySignalProtocolStore(); aliceSessionBuilder = SessionBuilder.fromSignalStore(aliceStore, bobAddress); aliceSessionCipher = SessionCipher.fromStore(aliceStore, bobAddress); bobPreKeyPair = Curve.generateKeyPair(); bobSignedPreKeyPair = Curve.generateKeyPair(); bobSignedPreKeySignature = Curve.calculateSignature( await bobStore .getIdentityKeyPair() .then((value) => value.getPrivateKey()), bobSignedPreKeyPair.publicKey.serialize()); bobPreKey = PreKeyBundle( await bobStore.getLocalRegistrationId(), 1, 31338, bobPreKeyPair.publicKey, 23, bobSignedPreKeyPair.publicKey, bobSignedPreKeySignature, await bobStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); await bobStore.storePreKey( 31338, PreKeyRecord(bobPreKey.getPreKeyId()!, bobPreKeyPair)); await bobStore.storeSignedPreKey( 23, SignedPreKeyRecord(23, Int64(DateTime.now().millisecondsSinceEpoch), bobSignedPreKeyPair, bobSignedPreKeySignature)); await aliceSessionBuilder.processPreKeyBundle(bobPreKey); outgoingMessage = await aliceSessionCipher .encrypt(Uint8List.fromList(utf8.encode(originalMessage))); try { plaintext = await bobSessionCipher .decrypt(PreKeySignalMessage(outgoingMessage.serialize())); throw AssertionError("shouldn't be trusted!"); } on UntrustedIdentityException { await bobStore.saveIdentity(aliceAddress, PreKeySignalMessage(outgoingMessage.serialize()).getIdentityKey()); } plaintext = await bobSessionCipher .decrypt(PreKeySignalMessage(outgoingMessage.serialize())); assert(utf8.decode(plaintext, allowMalformed: true) == originalMessage); bobPreKey = PreKeyBundle( await bobStore.getLocalRegistrationId(), 1, 31337, Curve.generateKeyPair().publicKey, 23, bobSignedPreKeyPair.publicKey, bobSignedPreKeySignature, await aliceStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); try { await aliceSessionBuilder.processPreKeyBundle(bobPreKey); throw AssertionError("shoulnd't be trusted!"); } on UntrustedIdentityException { // good } }); test('testBadSignedPreKeySignature', () async { final aliceStore = TestInMemorySignalProtocolStore(); final aliceSessionBuilder = SessionBuilder.fromSignalStore(aliceStore, bobAddress); final bobIdentityKeyStore = TestInMemoryIdentityKeyStore(); final bobPreKeyPair = Curve.generateKeyPair(); final bobSignedPreKeyPair = Curve.generateKeyPair(); final bobSignedPreKeySignature = Curve.calculateSignature( await bobIdentityKeyStore .getIdentityKeyPair() .then((value) => value.getPrivateKey()), bobSignedPreKeyPair.publicKey.serialize()); for (var i = 0; i < bobSignedPreKeySignature.length * 8; i++) { final modifiedSignature = Uint8List(bobSignedPreKeySignature.length); Curve.arraycopy(bobSignedPreKeySignature, 0, modifiedSignature, 0, modifiedSignature.length); modifiedSignature[i ~/ 8] ^= 0x01 << (i % 8); final bobPreKey = PreKeyBundle( await bobIdentityKeyStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.publicKey, 22, bobSignedPreKeyPair.publicKey, modifiedSignature, await bobIdentityKeyStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); try { await aliceSessionBuilder.processPreKeyBundle(bobPreKey); throw AssertionError('Accepted modified device key signature!'); } on InvalidKeyException { // good } } final bobPreKey = PreKeyBundle( await bobIdentityKeyStore.getLocalRegistrationId(), 1, 31337, bobPreKeyPair.publicKey, 22, bobSignedPreKeyPair.publicKey, bobSignedPreKeySignature, await bobIdentityKeyStore .getIdentityKeyPair() .then((value) => value.getPublicKey())); await aliceSessionBuilder.processPreKeyBundle(bobPreKey); }); }