]> git.saurik.com Git - apple/security.git/blobdiff - keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / TrustedPeersHelper / RecoveryKey / RecoverKeySet.swift
diff --git a/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift b/keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift
new file mode 100644 (file)
index 0000000..2ed3884
--- /dev/null
@@ -0,0 +1,346 @@
+/*
+ * Copyright (c) 2019 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+import Foundation
+import SecurityFoundation
+
+let OT_RECOVERY_SIGNING_HKDF_SIZE = 56
+let OT_RECOVERY_ENCRYPTION_HKDF_SIZE = 56
+
+enum recoveryKeyType: Int {
+    case kOTRecoveryKeySigning = 1
+    case kOTRecoveryKeyEncryption = 2
+}
+
+class RecoveryKeySet: NSObject {
+    public var encryptionKey: _SFECKeyPair
+    public var signingKey: _SFECKeyPair
+
+    public var secret: Data
+    public var recoverySalt: String
+
+    public init (secret: Data, recoverySalt: String) throws {
+        self.secret = secret
+        self.recoverySalt = recoverySalt
+
+        let encryptionKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret, recoverySalt: recoverySalt)
+        self.encryptionKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: encryptionKeyData))
+
+        let signingKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret, recoverySalt: recoverySalt)
+        self.signingKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: signingKeyData))
+
+        let RecoverySigningPubKeyHash = try RecoveryKeySet.hashRecoveryedSigningPublicKey(keyData: self.signingKey.publicKey().spki())
+        _ = try RecoveryKeySet.storeRecoveryedSigningKeyPair(keyData: self.signingKey.keyData, label: RecoverySigningPubKeyHash)
+        _ = try RecoveryKeySet.storeRecoveryedEncryptionKeyPair(keyData: self.encryptionKey.keyData, label: RecoverySigningPubKeyHash)
+    }
+
+    class func generateMasterKeyString() -> (String?) {
+        return  SecRKCreateRecoveryKeyString(nil) as String
+    }
+
+    class func generateRecoveryKey(keyType: recoveryKeyType, masterSecret: Data, recoverySalt: String) throws -> (Data) {
+        var keyLength: Int
+        var info: Data
+        var infoLength: Int
+        var derivedKey: Data
+        var finalKey = Data()
+
+        switch keyType {
+        case recoveryKeyType.kOTRecoveryKeyEncryption:
+            keyLength = OT_RECOVERY_ENCRYPTION_HKDF_SIZE
+
+            let infoString = Array("Recovery Encryption Private Key".utf8)
+            info = Data(bytes: infoString, count: infoString.count)
+            infoLength = info.count
+
+            break
+        case recoveryKeyType.kOTRecoveryKeySigning:
+            keyLength = OT_RECOVERY_SIGNING_HKDF_SIZE
+
+            let infoString = Array("Recovery Signing Private Key".utf8)
+            info = Data(bytes: infoString, count: infoString.count)
+            infoLength = info.count
+
+            break
+        }
+
+        guard let cp = ccec_cp_384() else {
+            throw RecoveryKeySetError.keyGeneration
+        }
+        var status: Int32 = 0
+
+        let fullKey = TPHObjectiveC.ccec384Context()
+        defer { TPHObjectiveC.contextFree(fullKey) }
+
+        derivedKey = Data(count: keyLength)
+
+        var masterSecretMutable = masterSecret
+        let masterSecretLength = masterSecret.count
+        let derivedKeySize = derivedKey.count
+
+        let bottleSaltData = Data(bytes: Array(recoverySalt.utf8), count: recoverySalt.utf8.count)
+
+        try derivedKey.withUnsafeMutableBytes { (derivedKeyBytes: UnsafeMutablePointer<UInt8>) throws ->Void in
+            try masterSecretMutable.withUnsafeMutableBytes { (masterSecretBytes: UnsafeMutablePointer<UInt8>) throws ->Void in
+                try bottleSaltData.withUnsafeBytes { (bottleSaltBytes: UnsafePointer<UInt8>) throws -> Void in
+                    try info.withUnsafeBytes { (infoBytes: UnsafePointer<UInt8>) throws -> Void in
+                        status = cchkdf(ccsha384_di(),
+                                        masterSecretLength, masterSecretBytes,
+                                        bottleSaltData.count, bottleSaltBytes,
+                                        infoLength, infoBytes,
+                                        keyLength, derivedKeyBytes)
+                        if status != 0 {
+                            throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
+                        }
+
+                        if(keyType == recoveryKeyType.kOTRecoveryKeyEncryption || keyType == recoveryKeyType.kOTRecoveryKeySigning) {
+                            status = ccec_generate_key_deterministic(cp,
+                                                                     derivedKeySize, derivedKeyBytes,
+                                                                     ccDRBGGetRngState(),
+                                                                     UInt32(CCEC_GENKEY_DETERMINISTIC_FIPS),
+                                                                     fullKey)
+
+                            guard status == 0 else {
+                                throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
+                            }
+
+                            let space = ccec_x963_export_size(1, ccec_ctx_pub(fullKey))
+                            var key = Data(count: space)
+                            key.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
+                                ccec_x963_export(1, bytes, fullKey)
+                            }
+                            finalKey = Data(key)
+                        }
+                    }
+                }
+            }
+        }
+        return finalKey
+    }
+
+    class func createSecKey(keyData: Data) throws -> (SecKey) {
+        let keyAttributes = [kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyType: kSecAttrKeyTypeEC]
+
+        guard let key = SecKeyCreateWithData(keyData as CFData, keyAttributes as CFDictionary, nil) else {
+            throw RecoveryKeySetError.keyGeneration
+        }
+
+        return key
+    }
+
+    class func setKeyMaterialInKeychain(query: Dictionary<CFString, Any>) throws -> (Bool) {
+        var result = false
+
+        var results: CFTypeRef?
+        var status = SecItemAdd(query as CFDictionary, &results)
+
+        if status == errSecSuccess {
+            result = true
+        } else if status == errSecDuplicateItem {
+            var updateQuery: Dictionary<CFString, Any> = query
+            updateQuery[kSecClass] = nil
+
+            status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
+
+            if status != errSecSuccess {
+                throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
+            } else {
+                result = true
+            }
+        } else {
+            throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
+        }
+
+        return result
+    }
+
+    class func hashRecoveryedSigningPublicKey(keyData: Data) throws -> (String) {
+        let di = ccsha384_di()
+        var result = Data(count: TPHObjectiveC.ccsha384_diSize())
+
+        let derivedKeySize = keyData.count
+        var keyDataMutable = keyData
+        result.withUnsafeMutableBytes {(resultBytes: UnsafeMutablePointer<UInt8>) -> Void in
+            keyDataMutable.withUnsafeMutableBytes {(keyDataBytes: UnsafeMutablePointer<UInt8>) -> Void in
+                ccdigest(di, derivedKeySize, keyDataBytes, resultBytes)
+            }
+        }
+        let hash = result.base64EncodedString(options: [])
+
+        return hash
+    }
+
+    class func storeRecoveryedEncryptionKeyPair(keyData: Data, label: String) throws -> (Bool) {
+
+        let query: [CFString: Any] = [
+            kSecClass: kSecClassKey,
+            kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
+            kSecUseDataProtectionKeychain: true,
+            kSecAttrAccessGroup: "com.apple.security.octagon",
+            kSecAttrSynchronizable: kCFBooleanFalse,
+            kSecAttrLabel: label,
+            kSecAttrApplicationLabel: String(format: "Recoveryed Encryption Key-%@", NSUUID().uuidString),
+            kSecValueData: keyData,
+            ]
+        return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
+    }
+
+    class func storeRecoveryedSigningKeyPair(keyData: Data, label: String) throws -> (Bool) {
+        let query: [CFString: Any] = [
+            kSecClass: kSecClassKey,
+            kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
+            kSecUseDataProtectionKeychain: true,
+            kSecAttrAccessGroup: "com.apple.security.octagon",
+            kSecAttrSynchronizable: kCFBooleanFalse,
+            kSecAttrApplicationLabel: String(format: "Recoveryed Signing Key-%@", NSUUID().uuidString),
+            kSecAttrLabel: label,
+            kSecValueData: keyData,
+            ]
+        return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
+    }
+
+    class func retrieveRecoveryKeysFromKeychain(label: String) throws -> [Dictionary <CFString, Any>]? {
+        var keySet: [Dictionary<CFString, Any>]?
+
+        let query: [CFString: Any] = [
+            kSecClass: kSecClassKey,
+            kSecAttrAccessGroup: "com.apple.security.octagon",
+            kSecAttrLabel: label,
+            kSecReturnAttributes: true,
+            kSecReturnData: true,
+            kSecAttrSynchronizable: kCFBooleanFalse,
+            kSecMatchLimit: kSecMatchLimitAll,
+            ]
+
+        var result: CFTypeRef?
+        let status = SecItemCopyMatching(query as CFDictionary, &result)
+
+        if status != errSecSuccess || result == nil {
+            throw RecoveryKeySetError.itemDoesNotExist
+        }
+
+        if result != nil {
+            if let dictionaryArray = result as? [Dictionary<CFString, Any>] {
+                keySet = dictionaryArray
+            } else {
+                if let dictionary = result as? Dictionary<CFString, Any> {
+                    keySet = [dictionary]
+                } else {
+                    keySet = nil
+                }
+            }
+        }
+        return keySet
+    }
+
+    class func findRecoveryKeysForLabel(label: String) throws -> (_SFECKeyPair?, _SFECKeyPair?) {
+        var signingKey: _SFECKeyPair?
+        var encryptionKey: _SFECKeyPair?
+
+        let keySet = try retrieveRecoveryKeysFromKeychain(label: label)
+        if keySet == nil {
+            throw RecoveryKeySetError.itemDoesNotExist
+        }
+        for item in keySet! {
+            let keyTypeData = item[kSecAttrApplicationLabel as CFString] as! Data
+            let keyType = String(data: keyTypeData, encoding: .utf8)!
+
+            if keyType.range(of: "Encryption") != nil {
+                let keyData = item[kSecValueData as CFString] as! Data
+                let encryptionSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
+                encryptionKey = _SFECKeyPair.init(secKey: encryptionSecKey)
+            } else if keyType.range(of: "Signing") != nil {
+                let keyData = item[kSecValueData as CFString] as! Data
+                let signingSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
+                signingKey = _SFECKeyPair.init(secKey: signingSecKey)
+            } else {
+                throw RecoveryKeySetError.unsupportedKeyType(keyType: keyType)
+            }
+        }
+
+        return (signingKey, encryptionKey)
+    }
+}
+
+enum RecoveryKeySetError: Error {
+    case keyGeneration
+    case itemDoesNotExist
+    case failedToSaveToKeychain(errorCode: OSStatus)
+    case unsupportedKeyType(keyType: String)
+    case corecryptoKeyGeneration(corecryptoError: Int32)
+}
+
+extension RecoveryKeySetError: LocalizedError {
+    public var errorDescription: String? {
+        switch self {
+        case .keyGeneration:
+            return "Key generation failed"
+        case .itemDoesNotExist:
+            return "Item does not exist"
+        case .failedToSaveToKeychain(errorCode: let osError):
+            return "Failed to save item to keychain: \(osError)"
+        case .unsupportedKeyType(keyType: let keyType):
+            return "Unsupported Key Type \(keyType)"
+        case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
+            return "Key generation crypto failed \(corecryptoError)"
+        }
+    }
+}
+
+extension RecoveryKeySetError: CustomNSError {
+
+    public static var errorDomain: String {
+        return "com.apple.security.trustedpeers.RecoveryKeySetError"
+    }
+
+    public var errorCode: Int {
+        switch self {
+        case .keyGeneration:
+            return 1
+        case .itemDoesNotExist:
+            return 2
+        case .failedToSaveToKeychain:
+            return 3
+        case .unsupportedKeyType:
+            return 4
+        case .corecryptoKeyGeneration:
+            return 5
+        }
+    }
+
+    public var errorUserInfo: [String: Any] {
+        var userInfo: [String: Any] = [:]
+        if let desc = self.errorDescription {
+            userInfo[NSLocalizedDescriptionKey] = desc
+        }
+        switch self {
+        case .failedToSaveToKeychain(errorCode: let osError):
+            userInfo[NSUnderlyingErrorKey] = NSError.init(domain: NSOSStatusErrorDomain, code: Int(osError), userInfo: nil)
+        case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
+            userInfo[NSUnderlyingErrorKey] = NSError.init(domain: "corecrypto", code: Int(corecryptoError), userInfo: nil)
+        default:
+            break
+        }
+        return userInfo
+    }
+}