]> git.saurik.com Git - apple/security.git/blob - keychain/TrustedPeersHelper/RecoveryKey/RecoverKeySet.swift
Security-59306.41.2.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 derivedKey: Data
65 var finalKey = Data()
66
67 switch keyType {
68 case recoveryKeyType.kOTRecoveryKeyEncryption:
69 keyLength = OT_RECOVERY_ENCRYPTION_HKDF_SIZE
70
71 let infoString = Array("Recovery Encryption Private Key".utf8)
72 info = Data(bytes: infoString, count: infoString.count)
73
74 break
75 case recoveryKeyType.kOTRecoveryKeySigning:
76 keyLength = OT_RECOVERY_SIGNING_HKDF_SIZE
77
78 let infoString = Array("Recovery Signing Private Key".utf8)
79 info = Data(bytes: infoString, count: infoString.count)
80
81 break
82 }
83
84 guard let cp = ccec_cp_384() else {
85 throw RecoveryKeySetError.keyGeneration
86 }
87 var status: Int32 = 0
88
89 let fullKey = TPHObjectiveC.ccec384Context()
90 defer { TPHObjectiveC.contextFree(fullKey) }
91
92 derivedKey = Data(count: keyLength)
93
94 var masterSecretMutable = masterSecret
95
96 let bottleSaltData = Data(bytes: Array(recoverySalt.utf8), count: recoverySalt.utf8.count)
97
98 try derivedKey.withUnsafeMutableBytes { (derivedKeyBytes: UnsafeMutableRawBufferPointer) throws ->Void in
99 try masterSecretMutable.withUnsafeMutableBytes { (masterSecretBytes: UnsafeMutableRawBufferPointer) throws ->Void in
100 try bottleSaltData.withUnsafeBytes { (bottleSaltBytes: UnsafeRawBufferPointer) throws -> Void in
101 try info.withUnsafeBytes { (infoBytes: UnsafeRawBufferPointer) throws -> Void in
102 status = cchkdf(ccsha384_di(),
103 masterSecretBytes.count, masterSecretBytes.baseAddress!,
104 bottleSaltBytes.count, bottleSaltBytes.baseAddress!,
105 infoBytes.count, infoBytes.baseAddress!,
106 derivedKeyBytes.count, derivedKeyBytes.baseAddress!)
107 if status != 0 {
108 throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
109 }
110
111 if(keyType == recoveryKeyType.kOTRecoveryKeyEncryption || keyType == recoveryKeyType.kOTRecoveryKeySigning) {
112 status = ccec_generate_key_deterministic(cp,
113 derivedKeyBytes.count, derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress!,
114 ccDRBGGetRngState(),
115 UInt32(CCEC_GENKEY_DETERMINISTIC_FIPS),
116 fullKey)
117
118 guard status == 0 else {
119 throw RecoveryKeySetError.corecryptoKeyGeneration(corecryptoError: status)
120 }
121
122 let space = ccec_x963_export_size(1, ccec_ctx_pub(fullKey))
123 var key = Data(count: space)
124 key.withUnsafeMutableBytes { (bytes: UnsafeMutableRawBufferPointer) -> Void in
125 ccec_x963_export(1, bytes.baseAddress!, fullKey)
126 }
127 finalKey = Data(key)
128 }
129 }
130 }
131 }
132 }
133 return finalKey
134 }
135
136 class func createSecKey(keyData: Data) throws -> (SecKey) {
137 let keyAttributes = [kSecAttrKeyClass: kSecAttrKeyClassPrivate, kSecAttrKeyType: kSecAttrKeyTypeEC]
138
139 guard let key = SecKeyCreateWithData(keyData as CFData, keyAttributes as CFDictionary, nil) else {
140 throw RecoveryKeySetError.keyGeneration
141 }
142
143 return key
144 }
145
146 class func setKeyMaterialInKeychain(query: Dictionary<CFString, Any>) throws -> (Bool) {
147 var result = false
148
149 var results: CFTypeRef?
150 var status = SecItemAdd(query as CFDictionary, &results)
151
152 if status == errSecSuccess {
153 result = true
154 } else if status == errSecDuplicateItem {
155 var updateQuery: Dictionary<CFString, Any> = query
156 updateQuery[kSecClass] = nil
157
158 status = SecItemUpdate(query as CFDictionary, updateQuery as CFDictionary)
159
160 if status != errSecSuccess {
161 throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
162 } else {
163 result = true
164 }
165 } else {
166 throw RecoveryKeySetError.failedToSaveToKeychain(errorCode: status)
167 }
168
169 return result
170 }
171
172 class func hashRecoveryedSigningPublicKey(keyData: Data) throws -> (String) {
173 let di = ccsha384_di()
174 var result = Data(count: TPHObjectiveC.ccsha384_diSize())
175
176 var keyDataMutable = keyData
177 result.withUnsafeMutableBytes {(resultBytes: UnsafeMutableRawBufferPointer) -> Void in
178 keyDataMutable.withUnsafeMutableBytes {(keyDataBytes: UnsafeMutableRawBufferPointer) -> Void in
179 ccdigest(di, keyDataBytes.count, keyDataBytes.baseAddress!, resultBytes.baseAddress!)
180 }
181 }
182 let hash = result.base64EncodedString(options: [])
183
184 return hash
185 }
186
187 class func storeRecoveryedEncryptionKeyPair(keyData: Data, label: String) throws -> (Bool) {
188
189 let query: [CFString: Any] = [
190 kSecClass: kSecClassKey,
191 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
192 kSecUseDataProtectionKeychain: true,
193 kSecAttrAccessGroup: "com.apple.security.octagon",
194 kSecAttrSynchronizable: false,
195 kSecAttrLabel: label,
196 kSecAttrApplicationLabel: String(format: "Recoveryed Encryption Key-%@", NSUUID().uuidString),
197 kSecValueData: keyData,
198 ]
199 return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
200 }
201
202 class func storeRecoveryedSigningKeyPair(keyData: Data, label: String) throws -> (Bool) {
203 let query: [CFString: Any] = [
204 kSecClass: kSecClassKey,
205 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
206 kSecUseDataProtectionKeychain: true,
207 kSecAttrAccessGroup: "com.apple.security.octagon",
208 kSecAttrSynchronizable: false,
209 kSecAttrApplicationLabel: String(format: "Recoveryed Signing Key-%@", NSUUID().uuidString),
210 kSecAttrLabel: label,
211 kSecValueData: keyData,
212 ]
213 return try RecoveryKeySet.setKeyMaterialInKeychain(query: query)
214 }
215
216 class func retrieveRecoveryKeysFromKeychain(label: String) throws -> [Dictionary <CFString, Any>]? {
217 var keySet: [Dictionary<CFString, Any>]?
218
219 let query: [CFString: Any] = [
220 kSecClass: kSecClassKey,
221 kSecAttrAccessGroup: "com.apple.security.octagon",
222 kSecAttrLabel: label,
223 kSecReturnAttributes: true,
224 kSecReturnData: true,
225 kSecAttrSynchronizable: false,
226 kSecMatchLimit: kSecMatchLimitAll,
227 ]
228
229 var result: CFTypeRef?
230 let status = SecItemCopyMatching(query as CFDictionary, &result)
231
232 if status != errSecSuccess || result == nil {
233 throw RecoveryKeySetError.itemDoesNotExist
234 }
235
236 if result != nil {
237 if let dictionaryArray = result as? [Dictionary<CFString, Any>] {
238 keySet = dictionaryArray
239 } else {
240 if let dictionary = result as? Dictionary<CFString, Any> {
241 keySet = [dictionary]
242 } else {
243 keySet = nil
244 }
245 }
246 }
247 return keySet
248 }
249
250 class func findRecoveryKeysForLabel(label: String) throws -> (_SFECKeyPair?, _SFECKeyPair?) {
251 var signingKey: _SFECKeyPair?
252 var encryptionKey: _SFECKeyPair?
253
254 let keySet = try retrieveRecoveryKeysFromKeychain(label: label)
255 if keySet == nil {
256 throw RecoveryKeySetError.itemDoesNotExist
257 }
258 for item in keySet! {
259 let keyTypeData = item[kSecAttrApplicationLabel as CFString] as! Data
260 let keyType = String(data: keyTypeData, encoding: .utf8)!
261
262 if keyType.range(of: "Encryption") != nil {
263 let keyData = item[kSecValueData as CFString] as! Data
264 let encryptionSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
265 encryptionKey = _SFECKeyPair.init(secKey: encryptionSecKey)
266 } else if keyType.range(of: "Signing") != nil {
267 let keyData = item[kSecValueData as CFString] as! Data
268 let signingSecKey = try RecoveryKeySet.createSecKey(keyData: keyData)
269 signingKey = _SFECKeyPair.init(secKey: signingSecKey)
270 } else {
271 throw RecoveryKeySetError.unsupportedKeyType(keyType: keyType)
272 }
273 }
274
275 return (signingKey, encryptionKey)
276 }
277 }
278
279 enum RecoveryKeySetError: Error {
280 case keyGeneration
281 case itemDoesNotExist
282 case failedToSaveToKeychain(errorCode: OSStatus)
283 case unsupportedKeyType(keyType: String)
284 case corecryptoKeyGeneration(corecryptoError: Int32)
285 }
286
287 extension RecoveryKeySetError: LocalizedError {
288 public var errorDescription: String? {
289 switch self {
290 case .keyGeneration:
291 return "Key generation failed"
292 case .itemDoesNotExist:
293 return "Item does not exist"
294 case .failedToSaveToKeychain(errorCode: let osError):
295 return "Failed to save item to keychain: \(osError)"
296 case .unsupportedKeyType(keyType: let keyType):
297 return "Unsupported Key Type \(keyType)"
298 case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
299 return "Key generation crypto failed \(corecryptoError)"
300 }
301 }
302 }
303
304 extension RecoveryKeySetError: CustomNSError {
305
306 public static var errorDomain: String {
307 return "com.apple.security.trustedpeers.RecoveryKeySetError"
308 }
309
310 public var errorCode: Int {
311 switch self {
312 case .keyGeneration:
313 return 1
314 case .itemDoesNotExist:
315 return 2
316 case .failedToSaveToKeychain:
317 return 3
318 case .unsupportedKeyType:
319 return 4
320 case .corecryptoKeyGeneration:
321 return 5
322 }
323 }
324
325 public var errorUserInfo: [String: Any] {
326 var userInfo: [String: Any] = [:]
327 if let desc = self.errorDescription {
328 userInfo[NSLocalizedDescriptionKey] = desc
329 }
330 switch self {
331 case .failedToSaveToKeychain(errorCode: let osError):
332 userInfo[NSUnderlyingErrorKey] = NSError(domain: NSOSStatusErrorDomain, code: Int(osError), userInfo: nil)
333 case .corecryptoKeyGeneration(corecryptoError: let corecryptoError):
334 userInfo[NSUnderlyingErrorKey] = NSError(domain: "corecrypto", code: Int(corecryptoError), userInfo: nil)
335 default:
336 break
337 }
338 return userInfo
339 }
340 }