]> git.saurik.com Git - apple/security.git/blob - keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / TrustedPeersHelper / RecoveryKey / RecoverKeySet.swift
1 /*
2 * Copyright (c) 2019 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 import Foundation
25 import SecurityFoundation
26
27 let OT_RECOVERY_SIGNING_HKDF_SIZE = 56
28 let OT_RECOVERY_ENCRYPTION_HKDF_SIZE = 56
29
30 enum recoveryKeyType: Int {
31 case kOTRecoveryKeySigning = 1
32 case kOTRecoveryKeyEncryption = 2
33 }
34
35 class RecoveryKeySet: NSObject {
36 public var encryptionKey: _SFECKeyPair
37 public var signingKey: _SFECKeyPair
38
39 public var secret: Data
40 public var recoverySalt: String
41
42 public init (secret: Data, recoverySalt: String) throws {
43 self.secret = secret
44 self.recoverySalt = recoverySalt
45
46 let encryptionKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret, recoverySalt: recoverySalt)
47 self.encryptionKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: encryptionKeyData))
48
49 let signingKeyData = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret, recoverySalt: recoverySalt)
50 self.signingKey = _SFECKeyPair.init(secKey: try RecoveryKeySet.createSecKey(keyData: signingKeyData))
51
52 let RecoverySigningPubKeyHash = try RecoveryKeySet.hashRecoveryedSigningPublicKey(keyData: self.signingKey.publicKey().spki())
53 _ = try RecoveryKeySet.storeRecoveryedSigningKeyPair(keyData: self.signingKey.keyData, label: RecoverySigningPubKeyHash)
54 _ = try RecoveryKeySet.storeRecoveryedEncryptionKeyPair(keyData: self.encryptionKey.keyData, label: RecoverySigningPubKeyHash)
55 }
56
57 class func generateMasterKeyString() -> (String?) {
58 return SecRKCreateRecoveryKeyString(nil) as String
59 }
60
61 class func generateRecoveryKey(keyType: recoveryKeyType, masterSecret: Data, recoverySalt: String) throws -> (Data) {
62 var keyLength: Int
63 var info: Data
64 var infoLength: Int
65 var derivedKey: Data
66 var finalKey = Data()
67
68 switch keyType {
69 case recoveryKeyType.kOTRecoveryKeyEncryption:
70 keyLength = OT_RECOVERY_ENCRYPTION_HKDF_SIZE
71
72 let infoString = Array("Recovery Encryption Private Key".utf8)
73 info = Data(bytes: infoString, count: infoString.count)
74 infoLength = info.count
75
76 break
77 case recoveryKeyType.kOTRecoveryKeySigning:
78 keyLength = OT_RECOVERY_SIGNING_HKDF_SIZE
79
80 let infoString = Array("Recovery Signing Private Key".utf8)
81 info = Data(bytes: infoString, count: infoString.count)
82 infoLength = info.count
83
84 break
85 }
86
87 guard let cp = ccec_cp_384() else {
88 throw RecoveryKeySetError.keyGeneration
89 }
90 var status: Int32 = 0
91
92 let fullKey = TPHObjectiveC.ccec384Context()
93 defer { TPHObjectiveC.contextFree(fullKey) }
94
95 derivedKey = Data(count: keyLength)
96
97 var masterSecretMutable = masterSecret
98 let masterSecretLength = masterSecret.count
99 let derivedKeySize = derivedKey.count
100
101 let bottleSaltData = Data(bytes: Array(recoverySalt.utf8), count: recoverySalt.utf8.count)
102
103 try derivedKey.withUnsafeMutableBytes { (derivedKeyBytes: UnsafeMutablePointer<UInt8>) throws ->Void in
104 try masterSecretMutable.withUnsafeMutableBytes { (masterSecretBytes: UnsafeMutablePointer<UInt8>) throws ->Void in
105 try bottleSaltData.withUnsafeBytes { (bottleSaltBytes: UnsafePointer<UInt8>) throws -> Void in
106 try info.withUnsafeBytes { (infoBytes: UnsafePointer<UInt8>) throws -> Void in
107 status = cchkdf(ccsha384_di(),
108 masterSecretLength, masterSecretBytes,
109 bottleSaltData.count, bottleSaltBytes,
110 infoLength, infoBytes,
111 keyLength, derivedKeyBytes)
112 if status != 0 {
113 throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
114 }
115
116 if(keyType == recoveryKeyType.kOTRecoveryKeyEncryption || keyType == recoveryKeyType.kOTRecoveryKeySigning) {
117 status = ccec_generate_key_deterministic(cp,
118 derivedKeySize, derivedKeyBytes,
119 ccDRBGGetRngState(),
120 UInt32(CCEC_GENKEY_DETERMINISTIC_FIPS),
121 fullKey)
122
123 guard status == 0 else {
124 throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
125 }
126
127 let space = ccec_x963_export_size(1, ccec_ctx_pub(fullKey))
128 var key = Data(count: space)
129 key.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
130 ccec_x963_export(1, bytes, fullKey)
131 }
132 finalKey = Data(key)
133 }
134 }
135 }
136 }
137 }
138 return finalKey
139 }
140
141 class func createSecKey(keyData: Data) throws -> (SecKey) {
142 let keyAttributes = [kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyType: kSecAttrKeyTypeEC]
143
144 guard let key = SecKeyCreateWithData(keyData as CFData, keyAttributes as CFDictionary, nil) else {
145 throw RecoveryKeySetError.keyGeneration
146 }
147
148 return key
149 }
150
151 class func setKeyMaterialInKeychain(query: Dictionary<CFString, Any>) throws -> (Bool) {
152 var result = false
153
154 var results: CFTypeRef?
155 var status = SecItemAdd(query as CFDictionary, &results)
156
157 if status == errSecSuccess {
158 result = true
159 } else if status == errSecDuplicateItem {
160 var updateQuery: Dictionary<CFString, Any> = query
161 updateQuery[kSecClass] = nil
162
163 status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
164
165 if status != errSecSuccess {
166 throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
167 } else {
168 result = true
169 }
170 } else {
171 throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
172 }
173
174 return result
175 }
176
177 class func hashRecoveryedSigningPublicKey(keyData: Data) throws -> (String) {
178 let di = ccsha384_di()
179 var result = Data(count: TPHObjectiveC.ccsha384_diSize())
180
181 let derivedKeySize = keyData.count
182 var keyDataMutable = keyData
183 result.withUnsafeMutableBytes {(resultBytes: UnsafeMutablePointer<UInt8>) -> Void in
184 keyDataMutable.withUnsafeMutableBytes {(keyDataBytes: UnsafeMutablePointer<UInt8>) -> Void in
185 ccdigest(di, derivedKeySize, keyDataBytes, resultBytes)
186 }
187 }
188 let hash = result.base64EncodedString(options: [])
189
190 return hash
191 }
192
193 class func storeRecoveryedEncryptionKeyPair(keyData: Data, label: String) throws -> (Bool) {
194
195 let query: [CFString: Any] = [
196 kSecClass: kSecClassKey,
197 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
198 kSecUseDataProtectionKeychain: true,
199 kSecAttrAccessGroup: "com.apple.security.octagon",
200 kSecAttrSynchronizable: kCFBooleanFalse,
201 kSecAttrLabel: label,
202 kSecAttrApplicationLabel: String(format: "Recoveryed Encryption Key-%@", NSUUID().uuidString),
203 kSecValueData: keyData,
204 ]
205 return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
206 }
207
208 class func storeRecoveryedSigningKeyPair(keyData: Data, label: String) throws -> (Bool) {
209 let query: [CFString: Any] = [
210 kSecClass: kSecClassKey,
211 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
212 kSecUseDataProtectionKeychain: true,
213 kSecAttrAccessGroup: "com.apple.security.octagon",
214 kSecAttrSynchronizable: kCFBooleanFalse,
215 kSecAttrApplicationLabel: String(format: "Recoveryed Signing Key-%@", NSUUID().uuidString),
216 kSecAttrLabel: label,
217 kSecValueData: keyData,
218 ]
219 return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
220 }
221
222 class func retrieveRecoveryKeysFromKeychain(label: String) throws -> [Dictionary <CFString, Any>]? {
223 var keySet: [Dictionary<CFString, Any>]?
224
225 let query: [CFString: Any] = [
226 kSecClass: kSecClassKey,
227 kSecAttrAccessGroup: "com.apple.security.octagon",
228 kSecAttrLabel: label,
229 kSecReturnAttributes: true,
230 kSecReturnData: true,
231 kSecAttrSynchronizable: kCFBooleanFalse,
232 kSecMatchLimit: kSecMatchLimitAll,
233 ]
234
235 var result: CFTypeRef?
236 let status = SecItemCopyMatching(query as CFDictionary, &result)
237
238 if status != errSecSuccess || result == nil {
239 throw RecoveryKeySetError.itemDoesNotExist
240 }
241
242 if result != nil {
243 if let dictionaryArray = result as? [Dictionary<CFString, Any>] {
244 keySet = dictionaryArray
245 } else {
246 if let dictionary = result as? Dictionary<CFString, Any> {
247 keySet = [dictionary]
248 } else {
249 keySet = nil
250 }
251 }
252 }
253 return keySet
254 }
255
256 class func findRecoveryKeysForLabel(label: String) throws -> (_SFECKeyPair?, _SFECKeyPair?) {
257 var signingKey: _SFECKeyPair?
258 var encryptionKey: _SFECKeyPair?
259
260 let keySet = try retrieveRecoveryKeysFromKeychain(label: label)
261 if keySet == nil {
262 throw RecoveryKeySetError.itemDoesNotExist
263 }
264 for item in keySet! {
265 let keyTypeData = item[kSecAttrApplicationLabel as CFString] as! Data
266 let keyType = String(data: keyTypeData, encoding: .utf8)!
267
268 if keyType.range(of: "Encryption") != nil {
269 let keyData = item[kSecValueData as CFString] as! Data
270 let encryptionSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
271 encryptionKey = _SFECKeyPair.init(secKey: encryptionSecKey)
272 } else if keyType.range(of: "Signing") != nil {
273 let keyData = item[kSecValueData as CFString] as! Data
274 let signingSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
275 signingKey = _SFECKeyPair.init(secKey: signingSecKey)
276 } else {
277 throw RecoveryKeySetError.unsupportedKeyType(keyType: keyType)
278 }
279 }
280
281 return (signingKey, encryptionKey)
282 }
283 }
284
285 enum RecoveryKeySetError: Error {
286 case keyGeneration
287 case itemDoesNotExist
288 case failedToSaveToKeychain(errorCode: OSStatus)
289 case unsupportedKeyType(keyType: String)
290 case corecryptoKeyGeneration(corecryptoError: Int32)
291 }
292
293 extension RecoveryKeySetError: LocalizedError {
294 public var errorDescription: String? {
295 switch self {
296 case .keyGeneration:
297 return "Key generation failed"
298 case .itemDoesNotExist:
299 return "Item does not exist"
300 case .failedToSaveToKeychain(errorCode: let osError):
301 return "Failed to save item to keychain: \(osError)"
302 case .unsupportedKeyType(keyType: let keyType):
303 return "Unsupported Key Type \(keyType)"
304 case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
305 return "Key generation crypto failed \(corecryptoError)"
306 }
307 }
308 }
309
310 extension RecoveryKeySetError: CustomNSError {
311
312 public static var errorDomain: String {
313 return "com.apple.security.trustedpeers.RecoveryKeySetError"
314 }
315
316 public var errorCode: Int {
317 switch self {
318 case .keyGeneration:
319 return 1
320 case .itemDoesNotExist:
321 return 2
322 case .failedToSaveToKeychain:
323 return 3
324 case .unsupportedKeyType:
325 return 4
326 case .corecryptoKeyGeneration:
327 return 5
328 }
329 }
330
331 public var errorUserInfo: [String: Any] {
332 var userInfo: [String: Any] = [:]
333 if let desc = self.errorDescription {
334 userInfo[NSLocalizedDescriptionKey] = desc
335 }
336 switch self {
337 case .failedToSaveToKeychain(errorCode: let osError):
338 userInfo[NSUnderlyingErrorKey] = NSError.init(domain: NSOSStatusErrorDomain, code: Int(osError), userInfo: nil)
339 case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
340 userInfo[NSUnderlyingErrorKey] = NSError.init(domain: "corecrypto", code: Int(corecryptoError), userInfo: nil)
341 default:
342 break
343 }
344 return userInfo
345 }
346 }