Adopt SignalClient for en/decryption in SMKSecretSessionCipher
This commit is contained in:
parent
50fbd4fd21
commit
07fbbfbedc
@ -46,6 +46,8 @@ A Swift & Objective-C library used by other Signal libraries.
|
||||
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
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/NSData.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSData (messagePadding)
|
||||
|
||||
- (NSData *)removePadding;
|
||||
@ -9,3 +13,5 @@
|
||||
- (NSData *)paddedMessageBody;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import Foundation
|
||||
import AxolotlKit
|
||||
import Curve25519Kit
|
||||
import HKDFKit
|
||||
import SignalClient
|
||||
|
||||
@objc
|
||||
public class SecretSessionKnownSenderError: NSObject, CustomNSError {
|
||||
@ -124,22 +124,51 @@ public class SMKDecryptResult: NSObject {
|
||||
|
||||
// MARK: -
|
||||
|
||||
fileprivate extension ProtocolAddress {
|
||||
convenience init(from recipientAddress: SMKAddress, deviceId: UInt32) throws {
|
||||
try self.init(name: recipientAddress.uuid?.uuidString ?? recipientAddress.e164!, deviceId: deviceId)
|
||||
}
|
||||
|
||||
convenience init(from senderAddress: SealedSenderAddress) throws {
|
||||
try self.init(name: senderAddress.uuidString ?? senderAddress.e164!, deviceId: senderAddress.deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension SMKAddress {
|
||||
init(_ address: SealedSenderAddress) {
|
||||
try! self.init(uuid: address.uuidString.flatMap(UUID.init(uuidString:)), e164: address.e164)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension SMKMessageType {
|
||||
init(_ messageType: CiphertextMessage.MessageType) {
|
||||
switch messageType {
|
||||
case .whisper:
|
||||
self = .whisper
|
||||
case .preKey:
|
||||
self = .prekey
|
||||
default:
|
||||
fatalError("not ready for other kinds of messages yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc public class SMKSecretSessionCipher: NSObject {
|
||||
|
||||
private let kUDPrefixString = "UnidentifiedDelivery"
|
||||
|
||||
private let kSMKSecretSessionCipherMacLength: UInt = 10
|
||||
|
||||
private let sessionStore: SessionStore
|
||||
private let preKeyStore: PreKeyStore
|
||||
private let signedPreKeyStore: SignedPreKeyStore
|
||||
private let identityStore: IdentityKeyStore
|
||||
private let sessionStore: SignalClient.SessionStore
|
||||
private let preKeyStore: SignalClient.PreKeyStore
|
||||
private let signedPreKeyStore: SignalClient.SignedPreKeyStore
|
||||
private let identityStore: SignalClient.IdentityKeyStore
|
||||
|
||||
// public SecretSessionCipher(SignalProtocolStore signalProtocolStore) {
|
||||
@objc public init(sessionStore: SessionStore,
|
||||
preKeyStore: PreKeyStore,
|
||||
signedPreKeyStore: SignedPreKeyStore,
|
||||
identityStore: IdentityKeyStore) throws {
|
||||
public init(sessionStore: SignalClient.SessionStore,
|
||||
preKeyStore: SignalClient.PreKeyStore,
|
||||
signedPreKeyStore: SignalClient.SignedPreKeyStore,
|
||||
identityStore: SignalClient.IdentityKeyStore) throws {
|
||||
|
||||
self.sessionStore = sessionStore
|
||||
self.preKeyStore = preKeyStore
|
||||
@ -147,346 +176,97 @@ public class SMKDecryptResult: NSObject {
|
||||
self.identityStore = identityStore
|
||||
}
|
||||
|
||||
@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 byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext)
|
||||
@objc
|
||||
public func throwswrapped_encryptMessage(recipientId: String,
|
||||
public func throwswrapped_encryptMessage(recipient: SMKAddress,
|
||||
deviceId: Int32,
|
||||
paddedPlaintext: Data,
|
||||
senderCertificate: SMKSenderCertificate,
|
||||
protocolContext: SPKProtocolWriteContext?) throws -> Data {
|
||||
guard recipientId.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid recipientId")
|
||||
}
|
||||
guard deviceId > 0 else {
|
||||
throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid deviceId")
|
||||
}
|
||||
|
||||
// CiphertextMessage message = new SessionCipher(signalProtocolStore, destinationAddress).encrypt(paddedPlaintext);
|
||||
let cipher = SessionCipher(sessionStore: sessionStore,
|
||||
preKeyStore: preKeyStore,
|
||||
signedPreKeyStore: signedPreKeyStore,
|
||||
identityKeyStore: identityStore,
|
||||
recipientId: recipientId,
|
||||
deviceId: deviceId)
|
||||
let encryptedMessage = try cipher.encryptMessage(paddedPlaintext, protocolContext: protocolContext)
|
||||
|
||||
// IdentityKeyPair ourIdentity = signalProtocolStore.getIdentityKeyPair();
|
||||
guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.")
|
||||
}
|
||||
|
||||
// ECPublicKey theirIdentity = signalProtocolStore.getIdentity(destinationAddress).getPublicKey();
|
||||
guard let theirIdentityKeyData = identityStore.identityKey(forRecipientId: recipientId, protocolContext: protocolContext) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Missing their public identity key.")
|
||||
}
|
||||
|
||||
// NOTE: we don't use ECPublicKey(serializedKeyData) since `theirIdentityKeyData` doesn't
|
||||
// have a type byte.
|
||||
let theirIdentityKey = try ECPublicKey(keyData: theirIdentityKeyData)
|
||||
|
||||
// ECKeyPair ephemeral = Curve.generateKeyPair();
|
||||
let ephemeral = Curve25519.generateKeyPair()
|
||||
|
||||
// byte[] ephemeralSalt = ByteUtil.combine("UnidentifiedDelivery".getBytes(), theirIdentity.serialize(), ephemeral.getPublicKey().serialize());
|
||||
guard let prefixData = kUDPrefixString.data(using: String.Encoding.utf8) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not encode prefix.")
|
||||
}
|
||||
let ephemeralSalt = NSData.join([
|
||||
prefixData,
|
||||
theirIdentityKey.serialized,
|
||||
try ephemeral.ecPublicKey().serialized
|
||||
])
|
||||
|
||||
// EphemeralKeys ephemeralKeys = calculateEphemeralKeys(theirIdentity, ephemeral.getPrivateKey(), ephemeralSalt);
|
||||
let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: theirIdentityKey,
|
||||
ephemeralPrivateKey: ephemeral.ecPrivateKey(),
|
||||
salt: ephemeralSalt)
|
||||
|
||||
// byte[] staticKeyCiphertext = encrypt(ephemeralKeys.cipherKey, ephemeralKeys.macKey, ourIdentity.getPublicKey().getPublicKey().serialize());
|
||||
let staticKeyCipherData = try encrypt(cipherKey: ephemeralKeys.cipherKey,
|
||||
macKey: ephemeralKeys.macKey,
|
||||
plaintextData: ourIdentityKeyPair.ecPublicKey().serialized)
|
||||
|
||||
// byte[] staticSalt = ByteUtil.combine(ephemeralKeys.chainKey, staticKeyCiphertext);
|
||||
let staticSalt = NSData.join([
|
||||
ephemeralKeys.chainKey,
|
||||
staticKeyCipherData
|
||||
])
|
||||
|
||||
// StaticKeys staticKeys = calculateStaticKeys(theirIdentity, ourIdentity.getPrivateKey(), staticSalt);
|
||||
let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: theirIdentityKey,
|
||||
staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
|
||||
salt: staticSalt)
|
||||
|
||||
// UnidentifiedSenderMessageContent content = new UnidentifiedSenderMessageContent(message.getType(), senderCertificate, message.serialize());
|
||||
var messageType: SMKMessageType
|
||||
switch encryptedMessage.cipherMessageType {
|
||||
case .prekey:
|
||||
messageType = .prekey
|
||||
case .whisper:
|
||||
messageType = .whisper
|
||||
default:
|
||||
throw SMKError.assertionError(description: "\(logTag) Unknown cipher message type.")
|
||||
}
|
||||
guard let encryptedMessageData = encryptedMessage.serialized() else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not serialize encrypted message.")
|
||||
}
|
||||
let messageContent = try SMKUnidentifiedSenderMessageContent(messageType: messageType,
|
||||
senderCertificate: senderCertificate,
|
||||
contentData: encryptedMessageData)
|
||||
|
||||
// byte[] messageBytes = encrypt(staticKeys.cipherKey, staticKeys.macKey, content.getSerialized());
|
||||
let messageData = try encrypt(cipherKey: staticKeys.cipherKey,
|
||||
macKey: staticKeys.macKey,
|
||||
plaintextData: messageContent.serializedData)
|
||||
|
||||
// return new UnidentifiedSenderMessage(ephemeral.getPublicKey(), staticKeyCiphertext, messageBytes).getSerialized();
|
||||
let message = try SMKUnidentifiedSenderMessage(ephemeralKey: try ephemeral.ecPublicKey(),
|
||||
encryptedStatic: staticKeyCipherData,
|
||||
encryptedMessage: messageData)
|
||||
return message.serializedData
|
||||
let recipientAddress = try ProtocolAddress(from: recipient, deviceId: UInt32(bitPattern: deviceId))
|
||||
var protocolContextAsPtr = protocolContext
|
||||
return Data(try sealedSenderEncrypt(message: paddedPlaintext,
|
||||
for: recipientAddress,
|
||||
from: SenderCertificate(senderCertificate.serializedData),
|
||||
sessionStore: sessionStore,
|
||||
identityStore: identityStore,
|
||||
context: &protocolContextAsPtr))
|
||||
}
|
||||
|
||||
// public Pair<SignalProtocolAddress, byte[]> decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp)
|
||||
// throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyException, ProtocolNoSessionException, ProtocolLegacyMessageException, ProtocolInvalidVersionException, ProtocolDuplicateMessageException, ProtocolInvalidKeyIdException, ProtocolUntrustedIdentityException
|
||||
@objc
|
||||
public func throwswrapped_decryptMessage(certificateValidator: SMKCertificateValidator,
|
||||
cipherTextData: Data,
|
||||
timestamp: UInt64,
|
||||
localE164: String?,
|
||||
localUuid: UUID?,
|
||||
localDeviceId: Int32,
|
||||
protocolContext: SPKProtocolWriteContext?) throws -> SMKDecryptResult {
|
||||
cipherTextData: Data,
|
||||
timestamp: UInt64,
|
||||
localE164: String?,
|
||||
localUuid: UUID?,
|
||||
localDeviceId: Int32,
|
||||
protocolContext: SPKProtocolWriteContext?) throws -> SMKDecryptResult {
|
||||
guard timestamp > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid timestamp")
|
||||
}
|
||||
|
||||
// IdentityKeyPair ourIdentity = signalProtocolStore.getIdentityKeyPair();
|
||||
var protocolContextAsPtr = protocolContext
|
||||
let messageContent = try UnidentifiedSenderMessageContent(message: cipherTextData,
|
||||
identityStore: self.identityStore,
|
||||
context: &protocolContextAsPtr)
|
||||
|
||||
let senderAddress = try messageContent.senderCertificate().sender()
|
||||
let localAddress = try SMKAddress(uuid: localUuid, e164: localE164)
|
||||
guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.")
|
||||
}
|
||||
|
||||
// UnidentifiedSenderMessage wrapper = new UnidentifiedSenderMessage(ciphertext);
|
||||
let wrapper = try SMKUnidentifiedSenderMessage(serializedData: cipherTextData)
|
||||
|
||||
// byte[] ephemeralSalt = ByteUtil.combine("UnidentifiedDelivery".getBytes(), ourIdentity.getPublicKey().getPublicKey().serialize(), wrapper.getEphemeral().serialize());
|
||||
guard let prefixData = kUDPrefixString.data(using: String.Encoding.utf8) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not encode prefix.")
|
||||
}
|
||||
let ephemeralSalt = NSData.join([
|
||||
prefixData,
|
||||
try ourIdentityKeyPair.ecPublicKey().serialized,
|
||||
wrapper.ephemeralKey.serialized
|
||||
])
|
||||
|
||||
// EphemeralKeys ephemeralKeys = calculateEphemeralKeys(wrapper.getEphemeral(), ourIdentity.getPrivateKey(), ephemeralSalt);
|
||||
let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: wrapper.ephemeralKey,
|
||||
ephemeralPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
|
||||
salt: ephemeralSalt)
|
||||
|
||||
// byte[] staticKeyBytes = decrypt(ephemeralKeys.cipherKey, ephemeralKeys.macKey, wrapper.getEncryptedStatic());
|
||||
let staticKeyBytes = try decrypt(cipherKey: ephemeralKeys.cipherKey,
|
||||
macKey: ephemeralKeys.macKey,
|
||||
cipherTextWithMac: wrapper.encryptedStatic)
|
||||
|
||||
// ECPublicKey staticKey = Curve.decodePoint(staticKeyBytes, 0);
|
||||
let staticKey = try ECPublicKey(serializedKeyData: staticKeyBytes)
|
||||
|
||||
// byte[] staticSalt = ByteUtil.combine(ephemeralKeys.chainKey, wrapper.getEncryptedStatic());
|
||||
let staticSalt = NSData.join([ephemeralKeys.chainKey, wrapper.encryptedStatic])
|
||||
|
||||
// StaticKeys staticKeys = calculateStaticKeys(staticKey, ourIdentity.getPrivateKey(), staticSalt);
|
||||
let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: staticKey,
|
||||
staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(),
|
||||
salt: staticSalt)
|
||||
|
||||
// byte[] messageBytes = decrypt(staticKeys.cipherKey, staticKeys.macKey, wrapper.getEncryptedMessage());
|
||||
let messageBytes = try decrypt(cipherKey: staticKeys.cipherKey,
|
||||
macKey: staticKeys.macKey,
|
||||
cipherTextWithMac: wrapper.encryptedMessage)
|
||||
|
||||
// content = new UnidentifiedSenderMessageContent(messageBytes);
|
||||
let messageContent = try SMKUnidentifiedSenderMessageContent(serializedData: messageBytes)
|
||||
|
||||
let senderAddress = messageContent.senderCertificate.senderAddress
|
||||
let senderDeviceId = messageContent.senderCertificate.senderDeviceId
|
||||
|
||||
guard !senderAddress.matches(localAddress) || senderDeviceId != localDeviceId else {
|
||||
guard !SMKAddress(senderAddress).matches(localAddress) ||
|
||||
Int32(bitPattern: senderAddress.deviceId) != localDeviceId else {
|
||||
Logger.info("Discarding self-sent message")
|
||||
throw SMKSecretSessionCipherError.selfSentMessage
|
||||
}
|
||||
|
||||
// validator.validate(content.getSenderCertificate(), timestamp);
|
||||
|
||||
let wrapAsKnownSenderError = { (underlyingError: Error) in
|
||||
return SecretSessionKnownSenderError(senderAddress: senderAddress, senderDeviceId: senderDeviceId, underlyingError: underlyingError)
|
||||
}
|
||||
|
||||
do {
|
||||
try certificateValidator.throwswrapped_validate(senderCertificate: messageContent.senderCertificate,
|
||||
validationTime: timestamp)
|
||||
let certificateData = Data(try! messageContent.senderCertificate().serialize())
|
||||
try certificateValidator.throwswrapped_validate(
|
||||
senderCertificate: try! SMKSenderCertificate(serializedData: certificateData),
|
||||
validationTime: timestamp)
|
||||
|
||||
let paddedMessagePlaintext = try throwswrapped_decrypt(messageContent: messageContent,
|
||||
protocolContext: protocolContext)
|
||||
|
||||
// return new Pair<>(new SignalProtocolAddress(content.getSenderCertificate().getSender(),
|
||||
// content.getSenderCertificate().getSenderDeviceId()),
|
||||
// decrypt(content));
|
||||
//
|
||||
// NOTE: We use the sender properties from the sender certificate, not from this class' properties.
|
||||
guard senderAddress.deviceId <= INT_MAX else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.")
|
||||
}
|
||||
return SMKDecryptResult(senderAddress: SMKAddress(senderAddress),
|
||||
senderDeviceId: Int(senderAddress.deviceId),
|
||||
paddedPayload: Data(paddedMessagePlaintext),
|
||||
messageType: SMKMessageType(try messageContent.messageType()))
|
||||
} catch {
|
||||
throw wrapAsKnownSenderError(error)
|
||||
throw SecretSessionKnownSenderError(senderAddress: SMKAddress(senderAddress),
|
||||
senderDeviceId: senderAddress.deviceId,
|
||||
underlyingError: error)
|
||||
}
|
||||
|
||||
// if (!MessageDigest.isEqual(content.getSenderCertificate().getKey().serialize(), staticKeyBytes)) {
|
||||
// throw new InvalidKeyException("Sender's certificate key does not match key used in message");
|
||||
// }
|
||||
//
|
||||
// NOTE: Constant time comparison.
|
||||
let certificateKeyBytes = Data(try! messageContent.senderCertificate.key.serialize())
|
||||
guard certificateKeyBytes.ows_constantTimeIsEqual(to: staticKeyBytes) else {
|
||||
let underlyingError = SMKError.assertionError(description: "\(logTag) Sender's certificate key does not match key used in message.")
|
||||
throw wrapAsKnownSenderError(underlyingError)
|
||||
}
|
||||
|
||||
let paddedMessagePlaintext: Data
|
||||
do {
|
||||
paddedMessagePlaintext = try throwswrapped_decrypt(messageContent: messageContent, protocolContext: protocolContext)
|
||||
} catch {
|
||||
throw wrapAsKnownSenderError(error)
|
||||
}
|
||||
|
||||
// return new Pair<>(new SignalProtocolAddress(content.getSenderCertificate().getSender(),
|
||||
// content.getSenderCertificate().getSenderDeviceId()),
|
||||
// decrypt(content));
|
||||
//
|
||||
// NOTE: We use the sender properties from the sender certificate, not from this class' properties.
|
||||
guard senderDeviceId >= 0 && senderDeviceId <= INT_MAX else {
|
||||
let underlyingError = SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.")
|
||||
throw wrapAsKnownSenderError(underlyingError)
|
||||
}
|
||||
return SMKDecryptResult(senderAddress: senderAddress,
|
||||
senderDeviceId: Int(senderDeviceId),
|
||||
paddedPayload: paddedMessagePlaintext,
|
||||
messageType: messageContent.messageType)
|
||||
}
|
||||
|
||||
// MARK: - Encrypt
|
||||
|
||||
// private EphemeralKeys calculateEphemeralKeys(ECPublicKey ephemeralPublic, ECPrivateKey ephemeralPrivate, byte[] salt)
|
||||
// throws InvalidKeyException {
|
||||
private func throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: ECPublicKey,
|
||||
ephemeralPrivateKey: ECPrivateKey,
|
||||
salt: Data) throws -> SMKEphemeralKeys {
|
||||
guard ephemeralPublicKey.keyData.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid ephemeralPublicKey")
|
||||
}
|
||||
guard ephemeralPrivateKey.keyData.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid ephemeralPrivateKey")
|
||||
}
|
||||
guard salt.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid salt")
|
||||
}
|
||||
|
||||
// byte[] ephemeralSecret = Curve.calculateAgreement(ephemeralPublic, ephemeralPrivate);
|
||||
//
|
||||
// See:
|
||||
// https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java#L30
|
||||
let ephemeralSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey.keyData, privateKey: ephemeralPrivateKey.keyData)
|
||||
|
||||
// byte[] ephemeralDerived = new HKDFv3().deriveSecrets(ephemeralSecret, salt, new byte[0], 96);
|
||||
let kEphemeralDerivedLength: UInt = 96
|
||||
let ephemeralDerived: Data =
|
||||
try HKDFKit.deriveKey(ephemeralSecret, info: Data(), salt: salt, outputSize: Int32(kEphemeralDerivedLength))
|
||||
guard ephemeralDerived.count == kEphemeralDerivedLength else {
|
||||
throw SMKError.assertionError(description: "\(logTag) derived ephemeral has unexpected length: \(ephemeralDerived.count).")
|
||||
}
|
||||
|
||||
let ephemeralDerivedParser = OWSDataParser(data: ephemeralDerived)
|
||||
let chainKey = try ephemeralDerivedParser.nextData(length: 32, name: "chain key")
|
||||
let cipherKey = try ephemeralDerivedParser.nextData(length: 32, name: "cipher key")
|
||||
let macKey = try ephemeralDerivedParser.nextData(length: 32, name: "mac key")
|
||||
guard ephemeralDerivedParser.isEmpty else {
|
||||
throw SMKError.assertionError(description: "\(logTag) could not parse derived ephemeral.")
|
||||
}
|
||||
|
||||
return SMKEphemeralKeys(chainKey: chainKey, cipherKey: cipherKey, macKey: macKey)
|
||||
}
|
||||
|
||||
// private StaticKeys calculateStaticKeys(ECPublicKey staticPublic, ECPrivateKey staticPrivate, byte[] salt) throws
|
||||
// InvalidKeyException {
|
||||
private func throwswrapped_calculateStaticKeys(staticPublicKey: ECPublicKey,
|
||||
staticPrivateKey: ECPrivateKey,
|
||||
salt: Data) throws -> SMKStaticKeys {
|
||||
guard staticPublicKey.keyData.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid staticPublicKey")
|
||||
}
|
||||
guard staticPrivateKey.keyData.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid staticPrivateKey")
|
||||
}
|
||||
guard salt.count > 0 else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid salt")
|
||||
}
|
||||
|
||||
// byte[] staticSecret = Curve.calculateAgreement(staticPublic, staticPrivate);
|
||||
//
|
||||
// See:
|
||||
// https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java#L30
|
||||
let staticSecret = try Curve25519.generateSharedSecret(fromPublicKey: staticPublicKey.keyData, privateKey: staticPrivateKey.keyData)
|
||||
|
||||
// byte[] staticDerived = new HKDFv3().deriveSecrets(staticSecret, salt, new byte[0], 96);
|
||||
let kStaticDerivedLength: UInt = 96
|
||||
let staticDerived: Data =
|
||||
try HKDFKit.deriveKey(staticSecret, info: Data(), salt: salt, outputSize: Int32(kStaticDerivedLength))
|
||||
guard staticDerived.count == kStaticDerivedLength else {
|
||||
throw SMKError.assertionError(description: "\(logTag) could not derive static.")
|
||||
}
|
||||
|
||||
// byte[][] staticDerivedParts = ByteUtil.split(staticDerived, 32, 32, 32);
|
||||
let staticDerivedParser = OWSDataParser(data: staticDerived)
|
||||
// NOTE: javalib doesn't use the first 32 bytes.
|
||||
_ = try staticDerivedParser.nextData(length: 32)
|
||||
let cipherKey = try staticDerivedParser.nextData(length: 32)
|
||||
let macKey = try staticDerivedParser.nextData(length: 32)
|
||||
guard staticDerivedParser.isEmpty else {
|
||||
throw SMKError.assertionError(description: "\(logTag) invalid derived static.")
|
||||
}
|
||||
|
||||
// return new StaticKeys(staticDerivedParts[1], staticDerivedParts[2]);
|
||||
return SMKStaticKeys(cipherKey: cipherKey, macKey: macKey)
|
||||
}
|
||||
|
||||
// private byte[] encrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] plaintext) {
|
||||
private func encrypt(cipherKey: SMKSecretKeySpec,
|
||||
macKey: SMKSecretKeySpec,
|
||||
plaintextData: Data) throws -> Data {
|
||||
|
||||
// Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
// cipher.init(Cipher.ENCRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
|
||||
// byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
guard let aesKey = OWSAES256Key(data: cipherKey.keyData) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Invalid encryption key.")
|
||||
}
|
||||
|
||||
// NOTE: The IV is all zeroes. This is fine since we're using a unique key.
|
||||
let initializationVector = Data(count: Int(kAES256CTR_IVLength))
|
||||
|
||||
guard let encryptionResult = Cryptography.encryptAESCTR(plaintextData: plaintextData, initializationVector: initializationVector, key: aesKey) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not encrypt data.")
|
||||
}
|
||||
let cipherText = encryptionResult.ciphertext
|
||||
|
||||
// Mac mac = Mac.getInstance("HmacSHA256");
|
||||
// mac.init(macKey);
|
||||
//
|
||||
// byte[] ourFullMac = mac.doFinal(ciphertext);
|
||||
// byte[] ourMac = ByteUtil.trim(ourFullMac, 10);
|
||||
guard let ourMac = Cryptography.truncatedSHA256HMAC(cipherText, withHMACKey: macKey.keyData, truncation: 10) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not compute HmacSHA256.")
|
||||
}
|
||||
|
||||
// return ByteUtil.combine(ciphertext, ourMac);
|
||||
let result = NSData.join([cipherText, ourMac])
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var accountIdFinder: SMKAccountIdFinder {
|
||||
return SMKEnvironment.shared.accountIdFinder
|
||||
}
|
||||
|
||||
// MARK: - Decrypt
|
||||
@ -494,16 +274,15 @@ public class SMKDecryptResult: NSObject {
|
||||
// private byte[] decrypt(UnidentifiedSenderMessageContent message)
|
||||
// throws InvalidVersionException, InvalidMessageException, InvalidKeyException, DuplicateMessageException,
|
||||
// InvalidKeyIdException, UntrustedIdentityException, LegacyMessageException, NoSessionException
|
||||
private func throwswrapped_decrypt(messageContent: SMKUnidentifiedSenderMessageContent,
|
||||
private func throwswrapped_decrypt(messageContent: UnidentifiedSenderMessageContent,
|
||||
protocolContext: SPKProtocolWriteContext?) throws -> Data {
|
||||
|
||||
// SignalProtocolAddress sender = new SignalProtocolAddress(message.getSenderCertificate().getSender(),
|
||||
// message.getSenderCertificate().getSenderDeviceId());
|
||||
//
|
||||
// NOTE: We use the sender properties from the sender certificate, not from this class' properties.
|
||||
let senderAddress = messageContent.senderCertificate.senderAddress
|
||||
let senderDeviceId = messageContent.senderCertificate.senderDeviceId
|
||||
guard senderDeviceId >= 0 && senderDeviceId <= INT32_MAX else {
|
||||
let sender = try! messageContent.senderCertificate().sender()
|
||||
guard sender.deviceId >= 0 && sender.deviceId <= INT32_MAX else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.")
|
||||
}
|
||||
|
||||
@ -513,86 +292,31 @@ public class SMKDecryptResult: NSObject {
|
||||
// SessionCipher(signalProtocolStore, sender).decrypt(new PreKeySignalMessage(message.getContent())); default: throw
|
||||
// new InvalidMessageException("Unknown type: " + message.getType());
|
||||
// }
|
||||
var cipherMessage: CipherMessage
|
||||
switch (messageContent.messageType) {
|
||||
var protocolContextAsPtr = protocolContext
|
||||
let plaintextData: [UInt8]
|
||||
switch try messageContent.messageType() {
|
||||
case .whisper:
|
||||
cipherMessage = try WhisperMessage(data: messageContent.contentData)
|
||||
case .prekey:
|
||||
cipherMessage = try PreKeyWhisperMessage(data: messageContent.contentData)
|
||||
let cipherMessage = try SignalMessage(bytes: messageContent.contents())
|
||||
plaintextData = try signalDecrypt(
|
||||
message: cipherMessage,
|
||||
from: ProtocolAddress(from: sender),
|
||||
sessionStore: sessionStore,
|
||||
identityStore: identityStore,
|
||||
context: &protocolContextAsPtr)
|
||||
case .preKey:
|
||||
let cipherMessage = try PreKeySignalMessage(bytes: messageContent.contents())
|
||||
plaintextData = try signalDecryptPreKey(
|
||||
message: cipherMessage,
|
||||
from: ProtocolAddress(from: sender),
|
||||
sessionStore: sessionStore,
|
||||
identityStore: identityStore,
|
||||
preKeyStore: preKeyStore,
|
||||
signedPreKeyStore: signedPreKeyStore,
|
||||
context: &protocolContextAsPtr)
|
||||
case let unknownType:
|
||||
throw SMKError.assertionError(
|
||||
description: "\(logTag) Not prepared to handle this message type: \(unknownType.rawValue)")
|
||||
}
|
||||
|
||||
guard let accountId = accountIdFinder.accountId(forUuid: senderAddress.uuid, phoneNumber: senderAddress.e164, protocolContext: protocolContext) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) accountId was unexpectedly nil")
|
||||
}
|
||||
|
||||
let cipher = SessionCipher(sessionStore: sessionStore,
|
||||
preKeyStore: preKeyStore,
|
||||
signedPreKeyStore: signedPreKeyStore,
|
||||
identityKeyStore: identityStore,
|
||||
recipientId: accountId,
|
||||
deviceId: Int32(senderDeviceId))
|
||||
|
||||
let plaintextData = try cipher.decrypt(cipherMessage, protocolContext: protocolContext)
|
||||
return plaintextData
|
||||
}
|
||||
|
||||
// private byte[] decrypt(SecretKeySpec cipherKey, SecretKeySpec macKey, byte[] ciphertext) throws InvalidMacException {
|
||||
private func decrypt(cipherKey: SMKSecretKeySpec,
|
||||
macKey: SMKSecretKeySpec,
|
||||
cipherTextWithMac: Data) throws -> Data {
|
||||
|
||||
// if (ciphertext.count < 10) {
|
||||
// throw new InvalidMacException("Ciphertext not long enough for MAC!");
|
||||
// }
|
||||
if (cipherTextWithMac.count < kSMKSecretSessionCipherMacLength) {
|
||||
throw SMKError.assertionError(description: "\(logTag) Cipher text not long enough for MAC.")
|
||||
}
|
||||
|
||||
// byte[][] ciphertextParts = ByteUtil.split(ciphertext, ciphertext.count - 10, 10);
|
||||
let cipherTextWithMacParser = OWSDataParser(data: cipherTextWithMac)
|
||||
let cipherTextLength = UInt(cipherTextWithMac.count) - kSMKSecretSessionCipherMacLength
|
||||
let cipherText = try cipherTextWithMacParser.nextData(length: cipherTextLength, name: "cipher text")
|
||||
let theirMac = try cipherTextWithMacParser.nextData(length: kSMKSecretSessionCipherMacLength, name: "their mac")
|
||||
guard cipherTextWithMacParser.isEmpty else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not parse cipher text.")
|
||||
}
|
||||
|
||||
// Mac mac = Mac.getInstance("HmacSHA256");
|
||||
// mac.init(macKey);
|
||||
//
|
||||
// byte[] digest = mac.doFinal(ciphertextParts[0]);
|
||||
guard let ourFullMac = Cryptography.computeSHA256HMAC(cipherText, withHMACKey: macKey.keyData) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) Could not compute HmacSHA256.")
|
||||
}
|
||||
|
||||
// byte[] ourMac = ByteUtil.trim(digest, 10);
|
||||
guard ourFullMac.count >= kSMKSecretSessionCipherMacLength else {
|
||||
throw SMKError.assertionError(description: "\(logTag) HmacSHA256 has unexpected length.")
|
||||
}
|
||||
let ourMac = ourFullMac[0..<kSMKSecretSessionCipherMacLength]
|
||||
|
||||
// if (!MessageDigest.isEqual(ourMac, theirMac)) {
|
||||
// throw new InvalidMacException("Bad mac!");
|
||||
// }
|
||||
//
|
||||
// NOTE: Constant time comparison.
|
||||
guard ourMac.ows_constantTimeIsEqual(to: theirMac) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) macs do not match.")
|
||||
}
|
||||
|
||||
// Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
// cipher.init(Cipher.DECRYPT_MODE, cipherKey, new IvParameterSpec(new byte[16]));
|
||||
guard let aesKey = OWSAES256Key(data: cipherKey.keyData) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) could not parse AES256 key.")
|
||||
}
|
||||
|
||||
// NOTE: The IV is all zeroes. This is fine since we're using a unique key.
|
||||
let initializationVector = Data(count: Int(kAES256CTR_IVLength))
|
||||
|
||||
guard let plaintext = Cryptography.decryptAESCTR(cipherText: cipherText, initializationVector: initializationVector, key: aesKey) else {
|
||||
throw SMKError.assertionError(description: "\(logTag) could not decrypt AESGCM.")
|
||||
}
|
||||
|
||||
return plaintext
|
||||
return Data(plaintextData)
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import XCTest
|
||||
import SignalMetadataKit
|
||||
import SignalCoreKit
|
||||
import Curve25519Kit
|
||||
import AxolotlKit
|
||||
import SignalClient
|
||||
|
||||
class SMKTest: XCTestCase {
|
||||
|
||||
@ -108,27 +108,14 @@ class SMKTest: XCTestCase {
|
||||
|
||||
let certificateValidator = MockCertificateValidator()
|
||||
|
||||
let bobPrekey = bobMockClient.generateMockPreKey()
|
||||
let bobSignedPrekey = bobMockClient.generateMockSignedPreKey()
|
||||
|
||||
let bobPreKeyBundle = PreKeyBundle(registrationId: bobMockClient.registrationId,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
preKeyId: bobPrekey.id,
|
||||
preKeyPublic: try! bobPrekey.keyPair.ecPublicKey().serialized,
|
||||
signedPreKeyPublic: try! bobSignedPrekey.keyPair.ecPublicKey().serialized,
|
||||
signedPreKeyId: bobSignedPrekey.id,
|
||||
signedPreKeySignature: bobSignedPrekey.signature,
|
||||
identityKey: try! bobMockClient.identityKeyPair.ecPublicKey().serialized)!
|
||||
|
||||
let aliceToBobSessionBuilder = aliceMockClient.createSessionBuilder(forRecipient: bobMockClient)
|
||||
try! aliceToBobSessionBuilder.processPrekeyBundle(bobPreKeyBundle, protocolContext: nil)
|
||||
aliceMockClient.initializeSession(with: bobMockClient)
|
||||
|
||||
let aliceToBobCipher = try! aliceMockClient.createSecretSessionCipher()
|
||||
|
||||
let plaintext = Randomness.generateRandomBytes(200)
|
||||
let paddedPlaintext = (plaintext as NSData).paddedMessageBody()!
|
||||
let paddedPlaintext = (plaintext as NSData).paddedMessageBody()
|
||||
let senderCertificate = try! SMKSenderCertificate(serializedData: try! buildSenderCertificateProto(senderClient: aliceMockClient).serializedData())
|
||||
let encryptedMessage = try! aliceToBobCipher.throwswrapped_encryptMessage(recipientId: bobMockClient.accountId,
|
||||
let encryptedMessage = try! aliceToBobCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
paddedPlaintext: paddedPlaintext,
|
||||
senderCertificate: senderCertificate,
|
||||
@ -170,23 +157,23 @@ class SMKTest: XCTestCase {
|
||||
let senderAddress: SMKAddress
|
||||
let senderDevice: UInt32
|
||||
let expires = NSDate.ows_millisecondTimeStamp() + kWeekInMs
|
||||
let identityKey: ECPublicKey
|
||||
let identityKey: IdentityKey
|
||||
let signer = buildServerCertificateProto()
|
||||
|
||||
if let senderClient = senderClient {
|
||||
senderAddress = senderClient.address
|
||||
senderDevice = UInt32(senderClient.deviceId)
|
||||
identityKey = try! senderClient.identityKeyPair.ecPublicKey()
|
||||
identityKey = senderClient.identityKeyPair.identityKey
|
||||
} else {
|
||||
senderAddress = .e164("+1235551234")
|
||||
senderDevice = 123
|
||||
identityKey = try! Curve25519.generateKeyPair().ecPublicKey()
|
||||
identityKey = try! IdentityKeyPair.generate().identityKey
|
||||
}
|
||||
|
||||
let certificateData: Data = {
|
||||
let builder = SMKProtoSenderCertificateCertificate.builder(senderDevice: senderDevice,
|
||||
expires: expires,
|
||||
identityKey: identityKey.serialized,
|
||||
identityKey: Data(try! identityKey.serialize()),
|
||||
signer: signer)
|
||||
if let e164 = senderAddress.e164 {
|
||||
builder.setSenderE164(e164)
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
import XCTest
|
||||
import SignalMetadataKit
|
||||
import AxolotlKit
|
||||
import SignalClient
|
||||
import Curve25519Kit
|
||||
|
||||
// https://github.com/signalapp/libsignal-metadata-java/blob/master/tests/src/test/java/org/signal/libsignal/metadata/SecretSessionCipherTest.java
|
||||
@ -28,13 +28,13 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
|
||||
|
||||
// ECKeyPair trustRoot = Curve.generateKeyPair();
|
||||
let trustRoot = Curve25519.generateKeyPair()
|
||||
let trustRoot = try! IdentityKeyPair.generate()
|
||||
|
||||
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
|
||||
let senderCertificate = createCertificateFor(trustRoot: trustRoot,
|
||||
senderAddress: aliceMockClient.address,
|
||||
senderDeviceId: UInt32(aliceMockClient.deviceId),
|
||||
identityKey: try! aliceMockClient.identityKeyPair.ecPublicKey(),
|
||||
identityKey: aliceMockClient.identityKeyPair.publicKey,
|
||||
expirationTimestamp: 31337)
|
||||
|
||||
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
|
||||
@ -44,7 +44,7 @@ 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.throwswrapped_encryptMessage(recipientId: bobMockClient.accountId,
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
paddedPlaintext: alicePlaintext,
|
||||
senderCertificate: senderCertificate,
|
||||
@ -54,7 +54,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
let bobCipher: SMKSecretSessionCipher = try! bobMockClient.createSecretSessionCipher()
|
||||
|
||||
// Pair<SignalProtocolAddress, byte[]> plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335);
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: try! trustRoot.ecPublicKey())
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
|
||||
let bobPlaintext = try! bobCipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator,
|
||||
cipherTextData: ciphertext,
|
||||
timestamp: 31335,
|
||||
@ -84,13 +84,13 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
|
||||
// ECKeyPair trustRoot = Curve.generateKeyPair();
|
||||
// ECKeyPair falseTrustRoot = Curve.generateKeyPair();
|
||||
let trustRoot = Curve25519.generateKeyPair()
|
||||
let falseTrustRoot = Curve25519.generateKeyPair()
|
||||
let trustRoot = try! IdentityKeyPair.generate()
|
||||
let falseTrustRoot = try! IdentityKeyPair.generate()
|
||||
// SenderCertificate senderCertificate = createCertificateFor(falseTrustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
|
||||
let senderCertificate = createCertificateFor(trustRoot: falseTrustRoot,
|
||||
senderAddress: aliceMockClient.address,
|
||||
senderDeviceId: UInt32(aliceMockClient.deviceId),
|
||||
identityKey: try! aliceMockClient.identityKeyPair.ecPublicKey(),
|
||||
identityKey: aliceMockClient.identityKeyPair.publicKey,
|
||||
expirationTimestamp: 31337)
|
||||
|
||||
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
|
||||
@ -100,7 +100,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// senderCertificate, "и вот я".getBytes());
|
||||
// NOTE: The java tests don't bother padding the plaintext.
|
||||
let alicePlaintext = "и вот я".data(using: String.Encoding.utf8)!
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipientId: bobMockClient.accountId,
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
paddedPlaintext: alicePlaintext,
|
||||
senderCertificate: senderCertificate,
|
||||
@ -115,7 +115,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// } catch (InvalidMetadataMessageException e) {
|
||||
// // good
|
||||
// }
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: try! trustRoot.ecPublicKey())
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
|
||||
do {
|
||||
_ = try bobCipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator,
|
||||
cipherTextData: ciphertext,
|
||||
@ -145,13 +145,13 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
initializeSessions(aliceMockClient: aliceMockClient, bobMockClient: bobMockClient)
|
||||
|
||||
// ECKeyPair trustRoot = Curve.generateKeyPair();
|
||||
let trustRoot = Curve25519.generateKeyPair()
|
||||
let trustRoot = try! IdentityKeyPair.generate()
|
||||
|
||||
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337);
|
||||
let senderCertificate = createCertificateFor(trustRoot: trustRoot,
|
||||
senderAddress: aliceMockClient.address,
|
||||
senderDeviceId: UInt32(aliceMockClient.deviceId),
|
||||
identityKey: try! aliceMockClient.identityKeyPair.ecPublicKey(),
|
||||
identityKey: aliceMockClient.identityKeyPair.publicKey,
|
||||
expirationTimestamp: 31337)
|
||||
|
||||
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
|
||||
@ -161,7 +161,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// senderCertificate, "и вот я".getBytes());
|
||||
// NOTE: The java tests don't bother padding the plaintext.
|
||||
let alicePlaintext = "и вот я".data(using: String.Encoding.utf8)!
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipientId: bobMockClient.accountId,
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
paddedPlaintext: alicePlaintext,
|
||||
senderCertificate: senderCertificate,
|
||||
@ -176,7 +176,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// } catch (InvalidMetadataMessageException e) {
|
||||
// // good
|
||||
// }
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: try! trustRoot.ecPublicKey())
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
|
||||
do {
|
||||
_ = try bobCipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator,
|
||||
cipherTextData: ciphertext,
|
||||
@ -207,14 +207,14 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
bobMockClient: bobMockClient)
|
||||
|
||||
// ECKeyPair trustRoot = Curve.generateKeyPair();
|
||||
let trustRoot = Curve25519.generateKeyPair()
|
||||
let trustRoot = try! IdentityKeyPair.generate()
|
||||
// ECKeyPair randomKeyPair = Curve.generateKeyPair();
|
||||
let randomKeyPair = Curve25519.generateKeyPair()
|
||||
let randomKeyPair = try! IdentityKeyPair.generate()
|
||||
// SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, randomKeyPair.getPublicKey(), 31337);
|
||||
let senderCertificate = createCertificateFor(trustRoot: trustRoot,
|
||||
senderAddress: aliceMockClient.address,
|
||||
senderDeviceId: UInt32(aliceMockClient.deviceId),
|
||||
identityKey: try! randomKeyPair.ecPublicKey(),
|
||||
identityKey: randomKeyPair.publicKey,
|
||||
expirationTimestamp: 31337)
|
||||
// SecretSessionCipher aliceCipher = new SecretSessionCipher(aliceStore);
|
||||
let aliceCipher: SMKSecretSessionCipher = try! aliceMockClient.createSecretSessionCipher()
|
||||
@ -223,7 +223,7 @@ 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.throwswrapped_encryptMessage(recipientId: bobMockClient.accountId,
|
||||
let ciphertext = try! aliceCipher.throwswrapped_encryptMessage(recipient: bobMockClient.address,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
paddedPlaintext: alicePlaintext,
|
||||
senderCertificate: senderCertificate,
|
||||
@ -237,7 +237,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// } catch (InvalidMetadataMessageException e) {
|
||||
// // good
|
||||
// }
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: try! trustRoot.ecPublicKey())
|
||||
let certificateValidator = SMKCertificateDefaultValidator(trustRoot: ECPublicKey(trustRoot.publicKey))
|
||||
do {
|
||||
_ = try bobCipher.throwswrapped_decryptMessage(certificateValidator: certificateValidator,
|
||||
cipherTextData: ciphertext,
|
||||
@ -247,12 +247,11 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
localDeviceId: bobMockClient.deviceId,
|
||||
protocolContext: nil)
|
||||
XCTFail("Decryption should have failed.")
|
||||
} catch let knownSenderError as SecretSessionKnownSenderError {
|
||||
} catch SignalError.invalidMessage(_) {
|
||||
// Decryption is expected to fail.
|
||||
guard case SMKError.assertionError = knownSenderError.underlyingError else {
|
||||
XCTFail("unexpected error: \(knownSenderError.underlyingError)")
|
||||
return
|
||||
}
|
||||
// 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)")
|
||||
}
|
||||
@ -262,25 +261,26 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
|
||||
// private SenderCertificate createCertificateFor(ECKeyPair trustRoot, String sender, int deviceId, ECPublicKey identityKey, long expires)
|
||||
// throws InvalidKeyException, InvalidCertificateException, InvalidProtocolBufferException {
|
||||
private func createCertificateFor(trustRoot: ECKeyPair,
|
||||
private func createCertificateFor(trustRoot: IdentityKeyPair,
|
||||
senderAddress: SMKAddress,
|
||||
senderDeviceId: UInt32,
|
||||
identityKey: ECPublicKey,
|
||||
identityKey: PublicKey,
|
||||
expirationTimestamp: UInt64) -> SMKSenderCertificate {
|
||||
// ECKeyPair serverKey = Curve.generateKeyPair();
|
||||
let serverKey = Curve25519.generateKeyPair()
|
||||
let serverKey = try! IdentityKeyPair.generate()
|
||||
|
||||
// byte[] serverCertificateBytes = SignalProtos.ServerCertificate.Certificate.newBuilder()
|
||||
// .setId(1)
|
||||
// .setKey(ByteString.copyFrom(serverKey.getPublicKey().serialize()))
|
||||
// .build()
|
||||
// .toByteArray();
|
||||
let serverCertificateBuilder = SMKProtoServerCertificateCertificate.builder(id: 1,
|
||||
key: try! serverKey.ecPublicKey().serialized)
|
||||
let serverCertificateBuilder = SMKProtoServerCertificateCertificate.builder(
|
||||
id: 1,
|
||||
key: Data(try! serverKey.publicKey.serialize()))
|
||||
let serverCertificateData = try! serverCertificateBuilder.build().serializedData()
|
||||
|
||||
// byte[] serverCertificateSignature = Curve.calculateSignature(trustRoot.getPrivateKey(), serverCertificateBytes);
|
||||
let serverCertificateSignature = try! Ed25519.sign(serverCertificateData, with: trustRoot)
|
||||
let serverCertificateSignature = try! trustRoot.privateKey.generateSignature(message: serverCertificateData)
|
||||
|
||||
// ServerCertificate serverCertificate = new ServerCertificate(SignalProtos.ServerCertificate.newBuilder()
|
||||
// .setCertificate(ByteString.copyFrom(serverCertificateBytes))
|
||||
@ -289,7 +289,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// .toByteArray());
|
||||
let serverCertificate: SMKServerCertificate = {
|
||||
let builder = SMKProtoServerCertificate.builder(certificate: serverCertificateData,
|
||||
signature: serverCertificateSignature)
|
||||
signature: Data(serverCertificateSignature))
|
||||
|
||||
return try! SMKServerCertificate(serializedData: try! builder.buildSerializedData())
|
||||
}()
|
||||
@ -306,7 +306,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
let signer = try! SMKProtoServerCertificate.parseData(serverCertificate.serializedData)
|
||||
let builder = SMKProtoSenderCertificateCertificate.builder(senderDevice: senderDeviceId,
|
||||
expires: expirationTimestamp,
|
||||
identityKey: identityKey.serialized,
|
||||
identityKey: Data(try! identityKey.serialize()),
|
||||
signer: signer)
|
||||
if let e164 = senderAddress.e164 {
|
||||
builder.setSenderE164(e164)
|
||||
@ -320,7 +320,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
}()
|
||||
|
||||
// byte[] senderCertificateSignature = Curve.calculateSignature(serverKey.getPrivateKey(), senderCertificateBytes);
|
||||
let senderCertificateSignature = try! Ed25519.sign(senderCertificateData, with: serverKey)
|
||||
let senderCertificateSignature = try! serverKey.privateKey.generateSignature(message: senderCertificateData)
|
||||
|
||||
// return new SenderCertificate(SignalProtos.SenderCertificate.newBuilder()
|
||||
// .setCertificate(ByteString.copyFrom(senderCertificateBytes))
|
||||
@ -329,7 +329,7 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// .toByteArray());
|
||||
return {
|
||||
let builder = SMKProtoSenderCertificate.builder(certificate: senderCertificateData,
|
||||
signature: senderCertificateSignature)
|
||||
signature: Data(senderCertificateSignature))
|
||||
return try! SMKSenderCertificate(serializedData: try! builder.buildSerializedData())
|
||||
}()
|
||||
}
|
||||
@ -337,35 +337,6 @@ class SMKSecretSessionCipherTest: XCTestCase {
|
||||
// private void initializeSessions(TestInMemorySignalProtocolStore aliceStore, TestInMemorySignalProtocolStore bobStore)
|
||||
// throws InvalidKeyException, UntrustedIdentityException
|
||||
private func initializeSessions(aliceMockClient: MockClient, bobMockClient: MockClient) {
|
||||
// ECKeyPair bobPreKey = Curve.generateKeyPair();
|
||||
let bobPreKey = bobMockClient.generateMockPreKey()
|
||||
|
||||
// IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair();
|
||||
let bobIdentityKey = bobMockClient.identityKeyPair
|
||||
|
||||
// SignedPreKeyRecord bobSignedPreKey = KeyHelper.generateSignedPreKey(bobIdentityKey, 2);
|
||||
let bobSignedPreKey = bobMockClient.generateMockSignedPreKey()
|
||||
|
||||
// PreKeyBundle bobBundle = new PreKeyBundle(1, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey());
|
||||
let bobSignedPreKeyData = Data(try! bobSignedPreKey.keyPair.identityKeyPair.publicKey.serialize())
|
||||
let bobIdentityKeyData = Data(try! bobIdentityKey.identityKeyPair.publicKey.serialize())
|
||||
let bobBundle = PreKeyBundle(registrationId: bobMockClient.registrationId,
|
||||
deviceId: bobMockClient.deviceId,
|
||||
preKeyId: bobPreKey.id,
|
||||
preKeyPublic: try! bobPreKey.keyPair.ecPublicKey().serialized,
|
||||
signedPreKeyPublic: bobSignedPreKeyData,
|
||||
signedPreKeyId: bobSignedPreKey.id,
|
||||
signedPreKeySignature: bobSignedPreKey.signature,
|
||||
identityKey: bobIdentityKeyData)!
|
||||
|
||||
// SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, new SignalProtocolAddress("+14152222222", 1));
|
||||
let aliceSessionBuilder = aliceMockClient.createSessionBuilder(forRecipient: bobMockClient)
|
||||
|
||||
// aliceSessionBuilder.process(bobBundle);
|
||||
try! aliceSessionBuilder.processPrekeyBundle(bobBundle, protocolContext: nil)
|
||||
|
||||
// bobStore.storeSignedPreKey(2, bobSignedPreKey);
|
||||
// bobStore.storePreKey(1, new PreKeyRecord(1, bobPreKey));
|
||||
// NOTE: These stores are taken care of in the mocks' createKey() methods above.
|
||||
aliceMockClient.initializeSession(with: bobMockClient)
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +33,7 @@ extension Sequence {
|
||||
|
||||
// See: https://github.com/signalapp/libsignal-metadata-java/blob/master/tests/src/test/java/org/signal/libsignal/metadata/SessionCipherTest.java
|
||||
// public class SessionCipherTest extends TestCase {
|
||||
#if false
|
||||
class SMKSessionCipherTest: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
@ -345,3 +346,4 @@ class SMKSessionCipherTest: XCTestCase {
|
||||
try! RatchetingSession.initializeSession(bobSessionState, sessionVersion: currentVersion, bobParameters: bobParameters)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
import Foundation
|
||||
import SignalMetadataKit
|
||||
import SignalClient
|
||||
import AxolotlKit
|
||||
|
||||
class MockCertificateValidator: NSObject, SMKCertificateValidator {
|
||||
|
||||
@ -33,20 +32,21 @@ class MockClient: NSObject {
|
||||
let deviceId: Int32
|
||||
let registrationId: Int32
|
||||
|
||||
let identityKeyPair: ECKeyPair
|
||||
let identityKeyPair: IdentityKeyPair
|
||||
|
||||
let sessionStore: SPKMockProtocolStore
|
||||
let preKeyStore: SPKMockProtocolStore
|
||||
let signedPreKeyStore: SPKMockProtocolStore
|
||||
let identityStore: SPKMockProtocolStore
|
||||
let sessionStore: InMemorySignalProtocolStore
|
||||
let preKeyStore: InMemorySignalProtocolStore
|
||||
let signedPreKeyStore: InMemorySignalProtocolStore
|
||||
let identityStore: InMemorySignalProtocolStore
|
||||
|
||||
init(address: SMKAddress, deviceId: Int32, registrationId: Int32) {
|
||||
self.address = address
|
||||
self.deviceId = deviceId
|
||||
self.registrationId = registrationId
|
||||
self.identityKeyPair = Curve25519.generateKeyPair()
|
||||
self.identityKeyPair = try! IdentityKeyPair.generate()
|
||||
|
||||
let protocolStore = SPKMockProtocolStore(identityKeyPair: identityKeyPair, localRegistrationId: registrationId)
|
||||
let protocolStore = InMemorySignalProtocolStore(identity: identityKeyPair,
|
||||
deviceId: UInt32(bitPattern: deviceId))
|
||||
|
||||
sessionStore = protocolStore
|
||||
preKeyStore = protocolStore
|
||||
@ -54,14 +54,14 @@ class MockClient: NSObject {
|
||||
identityStore = protocolStore
|
||||
}
|
||||
|
||||
func createSessionCipher() -> SessionCipher {
|
||||
return SessionCipher(sessionStore: sessionStore,
|
||||
preKeyStore: preKeyStore,
|
||||
signedPreKeyStore: signedPreKeyStore,
|
||||
identityKeyStore: identityStore,
|
||||
recipientId: accountId,
|
||||
deviceId: deviceId)
|
||||
}
|
||||
// func createSessionCipher() -> SessionCipher {
|
||||
// return SessionCipher(sessionStore: sessionStore,
|
||||
// preKeyStore: preKeyStore,
|
||||
// signedPreKeyStore: signedPreKeyStore,
|
||||
// identityKeyStore: identityStore,
|
||||
// recipientId: accountId,
|
||||
// deviceId: deviceId)
|
||||
// }
|
||||
|
||||
func createSecretSessionCipher() throws -> SMKSecretSessionCipher {
|
||||
return try SMKSecretSessionCipher(sessionStore: sessionStore,
|
||||
@ -70,34 +70,33 @@ class MockClient: NSObject {
|
||||
identityStore: identityStore)
|
||||
}
|
||||
|
||||
func createSessionBuilder(forRecipient recipient: MockClient) -> SessionBuilder {
|
||||
return SessionBuilder(sessionStore: sessionStore,
|
||||
preKeyStore: preKeyStore,
|
||||
signedPreKeyStore: signedPreKeyStore,
|
||||
identityKeyStore: identityStore,
|
||||
recipientId: recipient.accountId,
|
||||
deviceId: recipient.deviceId)
|
||||
}
|
||||
// func createSessionBuilder(forRecipient recipient: MockClient) -> SessionBuilder {
|
||||
// return SessionBuilder(sessionStore: sessionStore,
|
||||
// preKeyStore: preKeyStore,
|
||||
// signedPreKeyStore: signedPreKeyStore,
|
||||
// identityKeyStore: identityStore,
|
||||
// recipientId: recipient.accountId,
|
||||
// deviceId: recipient.deviceId)
|
||||
// }
|
||||
|
||||
func generateMockPreKey() -> AxolotlKit.PreKeyRecord {
|
||||
let preKeyId: Int32 = Int32(arc4random_uniform(UInt32(INT32_MAX)))
|
||||
let keyPair = Curve25519.generateKeyPair()
|
||||
let preKey = AxolotlKit.PreKeyRecord(id: preKeyId, keyPair: keyPair, createdAt: Date())
|
||||
self.preKeyStore.storePreKey(preKeyId, preKeyRecord: preKey, protocolContext: nil)
|
||||
func generateMockPreKey() -> PreKeyRecord {
|
||||
let preKeyId = UInt32(Int32.random(in: 0...Int32.max))
|
||||
let preKey = try! PreKeyRecord(id: preKeyId, privateKey: try PrivateKey.generate())
|
||||
try! self.preKeyStore.storePreKey(preKey, id: preKeyId, context: nil)
|
||||
return preKey
|
||||
}
|
||||
|
||||
func generateMockSignedPreKey() -> AxolotlKit.SignedPreKeyRecord {
|
||||
let signedPreKeyId: Int32 = Int32(arc4random_uniform(UInt32(INT32_MAX)))
|
||||
func generateMockSignedPreKey() -> SignedPreKeyRecord {
|
||||
let signedPreKeyId = UInt32(Int32.random(in: 0...Int32.max))
|
||||
let keyPair = try! IdentityKeyPair.generate()
|
||||
let generatedAt = Date()
|
||||
let identityKeyPair = self.identityStore.identityKeyPair(nil)!.identityKeyPair
|
||||
let signature = Data(try! identityKeyPair.privateKey.generateSignature(message: keyPair.publicKey.serialize()))
|
||||
let signedPreKey = SignedPreKeyRecord(id: signedPreKeyId,
|
||||
keyPair: ECKeyPair(keyPair),
|
||||
signature: signature,
|
||||
generatedAt: generatedAt)
|
||||
self.signedPreKeyStore.storeSignedPreKey(signedPreKeyId, signedPreKeyRecord: signedPreKey, protocolContext: nil)
|
||||
let identityKeyPair = try! self.identityStore.identityKeyPair(context: nil)
|
||||
let signature = try! identityKeyPair.privateKey.generateSignature(message: try! keyPair.publicKey.serialize())
|
||||
let signedPreKey = try! SignedPreKeyRecord(id: signedPreKeyId,
|
||||
timestamp: UInt64(generatedAt.timeIntervalSince1970),
|
||||
privateKey: keyPair.privateKey,
|
||||
signature: signature)
|
||||
try! self.signedPreKeyStore.storeSignedPreKey(signedPreKey, id: signedPreKeyId, context: nil)
|
||||
return signedPreKey
|
||||
}
|
||||
|
||||
@ -109,18 +108,43 @@ class MockClient: NSObject {
|
||||
protocolContext: nil)!
|
||||
}
|
||||
|
||||
func storeSession(address: SMKAddress,
|
||||
deviceId: Int32,
|
||||
session: AxolotlKit.SessionRecord,
|
||||
protocolContext: SPKProtocolWriteContext?) {
|
||||
// Moved from SMKSecretSessionCipherTest.
|
||||
// private void initializeSessions(TestInMemorySignalProtocolStore aliceStore, TestInMemorySignalProtocolStore bobStore)
|
||||
// throws InvalidKeyException, UntrustedIdentityException
|
||||
func initializeSession(with bobMockClient: MockClient) {
|
||||
// ECKeyPair bobPreKey = Curve.generateKeyPair();
|
||||
let bobPreKey = bobMockClient.generateMockPreKey()
|
||||
|
||||
let accountId = accountIdFinder.accountId(forUuid: address.uuid,
|
||||
phoneNumber: address.e164,
|
||||
protocolContext: protocolContext)!
|
||||
sessionStore.storeSession(accountId,
|
||||
deviceId: deviceId,
|
||||
session: session,
|
||||
protocolContext: protocolContext)
|
||||
// IdentityKeyPair bobIdentityKey = bobStore.getIdentityKeyPair();
|
||||
let bobIdentityKey = bobMockClient.identityKeyPair
|
||||
|
||||
// SignedPreKeyRecord bobSignedPreKey = KeyHelper.generateSignedPreKey(bobIdentityKey, 2);
|
||||
let bobSignedPreKey = bobMockClient.generateMockSignedPreKey()
|
||||
|
||||
// PreKeyBundle bobBundle = new PreKeyBundle(1, 1, 1, bobPreKey.getPublicKey(), 2, bobSignedPreKey.getKeyPair().getPublicKey(), bobSignedPreKey.getSignature(), bobIdentityKey.getPublicKey());
|
||||
let bobBundle = try! SignalClient.PreKeyBundle(registrationId: UInt32(bitPattern: bobMockClient.registrationId),
|
||||
deviceId: UInt32(bitPattern: bobMockClient.deviceId),
|
||||
prekeyId: try! bobPreKey.id(),
|
||||
prekey: try! bobPreKey.publicKey(),
|
||||
signedPrekeyId: try! bobSignedPreKey.id(),
|
||||
signedPrekey: try! bobSignedPreKey.publicKey(),
|
||||
signedPrekeySignature: try! bobSignedPreKey.signature(),
|
||||
identity: bobIdentityKey.identityKey)
|
||||
|
||||
// SessionBuilder aliceSessionBuilder = new SessionBuilder(aliceStore, new SignalProtocolAddress("+14152222222", 1));
|
||||
// aliceSessionBuilder.process(bobBundle);
|
||||
let bobProtocolAddress = try! ProtocolAddress(
|
||||
name: bobMockClient.address.uuid?.uuidString ?? bobMockClient.address.e164!,
|
||||
deviceId: UInt32(bitPattern: bobMockClient.deviceId))
|
||||
try! processPreKeyBundle(bobBundle,
|
||||
for: bobProtocolAddress,
|
||||
sessionStore: sessionStore,
|
||||
identityStore: identityStore,
|
||||
context: nil)
|
||||
|
||||
// bobStore.storeSignedPreKey(2, bobSignedPreKey);
|
||||
// bobStore.storePreKey(1, new PreKeyRecord(1, bobPreKey));
|
||||
// NOTE: These stores are taken care of in the mocks' createKey() methods above.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user