// // Copyright 2020 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import CommonCrypto import LibSignalClient public class OWSProvisioningCipher: NSObject { // Local errors for logging purposes only. // FIXME: If we start propagating errors out of encrypt(_:), we'll want to revisit this. private enum Error: Swift.Error { case unexpectedLengthForInitializationVector case dataIsTooLongToEncrypt case encryptionFailed case macComputationFailed } private static let cipherKeyLength: Int = 32 private static let macKeyLength: Int = 32 private let theirPublicKey: PublicKey private let ourKeyPair: IdentityKeyPair private let initializationVector: Data public convenience init(theirPublicKey: PublicKey) { self.init( theirPublicKey: theirPublicKey, ourKeyPair: IdentityKeyPair.generate(), initializationVector: Cryptography.generateRandomBytes(UInt(kCCBlockSizeAES128)) ) } #if TESTABLE_BUILD public convenience init(theirPublicKey: PublicKey, ourKeyPair: ECKeyPair, initializationVector: Data) { self.init( theirPublicKey: theirPublicKey, ourKeyPair: ourKeyPair.identityKeyPair, initializationVector: initializationVector ) } #endif private init(theirPublicKey: PublicKey, ourKeyPair: IdentityKeyPair, initializationVector: Data) { self.theirPublicKey = theirPublicKey self.ourKeyPair = ourKeyPair self.initializationVector = initializationVector } public var ourPublicKey: PublicKey { self.ourKeyPair.publicKey } // FIXME: propagate errors from here instead of just returning nil. // This means auditing all of the places we throw OR deciding it's okay to throw arbitrary errors. @objc public func encrypt(_ data: Data) -> Data? { do { let sharedSecret = self.ourKeyPair.privateKey.keyAgreement(with: self.theirPublicKey) let infoData = ProvisioningCipher.messageInfo let derivedSecret: [UInt8] = try infoData.utf8.withContiguousStorageIfAvailable { let totalLength = Self.cipherKeyLength + Self.macKeyLength return try hkdf(outputLength: totalLength, inputKeyMaterial: sharedSecret, salt: [], info: $0) }! let cipherKey = derivedSecret[0..) throws -> Data { guard initializationVector.count == kCCBlockSizeAES128 else { // FIXME: This can only occur during testing; should it be non-recoverable? throw Error.unexpectedLengthForInitializationVector } guard data.count < Int.max - (kCCBlockSizeAES128 + initializationVector.count) else { throw Error.dataIsTooLongToEncrypt } let ciphertextBufferSize = data.count + kCCBlockSizeAES128 var ciphertextData = Data(count: ciphertextBufferSize) var bytesEncrypted = 0 let cryptStatus: CCCryptorStatus = key.withUnsafeBytes { keyBytes in initializationVector.withUnsafeBytes { ivBytes in data.withUnsafeBytes { dataBytes in ciphertextData.withUnsafeMutableBytes { ciphertextBytes in let status = CCCrypt( CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOptions(kCCOptionPKCS7Padding), keyBytes.baseAddress, keyBytes.count, ivBytes.baseAddress, dataBytes.baseAddress, dataBytes.count, ciphertextBytes.baseAddress, ciphertextBytes.count, &bytesEncrypted) return status } } } } guard cryptStatus == kCCSuccess else { throw Error.encryptionFailed } // message format is (iv || ciphertext) return initializationVector + ciphertextData.prefix(bytesEncrypted) } private func mac(forMessage message: Data, key: ArraySlice) throws -> Data { guard let mac = Cryptography.computeSHA256HMAC(message, key: Data(key)) else { throw Error.macComputationFailed } return mac } }