Compare commits

..

1 Commits

Author SHA1 Message Date
Matthew Chen
81f7f4d6e6 Rename Logger to OWSLog. 2021-03-05 11:59:01 -03:00
7 changed files with 129 additions and 374 deletions

View File

@ -39,12 +39,15 @@ A Swift & Objective-C library used by other Signal libraries.
s.resources = ["SignalMetadataKit/Resources/Certificates/*"]
s.dependency 'AxolotlKit'
s.dependency 'CocoaLumberjack'
s.dependency 'Curve25519Kit'
s.dependency 'SignalClient'
s.dependency 'HKDFKit'
s.dependency 'SignalCoreKit'
s.dependency 'SwiftProtobuf'
s.dependency 'SignalClient'
s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'SignalMetadataKitTests/src/**/*.{h,m,swift}'
end

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -11,7 +11,10 @@ public enum SMKCertificateError: Error {
case invalidCertificate(description: String)
}
public protocol SMKCertificateValidator {
@objc(SMKCertificateValidator)
public protocol SMKCertificateValidatorObjC {}
public protocol SMKCertificateValidator: SMKCertificateValidatorObjC {
func throwswrapped_validate(senderCertificate: SenderCertificate, validationTime: UInt64) throws
func throwswrapped_validate(serverCertificate: ServerCertificate) throws
}
@ -50,9 +53,9 @@ public protocol SMKCertificateValidator {
// }
guard try serverCertificate.publicKey.verifySignature(message: senderCertificate.certificateBytes,
signature: senderCertificate.signatureBytes) else {
Logger.error("Sender certificate signature verification failed.")
OWSLog.error("Sender certificate signature verification failed.")
let error = SMKCertificateError.invalidCertificate(description: "Sender certificate signature verification failed.")
Logger.error("\(error)")
OWSLog.error("\(error)")
throw error
}
@ -61,7 +64,7 @@ public protocol SMKCertificateValidator {
// }
guard validationTime <= senderCertificate.expiration else {
let error = SMKCertificateError.invalidCertificate(description: "Certficate is expired.")
Logger.error("\(error)")
OWSLog.error("\(error)")
throw error
}
@ -79,7 +82,7 @@ public protocol SMKCertificateValidator {
guard try trustRoot.key.verifySignature(message: serverCertificate.certificateBytes,
signature: serverCertificate.signatureBytes) else {
let error = SMKCertificateError.invalidCertificate(description: "Server certificate signature verification failed.")
Logger.error("\(error)")
OWSLog.error("\(error)")
throw error
}
@ -88,7 +91,7 @@ public protocol SMKCertificateValidator {
// }
guard !SMKCertificateDefaultValidator.kRevokedCertificateIds.contains(serverCertificate.keyId) else {
let error = SMKCertificateError.invalidCertificate(description: "Revoked certificate.")
Logger.error("\(error)")
OWSLog.error("\(error)")
throw error
}

View File

@ -3,28 +3,47 @@
//
import Foundation
import AxolotlKit
import Curve25519Kit
import SignalCoreKit
import SignalClient
public struct SecretSessionKnownSenderError: Error {
@objc
public class SecretSessionKnownSenderError: NSObject, CustomNSError {
@objc
public static let kSenderE164Key = "kSenderE164Key"
@objc
public static let kSenderUuidKey = "kSenderUuidKey"
@objc
public static let kSenderDeviceIdKey = "kSenderDeviceIdKey"
public let senderAddress: SMKAddress
public let senderDeviceId: UInt32
public let cipherType: CiphertextMessage.MessageType
public let groupId: Data?
public let unsealedContent: Data
public let contentHint: UnidentifiedSenderMessageContent.ContentHint
public let underlyingError: Error
init(messageContent: UnidentifiedSenderMessageContent, underlyingError: Error) {
self.senderAddress = SMKAddress(messageContent.senderCertificate.sender)
self.senderDeviceId = messageContent.senderCertificate.sender.deviceId
self.cipherType = messageContent.messageType
self.groupId = messageContent.groupId.map { Data($0) }
self.unsealedContent = Data(messageContent.contents)
self.contentHint = messageContent.contentHint
init(senderAddress: SMKAddress, senderDeviceId: UInt32, underlyingError: Error) {
self.senderAddress = senderAddress
self.senderDeviceId = senderDeviceId
self.underlyingError = underlyingError
}
public var errorUserInfo: [String: Any] {
var info: [String: Any] = [
type(of: self).kSenderDeviceIdKey: self.senderDeviceId,
NSUnderlyingErrorKey: (underlyingError as NSError)
]
if let e164 = senderAddress.e164 {
info[type(of: self).kSenderE164Key] = e164
}
if let uuid = senderAddress.uuid {
info[type(of: self).kSenderUuidKey] = uuid
}
return info
}
}
@objc
@ -78,8 +97,6 @@ private class SMKStaticKeys: NSObject {
@objc public enum SMKMessageType: Int {
case whisper
case prekey
case senderKey
case plaintext
}
@objc
@ -135,10 +152,6 @@ fileprivate extension SMKMessageType {
self = .whisper
case .preKey:
self = .prekey
case .senderKey:
self = .senderKey
case .plaintext:
self = .plaintext
default:
fatalError("not ready for other kinds of messages yet")
}
@ -151,113 +164,74 @@ fileprivate extension SMKMessageType {
private let kSMKSecretSessionCipherMacLength: UInt = 10
private let sessionStore: SessionStore
private let preKeyStore: PreKeyStore
private let signedPreKeyStore: SignedPreKeyStore
private let identityStore: IdentityKeyStore
private let senderKeyStore: SenderKeyStore
private let sessionStore: SignalClient.SessionStore
private let preKeyStore: SignalClient.PreKeyStore
private let signedPreKeyStore: SignalClient.SignedPreKeyStore
private let identityStore: SignalClient.IdentityKeyStore
// public SecretSessionCipher(SignalProtocolStore signalProtocolStore) {
public init(sessionStore: SessionStore,
preKeyStore: PreKeyStore,
signedPreKeyStore: SignedPreKeyStore,
identityStore: IdentityKeyStore,
senderKeyStore: SenderKeyStore) throws {
public init(sessionStore: SignalClient.SessionStore,
preKeyStore: SignalClient.PreKeyStore,
signedPreKeyStore: SignalClient.SignedPreKeyStore,
identityStore: SignalClient.IdentityKeyStore) throws {
self.sessionStore = sessionStore
self.preKeyStore = preKeyStore
self.signedPreKeyStore = signedPreKeyStore
self.identityStore = identityStore
self.senderKeyStore = senderKeyStore
}
@objc(initWithSessionStore:preKeyStore:signedPreKeyStore:identityStore:error:)
public convenience init(transitionalSessionStore sessionStore: AxolotlKit.SessionStore,
preKeyStore: AxolotlKit.PreKeyStore,
signedPreKeyStore: AxolotlKit.SignedPreKeyStore,
identityStore: AxolotlKit.IdentityKeyStore) throws {
try self.init(
sessionStore: sessionStore as! SignalClient.SessionStore,
preKeyStore: preKeyStore as! SignalClient.PreKeyStore,
signedPreKeyStore: signedPreKeyStore as! SignalClient.SignedPreKeyStore,
identityStore: identityStore as! SignalClient.IdentityKeyStore)
}
// MARK: - Public
public func encryptMessage(
recipient: SMKAddress,
deviceId: Int32,
paddedPlaintext: Data,
contentHint: UnidentifiedSenderMessageContent.ContentHint,
groupId: Data?,
senderCertificate: SenderCertificate,
protocolContext: StoreContext
) throws -> Data {
// public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext)
public func throwswrapped_encryptMessage(recipient: SMKAddress,
deviceId: Int32,
paddedPlaintext: Data,
senderCertificate: SenderCertificate,
protocolContext: SPKProtocolWriteContext?) throws -> Data {
guard deviceId > 0 else {
throw SMKError.assertionError(description: "\(logTag) invalid deviceId")
}
// CiphertextMessage message = new SessionCipher(signalProtocolStore, destinationAddress).encrypt(paddedPlaintext);
let recipientAddress = try ProtocolAddress(from: recipient, deviceId: UInt32(bitPattern: deviceId))
let ciphertextMessage = try signalEncrypt(
message: paddedPlaintext,
for: recipientAddress,
sessionStore: sessionStore,
identityStore: identityStore,
context: protocolContext)
let usmc = try UnidentifiedSenderMessageContent(
ciphertextMessage,
from: senderCertificate,
contentHint: contentHint,
groupId: groupId ?? Data())
let outerBytes = try sealedSenderEncrypt(
usmc,
for: recipientAddress,
identityStore: identityStore,
context: protocolContext)
return Data(outerBytes)
}
public func groupEncryptMessage(recipients: [ProtocolAddress],
paddedPlaintext: Data,
senderCertificate: SenderCertificate,
groupId: Data,
distributionId: UUID,
contentHint: UnidentifiedSenderMessageContent.ContentHint = .default,
protocolContext: StoreContext?) throws -> Data {
let senderAddress = try ProtocolAddress(from: senderCertificate.sender)
let ciphertext = try groupEncrypt(
paddedPlaintext,
from: senderAddress,
distributionId: distributionId,
store: senderKeyStore,
context: protocolContext ?? NullContext())
let udMessageContent = try UnidentifiedSenderMessageContent(
ciphertext,
from: senderCertificate,
contentHint: contentHint,
groupId: groupId)
let multiRecipientMessage = try sealedSenderMultiRecipientEncrypt(
udMessageContent,
for: recipients,
identityStore: identityStore,
sessionStore: sessionStore,
context: protocolContext ?? NullContext())
return Data(multiRecipientMessage)
// Allow nil contexts for testing.
return Data(try sealedSenderEncrypt(message: paddedPlaintext,
for: recipientAddress,
from: senderCertificate,
sessionStore: sessionStore,
identityStore: identityStore,
context: (protocolContext as! StoreContext?) ?? NullContext()))
}
// public Pair<SignalProtocolAddress, byte[]> decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp)
// throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyException, ProtocolNoSessionException, ProtocolLegacyMessageException, ProtocolInvalidVersionException, ProtocolDuplicateMessageException, ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException
public func throwswrapped_decryptMessage(certificateValidator: SMKCertificateValidator,
@objc
public func throwswrapped_decryptMessage(certificateValidator: SMKCertificateValidatorObjC,
cipherTextData: Data,
timestamp: UInt64,
localE164: String?,
localUuid: UUID?,
localDeviceId: Int32,
protocolContext: StoreContext?) throws -> SMKDecryptResult {
protocolContext: SPKProtocolWriteContext?) throws -> SMKDecryptResult {
guard timestamp > 0 else {
throw SMKError.assertionError(description: "\(logTag) invalid timestamp")
}
// Allow nil contexts for testing.
let context = protocolContext ?? NullContext()
let context = (protocolContext as! StoreContext?) ?? NullContext()
let messageContent = try UnidentifiedSenderMessageContent(message: cipherTextData,
identityStore: self.identityStore,
context: context)
@ -267,12 +241,13 @@ fileprivate extension SMKMessageType {
guard !SMKAddress(senderAddress).matches(localAddress) ||
Int32(bitPattern: senderAddress.deviceId) != localDeviceId else {
Logger.info("Discarding self-sent message")
OWSLog.info("Discarding self-sent message")
throw SMKSecretSessionCipherError.selfSentMessage
}
do {
// validator.validate(content.getSenderCertificate(), timestamp);
let certificateValidator = certificateValidator as! SMKCertificateValidator
try certificateValidator.throwswrapped_validate(
senderCertificate: messageContent.senderCertificate,
validationTime: timestamp)
@ -293,7 +268,8 @@ fileprivate extension SMKMessageType {
paddedPayload: Data(paddedMessagePlaintext),
messageType: SMKMessageType(messageContent.messageType))
} catch {
throw SecretSessionKnownSenderError(messageContent: messageContent,
throw SecretSessionKnownSenderError(senderAddress: SMKAddress(senderAddress),
senderDeviceId: senderAddress.deviceId,
underlyingError: error)
}
}
@ -341,15 +317,6 @@ fileprivate extension SMKMessageType {
preKeyStore: preKeyStore,
signedPreKeyStore: signedPreKeyStore,
context: context)
case .senderKey:
plaintextData = try groupDecrypt(
messageContent.contents,
from: ProtocolAddress(from: sender),
store: senderKeyStore,
context: context)
case .plaintext:
let plaintextMessage = try PlaintextContent(bytes: messageContent.contents)
plaintextData = plaintextMessage.body
case let unknownType:
throw SMKError.assertionError(
description: "\(logTag) Not prepared to handle this message type: \(unknownType.rawValue)")
@ -357,28 +324,3 @@ fileprivate extension SMKMessageType {
return Data(plaintextData)
}
}
// MARK: - Internal for testing
extension SMKSecretSessionCipher {
// Only allow nil contexts for testing
func encryptMessage(
recipient: SMKAddress,
deviceId: Int32,
paddedPlaintext: Data,
contentHint: UnidentifiedSenderMessageContent.ContentHint = .default,
groupId: Data? = nil,
senderCertificate: SenderCertificate,
protocolContext: StoreContext? = nil
) throws -> Data {
try encryptMessage(
recipient: recipient,
deviceId: deviceId,
paddedPlaintext: paddedPlaintext,
contentHint: contentHint,
groupId: groupId,
senderCertificate: senderCertificate,
protocolContext: protocolContext ?? NullContext())
}
}

View File

@ -42,30 +42,10 @@ public class SMKUDAccessKey: NSObject {
self.keyData = Randomness.generateRandomBytes(Int32(SMKUDAccessKey.kUDAccessKeyLength))
}
private init(keyData: Data) {
self.keyData = keyData
}
/// Used to compose multiple Unidentified-Access-Keys for the multiRecipient endpoint
public static func ^(lhs: SMKUDAccessKey, rhs: SMKUDAccessKey) -> SMKUDAccessKey {
owsAssert(lhs.keyData.count == SMKUDAccessKey.kUDAccessKeyLength)
owsAssert(rhs.keyData.count == SMKUDAccessKey.kUDAccessKeyLength)
let xoredBytes = zip(lhs.keyData, rhs.keyData).map(^)
return .init(keyData: Data(xoredBytes))
}
// MARK:
override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? SMKUDAccessKey else { return false }
return self.keyData == other.keyData
}
// Unrestricted UD recipients should have a zeroed access key sent to the multi-recipient endpoint
// For a collection of mixed recipients, a zeroed key will have no effect composing keys with xor
// For a collection of only unrestricted UD recipients, the server expects a zero access key
public static var zeroedKey: SMKUDAccessKey {
.init(keyData: Data(repeating: 0, count: kUDAccessKeyLength))
}
}

View File

@ -3,7 +3,7 @@
//
import XCTest
@testable import SignalMetadataKit
import SignalMetadataKit
import SignalCoreKit
import Curve25519Kit
import SignalClient
@ -51,10 +51,11 @@ class SMKTest: XCTestCase {
let plaintext = Randomness.generateRandomBytes(200)
let paddedPlaintext = (plaintext as NSData).paddedMessageBody()
let senderCertificate = try! SenderCertificate(buildSenderCertificateProto(senderClient: aliceMockClient).serializedData())
let encryptedMessage = try! aliceToBobCipher.encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: paddedPlaintext,
senderCertificate: senderCertificate)
let encryptedMessage = try! aliceToBobCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: paddedPlaintext,
senderCertificate: senderCertificate,
protocolContext: nil)
let messageTimestamp = NSDate.ows_millisecondTimeStamp()

View File

@ -3,10 +3,9 @@
//
import XCTest
@testable import SignalMetadataKit
@testable import SignalClient
import SignalMetadataKit
import SignalClient
import Curve25519Kit
import SignalCoreKit
// https://github.com/signalapp/libsignal-metadata-java/blob/master/tests/src/test/java/org/signal/libsignal/metadata/SecretSessionCipherTest.java
// public class SecretSessionCipherTest extends TestCase {
@ -40,10 +39,11 @@ class SMKSecretSessionCipherTest: XCTestCase {
// 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(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate)
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
protocolContext: nil)
// SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1));
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
@ -95,14 +95,11 @@ class SMKSecretSessionCipherTest: XCTestCase {
// 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(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: aliceContentHint,
groupId: aliceGroupId,
senderCertificate: senderCertificate)
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
protocolContext: nil)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
@ -126,16 +123,6 @@ class SMKSecretSessionCipherTest: XCTestCase {
} catch let knownSenderError as SecretSessionKnownSenderError {
// Decryption is expected to fail.
XCTAssert(knownSenderError.underlyingError is SMKCertificateError )
XCTAssertEqual(knownSenderError.contentHint, aliceContentHint)
XCTAssertEqual(knownSenderError.groupId, aliceGroupId)
XCTAssertNoThrow(
try DecryptionErrorMessage(
originalMessageBytes: knownSenderError.unsealedContent,
type: knownSenderError.cipherType,
timestamp: 31335,
originalSenderDeviceId: knownSenderError.senderDeviceId
)
)
} catch {
XCTFail("Unexpected error: \(error)")
}
@ -169,15 +156,11 @@ class SMKSecretSessionCipherTest: XCTestCase {
// 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(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
contentHint: aliceContentHint,
groupId: aliceGroupId,
senderCertificate: senderCertificate)
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
protocolContext: nil)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
@ -201,16 +184,6 @@ class SMKSecretSessionCipherTest: XCTestCase {
} catch let knownSenderError as SecretSessionKnownSenderError {
// Decryption is expected to fail.
XCTAssert(knownSenderError.underlyingError is SMKCertificateError )
XCTAssertEqual(knownSenderError.contentHint, aliceContentHint)
XCTAssertEqual(knownSenderError.groupId, aliceGroupId)
XCTAssertNoThrow(
try DecryptionErrorMessage(
originalMessageBytes: knownSenderError.unsealedContent,
type: knownSenderError.cipherType,
timestamp: 31338,
originalSenderDeviceId: knownSenderError.senderDeviceId
)
)
} catch {
XCTFail("Unexpected error: \(error)")
}
@ -245,10 +218,11 @@ class SMKSecretSessionCipherTest: XCTestCase {
// 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(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate)
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
deviceId: bobMockClient.deviceId,
paddedPlaintext: alicePlaintext,
senderCertificate: senderCertificate,
protocolContext: nil)
// SecretSessionCipher bobCipher = new SecretSessionCipher(bobStore);
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
@ -278,148 +252,6 @@ class SMKSecretSessionCipherTest: XCTestCase {
}
}
func testGroupEncryptDecrypt_Success() {
// Setup: Initialize sessions and sender certificate
let aliceMockClient = MockClient(address: aliceAddress, deviceId: 1, registrationId: 1234)
let bobMockClient = MockClient(address: bobAddress, deviceId: 1, registrationId: 1235)
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
let trustRoot = IdentityKeyPair.generate()
let senderCertificate = createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.address,
senderDeviceId: UInt32(aliceMockClient.deviceId),
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 bobValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
let bobPlaintext = try! bobCipher.throwswrapped_decryptMessage(
certificateValidator: bobValidator,
cipherTextData: Data(singleRecipientCiphertext),
timestamp: 31335,
localE164: bobMockClient.recipientE164,
localUuid: bobMockClient.recipientUuid,
localDeviceId: bobMockClient.deviceId,
protocolContext: nil)
// Verify
XCTAssertEqual(String(data: bobPlaintext.paddedPayload, encoding: .utf8), "beltalowda")
XCTAssertEqual(bobPlaintext.senderAddress, aliceMockClient.address)
XCTAssertEqual(bobPlaintext.senderDeviceId, Int(aliceMockClient.deviceId))
XCTAssertEqual(bobPlaintext.messageType, .senderKey)
}
func testGroupEncryptDecrypt_Failure() {
// Setup: Initialize sessions and sender certificate
let aliceMockClient = MockClient(address: aliceAddress, deviceId: 1, registrationId: 1234)
let bobMockClient = MockClient(address: bobAddress, deviceId: 1, registrationId: 1235)
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
let trustRoot = IdentityKeyPair.generate()
let senderCertificate = createCertificateFor(
trustRoot: trustRoot,
senderAddress: aliceMockClient.address,
senderDeviceId: UInt32(aliceMockClient.deviceId),
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()
let _ = 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()
let bobValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
do {
_ = try bobCipher.throwswrapped_decryptMessage(
certificateValidator: bobValidator,
cipherTextData: Data(singleRecipientCiphertext),
timestamp: 31335,
localE164: bobMockClient.recipientE164,
localUuid: bobMockClient.recipientUuid,
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.senderAddress, aliceMockClient.address)
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.invalidState(_) = knownSenderError.underlyingError {
// Expected
} else {
XCTFail()
}
} catch {
XCTFail("Unexpected error: \(error)")
}
}
// MARK: - Utils
// private SenderCertificate createCertificateFor(ECKeyPair trustRoot, String sender, int deviceId, ECPublicKey identityKey, long expires)

View File

@ -32,9 +32,6 @@ class MockClient: NSObject {
}
let address: SMKAddress
var protocolAddress: ProtocolAddress {
try! ProtocolAddress(name: address.uuid!.uuidString, deviceId: UInt32(deviceId))
}
let deviceId: Int32
let registrationId: Int32
@ -45,7 +42,6 @@ class MockClient: NSObject {
let preKeyStore: InMemorySignalProtocolStore
let signedPreKeyStore: InMemorySignalProtocolStore
let identityStore: InMemorySignalProtocolStore
let senderKeyStore: InMemorySignalProtocolStore
init(address: SMKAddress, deviceId: Int32, registrationId: Int32) {
self.address = address
@ -54,21 +50,19 @@ class MockClient: NSObject {
self.identityKeyPair = IdentityKeyPair.generate()
let protocolStore = InMemorySignalProtocolStore(identity: identityKeyPair,
registrationId: UInt32(registrationId))
deviceId: UInt32(bitPattern: deviceId))
sessionStore = protocolStore
preKeyStore = protocolStore
signedPreKeyStore = protocolStore
identityStore = protocolStore
senderKeyStore = protocolStore
}
func createSecretSessionCipher() throws -> SMKSecretSessionCipher {
return try SMKSecretSessionCipher(sessionStore: sessionStore,
preKeyStore: preKeyStore,
signedPreKeyStore: signedPreKeyStore,
identityStore: identityStore,
senderKeyStore: senderKeyStore)
preKeyStore: preKeyStore,
signedPreKeyStore: signedPreKeyStore,
identityStore: identityStore)
}
func generateMockPreKey() -> PreKeyRecord {
@ -106,14 +100,14 @@ class MockClient: NSObject {
let bobSignedPreKey = bobMockClient.generateMockSignedPreKey()
// PreKeyBundle bobBundle = new PreKeyBundle(1, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey());
let bobBundle = try! PreKeyBundle(registrationId: UInt32(bitPattern: bobMockClient.registrationId),
deviceId: UInt32(bitPattern: bobMockClient.deviceId),
prekeyId: bobPreKey.id,
prekey: bobPreKey.publicKey,
signedPrekeyId: bobSignedPreKey.id,
signedPrekey: bobSignedPreKey.publicKey,
signedPrekeySignature: bobSignedPreKey.signature,
identity: bobIdentityKey.identityKey)
let bobBundle = try! SignalClient.PreKeyBundle(registrationId: UInt32(bitPattern: bobMockClient.registrationId),
deviceId: UInt32(bitPattern: bobMockClient.deviceId),
prekeyId: bobPreKey.id,
prekey: bobPreKey.publicKey,
signedPrekeyId: bobSignedPreKey.id,
signedPrekey: bobSignedPreKey.publicKey,
signedPrekeySignature: bobSignedPreKey.signature,
identity: bobIdentityKey.identityKey)
// SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, new SignalProtocolAddress("+14152222222", 1));
// aliceSessionBuilder.process(bobBundle);