// // Copyright 2019 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // #if TESTABLE_BUILD import Foundation import LibSignalClient extension InMemorySignalProtocolStore: WritablePreKeyStore {} extension InMemorySignalProtocolStore: WritableSignedPreKeyStore {} extension InMemorySignalProtocolStore: WritableKyberPreKeyStore {} /// A helper for tests which can initializes Signal Protocol sessions /// and then encrypt and decrypt messages for those sessions. struct TestProtocolRunner { init() { } /// Sets up a session for `senderClient` to send to `recipientClient`, but not vice versa. /// /// Messages from `senderClient` will be PreKey messages. func initializePreKeys( senderClient: TestSignalClient, recipientClient: TestSignalClient, hasSignedPreKey: Bool = true, hasOneTimePreKey: Bool = true, transaction: DBWriteTransaction, ) throws { senderClient.ensureRecipientId(tx: transaction) recipientClient.ensureRecipientId(tx: transaction) let bobPreKey = PrivateKey.generate() let bobSignedPreKey = PrivateKey.generate() let bobKyberPreKey = KEMKeyPair.generate() let bobSignedPreKeyPublic = bobSignedPreKey.publicKey.serialize() let bobKyberPreKeyPublic = bobKyberPreKey.publicKey.serialize() let bobIdentityKey = recipientClient.identityKeyPair.identityKeyPair let bobSignedPreKeySignature = bobIdentityKey.privateKey.generateSignature(message: bobSignedPreKeyPublic) let bobKyberPreKeySignature = bobIdentityKey.privateKey.generateSignature(message: bobKyberPreKeyPublic) let bobRegistrationId = try recipientClient.identityKeyStore.localRegistrationId(context: transaction) let prekeyId: UInt32 = 4570 let signedPrekeyId: UInt32 = 3006 let kyberPrekeyId: UInt32 = 7777 let bobBundle = try LibSignalClient.PreKeyBundle( registrationId: bobRegistrationId, deviceId: recipientClient.deviceId, prekeyId: prekeyId, prekey: bobPreKey.publicKey, signedPrekeyId: signedPrekeyId, signedPrekey: bobSignedPreKey.publicKey, signedPrekeySignature: bobSignedPreKeySignature, identity: bobIdentityKey.identityKey, kyberPrekeyId: kyberPrekeyId, kyberPrekey: bobKyberPreKey.publicKey, kyberPrekeySignature: bobKyberPreKeySignature, ) // Alice processes the bundle: try processPreKeyBundle( bobBundle, for: recipientClient.protocolAddress, ourAddress: senderClient.protocolAddress, sessionStore: senderClient.sessionStore, identityStore: senderClient.identityKeyStore, context: transaction, ) // Bob does the same: if hasOneTimePreKey { try recipientClient.preKeyStore.storePreKey( LibSignalClient.PreKeyRecord(id: prekeyId, privateKey: bobPreKey), replacedAt: nil, context: transaction, ) } if hasSignedPreKey { try recipientClient.signedPreKeyStore.storeSignedPreKey( LibSignalClient.SignedPreKeyRecord( id: signedPrekeyId, timestamp: 42000, privateKey: bobSignedPreKey, signature: bobSignedPreKeySignature, ), replacedAt: nil, context: transaction, ) } try recipientClient.kyberPreKeyStore.storeKyberPreKey( LibSignalClient.KyberPreKeyRecord( id: kyberPrekeyId, timestamp: 42000, keyPair: bobKyberPreKey, signature: bobKyberPreKeySignature, ), isOneTime: true, replacedAt: nil, context: transaction, ) } /// Sets up a session between `senderClient` and `recipientClient`, so that either can talk to the other. /// /// Messages between both clients will be "Whisper" / "ciphertext" / "Signal" messages. func initialize( senderClient: TestSignalClient, recipientClient: TestSignalClient, hasSignedPreKey: Bool = true, hasOneTimePreKey: Bool = true, transaction: DBWriteTransaction, ) throws { try initializePreKeys( senderClient: senderClient, recipientClient: recipientClient, hasSignedPreKey: hasSignedPreKey, hasOneTimePreKey: hasOneTimePreKey, transaction: transaction, ) // Then Alice sends a message to Bob so he gets her pre-key as well. let aliceMessage = try encrypt( Data(), senderClient: senderClient, recipient: recipientClient.protocolAddress, context: transaction, ) _ = try signalDecryptPreKey( message: PreKeySignalMessage(bytes: aliceMessage.serialize()), from: senderClient.protocolAddress, localAddress: recipientClient.protocolAddress, sessionStore: recipientClient.sessionStore, identityStore: recipientClient.identityKeyStore, preKeyStore: recipientClient.preKeyStore, signedPreKeyStore: recipientClient.signedPreKeyStore, kyberPreKeyStore: recipientClient.kyberPreKeyStore, context: transaction, ) // Finally, Bob sends a message back to acknowledge the pre-key. let bobMessage = try encrypt( Data(), senderClient: recipientClient, recipient: senderClient.protocolAddress, context: transaction, ) _ = try signalDecrypt( message: SignalMessage(bytes: bobMessage.serialize()), from: recipientClient.protocolAddress, to: senderClient.protocolAddress, sessionStore: senderClient.sessionStore, identityStore: senderClient.identityKeyStore, context: transaction, ) } func encrypt( _ plaintext: Data, senderClient: TestSignalClient, recipient: ProtocolAddress, context: StoreContext, ) throws -> CiphertextMessage { return try signalEncrypt( message: plaintext, for: recipient, localAddress: senderClient.protocolAddress, sessionStore: senderClient.sessionStore, identityStore: senderClient.identityKeyStore, context: context, ) } func decrypt( _ cipherMessage: CiphertextMessage, recipientClient: TestSignalClient, sender: ProtocolAddress, context: StoreContext, ) throws -> Data { owsPrecondition(cipherMessage.messageType == .whisper, "only bare SignalMessages are supported") let message = try SignalMessage(bytes: cipherMessage.serialize()) return try signalDecrypt( message: message, from: sender, to: recipientClient.protocolAddress, sessionStore: recipientClient.sessionStore, identityStore: recipientClient.identityKeyStore, context: context, ) } } public typealias SignalE164Identifier = String public typealias SignalAccountIdentifier = String /// Represents a Signal installation, it can represent the local client or /// a remote client. protocol TestSignalClient { var identityKeyPair: ECKeyPair { get } var identityKey: Data { get } var e164Identifier: SignalE164Identifier? { get } var serviceId: ServiceId { get } var deviceId: UInt32 { get } var address: SignalServiceAddress { get } var protocolAddress: ProtocolAddress { get } var sessionStore: LibSignalClient.SessionStore { get } var preKeyStore: LibSignalClient.PreKeyStore & WritablePreKeyStore { get } var signedPreKeyStore: LibSignalClient.SignedPreKeyStore & WritableSignedPreKeyStore { get } var kyberPreKeyStore: LibSignalClient.KyberPreKeyStore & WritableKyberPreKeyStore { get } var identityKeyStore: LibSignalClient.IdentityKeyStore { get } } extension TestSignalClient { var identityKey: Data { return identityKeyPair.publicKey } var address: SignalServiceAddress { return SignalServiceAddress(serviceId: serviceId, phoneNumber: e164Identifier) } var protocolAddress: ProtocolAddress { return ProtocolAddress(serviceId, deviceId: deviceId) } func ensureRecipientId(tx: DBWriteTransaction) { _ = DependenciesBridge.shared.recipientFetcher.fetchOrCreate(serviceId: serviceId, tx: tx) } } /// Can be used to represent the protocol state held by a remote client. /// i.e. someone who's sending messages to the local client. struct FakeSignalClient: TestSignalClient { var sessionStore: LibSignalClient.SessionStore { return protocolStore } var preKeyStore: LibSignalClient.PreKeyStore & WritablePreKeyStore { return protocolStore } var signedPreKeyStore: LibSignalClient.SignedPreKeyStore & WritableSignedPreKeyStore { return protocolStore } var identityKeyStore: LibSignalClient.IdentityKeyStore { return protocolStore } var kyberPreKeyStore: LibSignalClient.KyberPreKeyStore & WritableKyberPreKeyStore { return protocolStore } let e164Identifier: SignalE164Identifier? let serviceId: ServiceId let protocolStore: InMemorySignalProtocolStore var deviceId = UInt32(1) var identityKeyPair: ECKeyPair { return ECKeyPair(try! protocolStore.identityKeyPair(context: NullContext())) } static func generate() -> FakeSignalClient { return FakeSignalClient( e164Identifier: CommonGenerator.e164(), serviceId: Aci.randomForTesting(), protocolStore: InMemorySignalProtocolStore(identity: .generate(), registrationId: 1), ) } static func generate( e164Identifier: SignalE164Identifier? = nil, aci: Aci? = nil, deviceID: UInt32? = nil, ) -> FakeSignalClient { var result = FakeSignalClient( e164Identifier: e164Identifier, serviceId: aci ?? Aci.randomForTesting(), protocolStore: InMemorySignalProtocolStore(identity: .generate(), registrationId: 1), ) if let deviceID { result.deviceId = deviceID } return result } } /// Represents the local user, backed by the same protocol stores, etc. /// used in the app. struct LocalSignalClient: TestSignalClient { let identity: OWSIdentity let _preKeyStore: PreKeyStore let _sessionStore: SessionManagerForIdentity init(identity: OWSIdentity = .aci) { self.identity = identity self._preKeyStore = PreKeyStore() self._sessionStore = SessionManagerForIdentity( identity: identity, recipientIdFinder: DependenciesBridge.shared.recipientIdFinder, sessionStore: SessionStore(), ) } var identityKeyPair: ECKeyPair { return SSKEnvironment.shared.databaseStorageRef.read { tx in return DependenciesBridge.shared.identityManager.identityKeyPair(for: identity, tx: tx)! } } var e164Identifier: SignalE164Identifier? { return DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction?.phoneNumber } var serviceId: ServiceId { let localIdentifiers = DependenciesBridge.shared.tsAccountManager.localIdentifiersWithMaybeSneakyTransaction! switch identity { case .aci: return localIdentifiers.aci case .pni: return localIdentifiers.pni! } } let deviceId: UInt32 = 1 var sessionStore: LibSignalClient.SessionStore { return _sessionStore } var preKeyStore: LibSignalClient.PreKeyStore & WritablePreKeyStore { return self._preKeyStore.forIdentity(self.identity) } var signedPreKeyStore: LibSignalClient.SignedPreKeyStore & WritableSignedPreKeyStore { return self._preKeyStore.forIdentity(self.identity) } var kyberPreKeyStore: LibSignalClient.KyberPreKeyStore & WritableKyberPreKeyStore { return self._preKeyStore.forIdentity(self.identity) } var identityKeyStore: IdentityKeyStore { return SSKEnvironment.shared.databaseStorageRef.read { transaction in return try! DependenciesBridge.shared.identityManager.libSignalStore(for: identity, tx: transaction) } } func linkedDevice(deviceID: UInt32) -> FakeSignalClient { return FakeSignalClient( e164Identifier: e164Identifier, serviceId: serviceId, protocolStore: InMemorySignalProtocolStore(identity: identityKeyPair.identityKeyPair, registrationId: 1), deviceId: deviceID, ) } } var envelopeId: UInt64 = 0 struct FakeService { let localClient: LocalSignalClient let runner: TestProtocolRunner init(localClient: LocalSignalClient, runner: TestProtocolRunner) { self.localClient = localClient self.runner = runner } func envelopeBuilder(fromSenderClient senderClient: TestSignalClient, bodyText: String? = nil) throws -> SSKProtoEnvelopeBuilder { envelopeId += 1 let builder = SSKProtoEnvelope.builder(timestamp: envelopeId) builder.setType(.ciphertext) builder.setSourceDevice(senderClient.deviceId) let timestamp = MessageTimestampGenerator.sharedInstance.generateTimestamp() builder.setTimestamp(timestamp) let content = try buildEncryptedContentData(fromSenderClient: senderClient, timestamp: timestamp, bodyText: bodyText) builder.setContent(content) // builder.setServerTimestamp(serverTimestamp) // builder.setServerGuid(serverGuid) return builder } func envelopeBuilder(fromSenderClient senderClient: TestSignalClient, groupV2Context: SSKProtoGroupContextV2) throws -> SSKProtoEnvelopeBuilder { envelopeId += 1 let builder = SSKProtoEnvelope.builder(timestamp: envelopeId) builder.setType(.ciphertext) builder.setSourceDevice(senderClient.deviceId) let content = try buildEncryptedContentData(fromSenderClient: senderClient, groupV2Context: groupV2Context) builder.setContent(content) // builder.setServerTimestamp(serverTimestamp) // builder.setServerGuid(serverGuid) return builder } func envelopeBuilderForServerGeneratedDeliveryReceipt(fromSenderClient senderClient: TestSignalClient) -> SSKProtoEnvelopeBuilder { envelopeId += 1 let builder = SSKProtoEnvelope.builder(timestamp: envelopeId) builder.setType(.receipt) builder.setSourceDevice(senderClient.deviceId) return builder } func envelopeBuilderForInvalidEnvelope(fromSenderClient senderClient: TestSignalClient) -> SSKProtoEnvelopeBuilder { envelopeId += 1 let builder = SSKProtoEnvelope.builder(timestamp: envelopeId) builder.setType(.unknown) builder.setSourceDevice(senderClient.deviceId) builder.setContent("Hello world".data(using: .utf8)!) return builder } func envelopeBuilderForUDDeliveryReceipt(fromSenderClient senderClient: TestSignalClient, timestamp: UInt64) -> SSKProtoEnvelopeBuilder { envelopeId += 1 let builder = SSKProtoEnvelope.builder(timestamp: envelopeId) builder.setType(.ciphertext) builder.setSourceDevice(senderClient.deviceId) let content = try! buildEncryptedContentData(fromSenderClient: senderClient, deliveryReceiptForMessage: timestamp) builder.setContent(content) return builder } func buildEncryptedContentData(fromSenderClient senderClient: TestSignalClient, timestamp: UInt64, bodyText: String?) throws -> Data { let plaintext = try buildContentData(timestamp: timestamp, bodyText: bodyText) let cipherMessage: CiphertextMessage = SSKEnvironment.shared.databaseStorageRef.write { transaction in return try! self.runner.encrypt( plaintext, senderClient: senderClient, recipient: self.localClient.protocolAddress, context: transaction, ) } assert(cipherMessage.messageType == .whisper) return cipherMessage.serialize() } func buildEncryptedContentData(fromSenderClient senderClient: TestSignalClient, groupV2Context: SSKProtoGroupContextV2) throws -> Data { let plaintext = try buildContentData(groupV2Context: groupV2Context) let cipherMessage: CiphertextMessage = SSKEnvironment.shared.databaseStorageRef.write { transaction in return try! self.runner.encrypt( plaintext, senderClient: senderClient, recipient: self.localClient.protocolAddress, context: transaction, ) } assert(cipherMessage.messageType == .whisper) return cipherMessage.serialize() } func buildEncryptedContentData(fromSenderClient senderClient: TestSignalClient, deliveryReceiptForMessage timestamp: UInt64) throws -> Data { let plaintext = try buildContentData(deliveryReceiptForMessage: timestamp) let cipherMessage: CiphertextMessage = SSKEnvironment.shared.databaseStorageRef.write { transaction in return try! self.runner.encrypt( plaintext, senderClient: senderClient, recipient: self.localClient.protocolAddress, context: transaction, ) } assert(cipherMessage.messageType == .whisper) return cipherMessage.serialize() } func buildContentData(timestamp: UInt64, bodyText: String?) throws -> Data { let dataMessageBuilder = SSKProtoDataMessage.builder() dataMessageBuilder.setTimestamp(timestamp) if let bodyText { dataMessageBuilder.setBody(bodyText) } else { dataMessageBuilder.setBody(CommonGenerator.paragraph) } let contentBuilder = SSKProtoContent.builder() contentBuilder.setDataMessage(try dataMessageBuilder.build()) return try contentBuilder.buildSerializedData() } func buildSyncSentMessage(bodyText: String, recipient: SignalServiceAddress, timestamp: UInt64) throws -> Data { guard let destinationServiceId = recipient.serviceId else { owsFail("Cannot build sync message without a recipient UUID. Test is not set up correctly") } let dataMessageBuilder = SSKProtoDataMessage.builder() dataMessageBuilder.setBody(bodyText) dataMessageBuilder.setTimestamp(timestamp) let sentBuilder = SSKProtoSyncMessageSent.builder() sentBuilder.setMessage(try dataMessageBuilder.build()) sentBuilder.setTimestamp(timestamp) sentBuilder.setDestinationServiceIDBinary(destinationServiceId.serviceIdBinary) let syncMessageBuilder = SSKProtoSyncMessage.builder() syncMessageBuilder.setSent(try sentBuilder.build()) let contentBuilder = SSKProtoContent.builder() contentBuilder.setSyncMessage(try syncMessageBuilder.build()) return try contentBuilder.buildSerializedData() } func buildContentData(groupV2Context: SSKProtoGroupContextV2) throws -> Data { let dataMessageBuilder = SSKProtoDataMessage.builder() dataMessageBuilder.setGroupV2(groupV2Context) let contentBuilder = SSKProtoContent.builder() contentBuilder.setDataMessage(try dataMessageBuilder.build()) return try contentBuilder.buildSerializedData() } func buildContentData(deliveryReceiptForMessage timestamp: UInt64) throws -> Data { let receiptMessageBuilder = SSKProtoReceiptMessage.builder() receiptMessageBuilder.setType(.delivery) receiptMessageBuilder.setTimestamp([timestamp]) let contentBuilder = SSKProtoContent.builder() contentBuilder.setReceiptMessage(receiptMessageBuilder.buildInfallibly()) return try contentBuilder.buildSerializedData() } } #endif