twonly-app-dependencies/libsignal_protocol_dart/test/session_builder_test.dart
2025-12-07 16:10:41 +01:00

347 lines
14 KiB
Dart

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<void> 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);
});
}