// // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // import Foundation import HKDFKit @objc public class SecretSessionKnownSenderError: NSObject, CustomNSError { @objc public static let kSenderRecipientIdKey = "kSenderRecipientIdKey" @objc public static let kSenderDeviceIdKey = "kSenderDeviceIdKey" public let senderRecipientId: String public let senderDeviceId: UInt32 public let underlyingError: Error init(senderRecipientId: String, senderDeviceId: UInt32, underlyingError: Error) { self.senderRecipientId = senderRecipientId self.senderDeviceId = senderDeviceId self.underlyingError = underlyingError } public var errorUserInfo: [String: Any] { return [ type(of: self).kSenderRecipientIdKey: self.senderRecipientId, type(of: self).kSenderDeviceIdKey: self.senderDeviceId, NSUnderlyingErrorKey: (underlyingError as NSError) ] } } @objc public enum SMKSecretSessionCipherError: Int, Error { case selfSentMessage } // See: // https://github.com/signalapp/libsignal-metadata-java/blob/master/java/src/main/java/org/signal/libsignal/metadata/SecretSessionCipher.java public extension ECKeyPair { // TODO: Rename to publicKey(), rename existing publicKey() method to publicKeyData(). func ecPublicKey() throws -> ECPublicKey { guard publicKey().count == ECCKeyLength else { throw SMKError.assertionError(description: "\(logTag) public key has invalid length") } // NOTE: we don't use ECPublicKey(serializedKeyData:) since the // key data should not have a type byte. return try ECPublicKey(keyData: publicKey()) } // TODO: Rename to privateKey(), rename existing privateKey() method to privateKeyData(). func ecPrivateKey() throws -> ECPrivateKey { guard privateKey().count == ECCKeyLength else { throw SMKError.assertionError(description: "\(logTag) private key has invalid length") } return try ECPrivateKey(keyData: privateKey()) } } // MARK: - private class SMKSecretKeySpec: NSObject { @objc public let keyData: Data @objc public let algorithm: String init(keyData: Data, algorithm: String) { self.keyData = keyData self.algorithm = algorithm } } // MARK: - private class SMKEphemeralKeys: NSObject { @objc public let chainKey: Data @objc public let cipherKey: SMKSecretKeySpec @objc public let macKey: SMKSecretKeySpec init(chainKey: Data, cipherKey: Data, macKey: Data) { self.chainKey = chainKey self.cipherKey = SMKSecretKeySpec(keyData: cipherKey, algorithm: "AES") self.macKey = SMKSecretKeySpec(keyData: macKey, algorithm: "HmacSHA256") } } // MARK: - private class SMKStaticKeys: NSObject { @objc public let cipherKey: SMKSecretKeySpec @objc public let macKey: SMKSecretKeySpec init(cipherKey: Data, macKey: Data) { self.cipherKey = SMKSecretKeySpec(keyData: cipherKey, algorithm: "AES") self.macKey = SMKSecretKeySpec(keyData: macKey, algorithm: "HmacSHA256") } } // MARK: - @objc public class SMKDecryptResult: NSObject { @objc public let senderRecipientId: String @objc public let senderDeviceId: Int @objc public let paddedPayload: Data @objc public let messageType: SMKMessageType init(senderRecipientId: String, senderDeviceId: Int, paddedPayload: Data, messageType: SMKMessageType) { self.senderRecipientId = senderRecipientId self.senderDeviceId = senderDeviceId self.paddedPayload = paddedPayload self.messageType = messageType } } // MARK: - @objc public class SMKSecretSessionCipher: NSObject { private let kUDPrefixString = "UnidentifiedDelivery" private let kSMKSecretSessionCipherMacLength: UInt = 10 private let sessionResetImplementation: SessionRestorationProtocol! private let sessionStore: SessionStore private let preKeyStore: PreKeyStore private let signedPreKeyStore: SignedPreKeyStore private let identityStore: IdentityKeyStore @objc public init(sessionResetImplementation: SessionRestorationProtocol!, sessionStore: SessionStore, preKeyStore: PreKeyStore, signedPreKeyStore: SignedPreKeyStore, identityStore: IdentityKeyStore) throws { self.sessionResetImplementation = sessionResetImplementation self.sessionStore = sessionStore self.preKeyStore = preKeyStore self.signedPreKeyStore = signedPreKeyStore self.identityStore = identityStore } @objc public convenience init(sessionStore: SessionStore, preKeyStore: PreKeyStore, signedPreKeyStore: SignedPreKeyStore, identityStore: IdentityKeyStore) throws { try self.init(sessionResetImplementation: nil, sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityStore: identityStore) } // MARK: - Public @objc public func throwswrapped_encryptMessage(recipientPublicKey: String, deviceID: Int32, paddedPlaintext: Data, senderCertificate: SMKSenderCertificate, protocolContext: Any, useFallbackSessionCipher: Bool) throws -> Data { guard recipientPublicKey.count > 0 else { throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid recipientId") } guard deviceID > 0 else { throw SMKError.assertionError(description: "\(SMKSecretSessionCipher.logTag) invalid deviceId") } guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else { throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.") } let encryptedMessage: CipherMessage if useFallbackSessionCipher { let cipher = FallBackSessionCipher(recipientPublicKey: recipientPublicKey, privateKey: try ourIdentityKeyPair.privateKey()) let ivAndCiphertext = cipher.encrypt(paddedPlaintext)! encryptedMessage = FallbackMessage(_throws_with: ivAndCiphertext) } else { let cipher = SessionCipher(sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityKeyStore: identityStore, recipientId: recipientPublicKey, deviceId: deviceID) encryptedMessage = try cipher.encryptMessage(paddedPlaintext, protocolContext: protocolContext) } guard let encryptedMessageData = encryptedMessage.serialized() else { throw SMKError.assertionError(description: "\(logTag) Could not serialize encrypted message.") } guard let theirIdentityKeyData = Data.data(fromHex: recipientPublicKey.substring(from: recipientPublicKey.index(recipientPublicKey.startIndex, offsetBy: 2))) else { throw SMKError.assertionError(description: "\(logTag) Missing their public identity key.") } // NOTE: we don't use ECPublicKey(serializedKeyData) since the // key data should not have a type byte. let theirIdentityKey = try ECPublicKey(keyData: theirIdentityKeyData) let ephemeral = Curve25519.generateKeyPair()! 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 ]) let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: theirIdentityKey, ephemeralPrivateKey: ephemeral.ecPrivateKey(), salt: ephemeralSalt) let staticKeyCipherData = try encrypt(cipherKey: ephemeralKeys.cipherKey, macKey: ephemeralKeys.macKey, plaintextData: ourIdentityKeyPair.ecPublicKey().serialized) let staticSalt = NSData.join([ ephemeralKeys.chainKey, staticKeyCipherData ]) let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: theirIdentityKey, staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(), salt: staticSalt) let messageType: SMKMessageType switch encryptedMessage.cipherMessageType { case .prekey: messageType = .prekey case .whisper: messageType = .whisper case .fallback: messageType = .fallback default: throw SMKError.assertionError(description: "\(logTag) Unknown cipher message type.") } let messageContent = SMKUnidentifiedSenderMessageContent(messageType: messageType, senderCertificate: senderCertificate, contentData: encryptedMessageData) let messageData = try encrypt(cipherKey: staticKeys.cipherKey, macKey: staticKeys.macKey, plaintextData: try messageContent.serialized()) let message = SMKUnidentifiedSenderMessage(ephemeralKey: try ephemeral.ecPublicKey(), encryptedStatic: staticKeyCipherData, encryptedMessage: messageData) return try message.serialized() } @objc public func throwswrapped_decryptMessage(certificateValidator: SMKCertificateValidator, cipherTextData: Data, timestamp: UInt64, localRecipientId: String, localDeviceId: Int32, protocolContext: Any) throws -> SMKDecryptResult { guard timestamp > 0 else { throw SMKError.assertionError(description: "\(logTag) invalid timestamp") } guard let ourIdentityKeyPair = identityStore.identityKeyPair(protocolContext) else { throw SMKError.assertionError(description: "\(logTag) Missing our identity key pair.") } let wrapper = try SMKUnidentifiedSenderMessage.parse(dataAndPrefix: cipherTextData) 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 ]) let ephemeralKeys = try throwswrapped_calculateEphemeralKeys(ephemeralPublicKey: wrapper.ephemeralKey, ephemeralPrivateKey: ourIdentityKeyPair.ecPrivateKey(), salt: ephemeralSalt) let staticKeyBytes = try decrypt(cipherKey: ephemeralKeys.cipherKey, macKey: ephemeralKeys.macKey, cipherTextWithMac: wrapper.encryptedStatic) let staticKey = try ECPublicKey(serializedKeyData: staticKeyBytes) let staticSalt = NSData.join([ ephemeralKeys.chainKey, wrapper.encryptedStatic ]) let staticKeys = try throwswrapped_calculateStaticKeys(staticPublicKey: staticKey, staticPrivateKey: ourIdentityKeyPair.ecPrivateKey(), salt: staticSalt) let messageBytes = try decrypt(cipherKey: staticKeys.cipherKey, macKey: staticKeys.macKey, cipherTextWithMac: wrapper.encryptedMessage) let messageContent = try SMKUnidentifiedSenderMessageContent.parse(data: messageBytes) let senderRecipientId = messageContent.senderCertificate.senderRecipientId let senderDeviceId = messageContent.senderCertificate.senderDeviceId guard senderRecipientId != localRecipientId || senderDeviceId != localDeviceId else { Logger.info("Discarding self-sent message") throw SMKSecretSessionCipherError.selfSentMessage } // validator.validate(content.getSenderCertificate(), timestamp); let wrapAsKnownSenderError = { (underlyingError: Error) in return SecretSessionKnownSenderError(senderRecipientId: senderRecipientId, senderDeviceId: senderDeviceId, underlyingError: underlyingError) } do { try certificateValidator.throwswrapped_validate(senderCertificate: messageContent.senderCertificate, validationTime: timestamp) } catch { throw wrapAsKnownSenderError(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. // guard messageContent.senderCertificate.key.serialized.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) } // 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(senderRecipientId: senderRecipientId, 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 keyPair = ECKeyPair(publicKey: ephemeralPublicKey.keyData, privateKey: ephemeralPrivateKey.keyData) let ephemeralSecret = try Curve25519.generateSharedSecret(fromPublicKey: ephemeralPublicKey.keyData, andKeyPair: keyPair) // 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 keyPair = ECKeyPair(publicKey: staticPublicKey.keyData, privateKey: staticPrivateKey.keyData) let staticSecret = Curve25519.generateSharedSecret(fromPublicKey: staticPublicKey.keyData, andKeyPair: keyPair) // byte[] staticDerived = new HKDFv3().deriveSecrets(staticSecret, salt, new byte[0], 96); let kStaticDerivedLength: UInt = 96 let staticDerived: Data = 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) _ = 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 } // MARK: - Decrypt private func throwswrapped_decrypt(messageContent: SMKUnidentifiedSenderMessageContent, protocolContext: Any) throws -> Data { // NOTE: We use the sender properties from the sender certificate, not from this class' properties. let senderRecipientId = messageContent.senderCertificate.senderRecipientId let senderDeviceId = messageContent.senderCertificate.senderDeviceId guard senderDeviceId >= 0 && senderDeviceId <= INT32_MAX else { throw SMKError.assertionError(description: "\(logTag) Invalid senderDeviceId.") } let cipherMessage: CipherMessage switch (messageContent.messageType) { case .whisper: cipherMessage = try WhisperMessage(data: messageContent.contentData) case .prekey: cipherMessage = try PreKeyWhisperMessage(data: messageContent.contentData) case .fallback: let privateKey = try? identityStore.identityKeyPair(protocolContext)?.privateKey() let cipher = FallBackSessionCipher(recipientPublicKey: senderRecipientId, privateKey: privateKey) let plaintext = cipher.decrypt(messageContent.contentData)! return plaintext } let cipher = LokiSessionCipher(sessionResetImplementation: sessionResetImplementation, sessionStore: sessionStore, preKeyStore: preKeyStore, signedPreKeyStore: signedPreKeyStore, identityKeyStore: identityStore, recipientID: senderRecipientId, 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..