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