TM-SGNL-iOS/SignalServiceKit/tests/Messages/SMKSecretSessionCipherTest.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

470 lines
23 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import XCTest
@testable import SignalServiceKit
@testable import LibSignalClient
// https://github.com/signalapp/libsignal-metadata-java/blob/4a0e0c87ea733d5c007488671b74ace0dc5dcbef/tests/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java
// public class SecretSessionCipherTest extends TestCase {
class SMKSecretSessionCipherTest: XCTestCase {
private lazy var aliceMockClient = MockClient(
aci: Aci.constantForTesting("aaaaaaaa-7000-11eb-b32a-33b8a8a487a6"),
phoneNumber: E164("+16505550100")!,
deviceId: 1,
registrationId: 1234
)
private lazy var bobMockClient = MockClient(
aci: Aci.constantForTesting("bbbbbbbb-7000-11eb-b32a-33b8a8a487a6"),
phoneNumber: E164("+16505550101")!,
deviceId: 1,
registrationId: 1234
)
// public void testEncryptDecrypt() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidProtocolBufferException, InvalidMetadataMessageException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException {
func testEncryptDecrypt() {
// TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
// TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore();
// NOTE: We use MockClient to ensure consistency between of our session state.
// initializeSessions(aliceStore, bobStore);
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
// ECKeyPair trustRoot = Curve.generateKeyPair();
let trustRoot = IdentityKeyPair.generate()
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
let senderCertificate = Self.createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: aliceMockClient.identityKeyPair.publicKey,
expirationTimestamp: 31337
)
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
let aliceCipher: SMKSecretSessionCipher = try! aliceMockClient.createSecretSessionCipher()
// byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1),
// senderCertificate, "smert za smert".getBytes());
// NOTE: The java tests don't bother padding the plaintext.
let alicePlaintext = "smert za smert".data(using: .utf8)!
let ciphertext = try! aliceCipher.encryptMessage(
for: bobMockClient.aci,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: .default,
groupId: nil,
senderCertificate: senderCertificate,
protocolContext: NullContext()
)
// SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1));
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
// Pair<SignalProtocolAddress, byte[]> plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335);
let bobPlaintext = try! bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: ciphertext,
timestamp: 31335,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
// assertEquals(new String(plaintext.second()), "smert za smert");
// assertEquals(plaintext.first().getName(), "+14151111111");
// assertEquals(plaintext.first().getDeviceId(), 1);
XCTAssertEqual(String(data: bobPlaintext.paddedPayload, encoding: .utf8), "smert za smert")
XCTAssertEqual(bobPlaintext.senderDeviceId, aliceMockClient.deviceId)
XCTAssertEqual(bobPlaintext.senderAci, aliceMockClient.aci)
XCTAssertEqual(bobPlaintext.senderE164, aliceMockClient.phoneNumber.stringValue)
}
// public void testEncryptDecryptUntrusted() throws Exception {
func testEncryptDecryptUntrusted() {
// TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
// TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore();
// NOTE: We use MockClient to ensure consistency between of our session state.
// initializeSessions(aliceStore, bobStore);
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
// ECKeyPair trustRoot = Curve.generateKeyPair();
// ECKeyPair falseTrustRoot = Curve.generateKeyPair();
let trustRoot = IdentityKeyPair.generate()
let falseTrustRoot = IdentityKeyPair.generate()
// SenderCertificate senderCertificate = createCertificateFor(falseTrustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
let senderCertificate = Self.createCertificateFor(
trustRoot: falseTrustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: aliceMockClient.identityKeyPair.publicKey,
expirationTimestamp: 31337)
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
let aliceCipher: SMKSecretSessionCipher = try! aliceMockClient.createSecretSessionCipher()
// byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1),
// senderCertificate, "и вот я".getBytes());
// NOTE: The java tests don't bother padding the plaintext.
let alicePlaintext = "и вот я".data(using: String.Encoding.utf8)!
let aliceGroupId = Randomness.generateRandomBytes(6)
let aliceContentHint = UnidentifiedSenderMessageContent.ContentHint.implicit
let ciphertext = try! aliceCipher.encryptMessage(
for: bobMockClient.aci,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: aliceContentHint,
groupId: aliceGroupId,
senderCertificate: senderCertificate,
protocolContext: NullContext()
)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
// try {
// bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335);
// throw new AssertionError();
// } catch (InvalidMetadataMessageException e) {
// // good
// }
do {
_ = try bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: ciphertext,
timestamp: 31335,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
XCTFail("Decryption should have failed.")
} catch let knownSenderError as SecretSessionKnownSenderError {
// Decryption is expected to fail.
guard case SMKSecretSessionCipherError.invalidCertificate = knownSenderError.underlyingError else {
XCTFail("wrong underlying error: \(knownSenderError.underlyingError)")
return
}
XCTAssertEqual(knownSenderError.contentHint, aliceContentHint)
XCTAssertEqual(knownSenderError.groupId, aliceGroupId)
XCTAssertNoThrow(
try DecryptionErrorMessage(
originalMessageBytes: knownSenderError.unsealedContent,
type: knownSenderError.cipherType,
timestamp: 31335,
originalSenderDeviceId: knownSenderError.senderDeviceId
)
)
XCTAssertEqual(knownSenderError.senderAci, aliceMockClient.aci)
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// public void testEncryptDecryptExpired() throws Exception {
func testEncryptDecryptExpired() {
// TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
// TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore();
// NOTE: We use MockClient to ensure consistency between of our session state.
// initializeSessions(aliceStore, bobStore);
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
// ECKeyPair trustRoot = Curve.generateKeyPair();
let trustRoot = IdentityKeyPair.generate()
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
let senderCertificate = Self.createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: aliceMockClient.identityKeyPair.publicKey,
expirationTimestamp: 31337)
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
let aliceCipher: SMKSecretSessionCipher = try! aliceMockClient.createSecretSessionCipher()
// byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1),
// senderCertificate, "и вот я".getBytes());
// NOTE: The java tests don't bother padding the plaintext.
let alicePlaintext = "и вот я".data(using: String.Encoding.utf8)!
let aliceGroupId = Randomness.generateRandomBytes(6)
let aliceContentHint = UnidentifiedSenderMessageContent.ContentHint.resendable
let ciphertext = try! aliceCipher.encryptMessage(
for: bobMockClient.aci,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: aliceContentHint,
groupId: aliceGroupId,
senderCertificate: senderCertificate,
protocolContext: NullContext()
)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
// try {
// bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31338);
// throw new AssertionError();
// } catch (InvalidMetadataMessageException e) {
// // good
// }
do {
_ = try bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: ciphertext,
timestamp: 31338,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
XCTFail("Decryption should have failed.")
} catch let knownSenderError as SecretSessionKnownSenderError {
// Decryption is expected to fail.
guard case SMKSecretSessionCipherError.invalidCertificate = knownSenderError.underlyingError else {
XCTFail("wrong underlying error: \(knownSenderError.underlyingError)")
return
}
XCTAssertEqual(knownSenderError.contentHint, aliceContentHint)
XCTAssertEqual(knownSenderError.groupId, aliceGroupId)
XCTAssertNoThrow(
try DecryptionErrorMessage(
originalMessageBytes: knownSenderError.unsealedContent,
type: knownSenderError.cipherType,
timestamp: 31338,
originalSenderDeviceId: knownSenderError.senderDeviceId
)
)
XCTAssertEqual(knownSenderError.senderAci, aliceMockClient.aci)
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// public void testEncryptFromWrongIdentity() throws Exception {
func testEncryptFromWrongIdentity() {
// TestInMemorySignalProtocolStore aliceStore = new TestInMemorySignalProtocolStore();
// TestInMemorySignalProtocolStore bobStore = new TestInMemorySignalProtocolStore();
// NOTE: We use MockClient to ensure consistency between of our session state.
// initializeSessions(aliceStore, bobStore);
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
// ECKeyPair trustRoot = Curve.generateKeyPair();
let trustRoot = IdentityKeyPair.generate()
// ECKeyPair randomKeyPair = Curve.generateKeyPair();
let randomKeyPair = IdentityKeyPair.generate()
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, randomKeyPair.getPublicKey(), 31337);
let senderCertificate = Self.createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: randomKeyPair.publicKey,
expirationTimestamp: 31337)
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
let aliceCipher: SMKSecretSessionCipher = try! aliceMockClient.createSecretSessionCipher()
// byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1),
// senderCertificate, "smert za smert".getBytes());
// NOTE: The java tests don't bother padding the plaintext.
let alicePlaintext = "smert za smert".data(using: String.Encoding.utf8)!
let ciphertext = try! aliceCipher.encryptMessage(
for: bobMockClient.aci,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: .default,
groupId: nil,
senderCertificate: senderCertificate,
protocolContext: NullContext()
)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
// try {
// bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335);
// } catch (InvalidMetadataMessageException e) {
// // good
// }
do {
_ = try bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: ciphertext,
timestamp: 31335,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
XCTFail("Decryption should have failed.")
} catch SignalError.invalidMessage(_) {
// Decryption is expected to fail.
// FIXME: This particular failure doesn't get wrapped as a SecretSessionKnownSenderError
// because it's checked before the unwrapped message is returned.
// Why? Because it uses crypto values calculated during unwrapping to validate the sender certificate.
} catch {
XCTFail("unexpected error: \(error)")
}
}
func testGroupEncryptDecrypt_Success() {
// Setup: Initialize sessions and sender certificate
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
let trustRoot = IdentityKeyPair.generate()
let senderCertificate = Self.createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: aliceMockClient.identityKeyPair.publicKey,
expirationTimestamp: 31337)
// Setup: Distribute alice's sender key to bob's key store
let distributionId = UUID()
let aliceSenderKeyMessage = try! SenderKeyDistributionMessage(
from: aliceMockClient.protocolAddress,
distributionId: distributionId,
store: aliceMockClient.senderKeyStore,
context: NullContext())
try! processSenderKeyDistributionMessage(
aliceSenderKeyMessage,
from: aliceMockClient.protocolAddress,
store: bobMockClient.senderKeyStore,
context: NullContext())
// Test: Alice encrypt's a message using `groupEncryptMessage`
let aliceCipher = try! aliceMockClient.createSecretSessionCipher()
let alicePlaintext = "beltalowda".data(using: String.Encoding.utf8)!
let aliceCiphertext = try! aliceCipher.groupEncryptMessage(
recipients: [bobMockClient.protocolAddress],
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
groupId: Data(),
distributionId: distributionId,
contentHint: .implicit,
protocolContext: nil).map { $0 }
// This splits out irrelevant per-recipient data from the shared sender key message
// This is only necessary in tests. The server would usually handle this.
let singleRecipientCiphertext = try! sealedSenderMultiRecipientMessageForSingleRecipient(aliceCiphertext)
// Test: Bob decrypts the ciphertext
let bobCipher = try! bobMockClient.createSecretSessionCipher()
let bobPlaintext = try! bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: Data(singleRecipientCiphertext),
timestamp: 31335,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
// Verify
XCTAssertEqual(String(data: bobPlaintext.paddedPayload, encoding: .utf8), "beltalowda")
XCTAssertEqual(bobPlaintext.senderAci, aliceMockClient.aci)
XCTAssertEqual(bobPlaintext.senderDeviceId, aliceMockClient.deviceId)
XCTAssertEqual(bobPlaintext.messageType, .senderKey)
}
func testGroupEncryptDecrypt_Failure() {
// Setup: Initialize sessions and sender certificate
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
let trustRoot = IdentityKeyPair.generate()
let senderCertificate = Self.createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.sealedSenderAddress,
identityKey: aliceMockClient.identityKeyPair.publicKey,
expirationTimestamp: 31337)
// Setup: Alice creates a sender key
// Test: Bob intentionally does not process Alice's SKDM to simulate an unsent key
let distributionId = UUID()
_ = try! SenderKeyDistributionMessage(
from: aliceMockClient.protocolAddress,
distributionId: distributionId,
store: aliceMockClient.senderKeyStore,
context: NullContext())
// Test: Alice encrypt's a message using `groupEncryptMessage`
let aliceCipher = try! aliceMockClient.createSecretSessionCipher()
let alicePlaintext = "beltalowda".data(using: String.Encoding.utf8)!
let aliceCiphertext = try! aliceCipher.groupEncryptMessage(
recipients: [bobMockClient.protocolAddress],
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
groupId: "inyalowda".data(using: String.Encoding.utf8)!,
distributionId: distributionId,
contentHint: .resendable,
protocolContext: nil).map { $0 }
// This splits out irrelevant per-recipient data from the shared sender key message
// This is only necessary in tests. The server would usually handle this.
let singleRecipientCiphertext = try! sealedSenderMultiRecipientMessageForSingleRecipient(aliceCiphertext)
// Test: Bob decrypts the ciphertext
let bobCipher = try! bobMockClient.createSecretSessionCipher()
do {
_ = try bobCipher.decryptMessage(
trustRoot: trustRoot.publicKey,
cipherTextData: Data(singleRecipientCiphertext),
timestamp: 31335,
localIdentifiers: bobMockClient.localIdentifiers,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil
)
XCTFail("Decryption should have failed.")
} catch let knownSenderError as SecretSessionKnownSenderError {
// Verify: We need to make sure that the sender, group, and contentHint are preserved
// through decryption failures because of missing a missing sender key. This will
// help with recovery.
XCTAssertEqual(knownSenderError.senderAci, aliceMockClient.aci)
XCTAssertEqual(knownSenderError.senderDeviceId, UInt32(aliceMockClient.deviceId))
XCTAssertEqual(Data(knownSenderError.groupId!), "inyalowda".data(using: String.Encoding.utf8)!)
XCTAssertEqual(knownSenderError.contentHint, .resendable)
XCTAssertNoThrow(
try DecryptionErrorMessage(
originalMessageBytes: knownSenderError.unsealedContent,
type: knownSenderError.cipherType,
timestamp: 31335,
originalSenderDeviceId: knownSenderError.senderDeviceId
)
)
if case SignalError.sessionNotFound(_) = knownSenderError.underlyingError {
// Expected
} else {
XCTFail("Unexpected: error is not sessionNotFound")
}
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// MARK: - Utils
// private SenderCertificate createCertificateFor(ECKeyPair trustRoot, String sender, int deviceId, ECPublicKey identityKey, long expires)
// throws InvalidKeyException, InvalidCertificateException, InvalidProtocolBufferException {
internal static func createCertificateFor(trustRoot: IdentityKeyPair,
senderAddress: SealedSenderAddress,
identityKey: PublicKey,
expirationTimestamp: UInt64) -> SenderCertificate {
let serverKey = IdentityKeyPair.generate()
let serverCertificate = try! ServerCertificate(keyId: 1,
publicKey: serverKey.publicKey,
trustRoot: trustRoot.privateKey)
return try! SenderCertificate(sender: senderAddress,
publicKey: identityKey,
expiration: expirationTimestamp,
signerCertificate: serverCertificate,
signerKey: serverKey.privateKey)
}
// private void initializeSessions(TestInMemorySignalProtocolStore aliceStore, TestInMemorySignalProtocolStore bobStore)
// throws InvalidKeyException, UntrustedIdentityException
private func initializeSessions(aliceMockClient: MockClient, bobMockClient: MockClient) {
aliceMockClient.initializeSession(with: bobMockClient)
}
}