]> git.saurik.com Git - apple/security.git/blob - keychain/securityd/SecDbBackupManager.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / securityd / SecDbBackupManager.m
1 /*
2 * Copyright (c) 2018 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 "SecDbBackupManager.h"
25 #import "sec_action.h"
26
27 NSString* const KeychainBackupsErrorDomain = @"com.apple.security.keychain.backups";
28
29 // oink oink
30 @implementation SecDbBackupWrappedKey
31 + (BOOL)supportsSecureCoding {
32 return YES;
33 }
34 - (void)encodeWithCoder:(nonnull NSCoder *)coder {
35 [coder encodeObject:self.wrappedKey forKey:@"wrappedKey"];
36 [coder encodeObject:self.baguuid forKey:@"baguuid"];
37 }
38
39 - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
40 if (self = [super init]) {
41 _wrappedKey = [coder decodeObjectOfClass:[NSData class] forKey:@"wrappedKey"];
42 _baguuid = [coder decodeObjectOfClass:[NSData class] forKey:@"baguuid"];
43 }
44 return self;
45 }
46 @end
47
48 #if SECDB_BACKUPS_ENABLED // The bottom of this file contains stubs in case the feature is disabled
49
50 #import "SecDbBackupManager_Internal.h"
51 #include <CommonCrypto/CommonRandom.h>
52 #import <Foundation/NSData_Private.h>
53 #import "SecItemServer.h"
54 #import "SecItemDb.h"
55 #import "keychain/categories/NSError+UsefulConstructors.h"
56 #import "sec_action.h"
57 #import "SecItemServer.h"
58 #include "utilities/der_plist.h"
59
60 // TODO: fire off metric on outcome
61 bool SecDbBackupCreateOrLoadBackupInfrastructure(CFErrorRef* error)
62 {
63 NSError* localError;
64 bool ok = [[SecDbBackupManager manager] createOrLoadBackupInfrastructure:&localError];
65 if (!ok) {
66 // Translate this to intelligible constant in C, but other errors can be passed along
67 if (localError.code == SecDbBackupKeychainLocked) {
68 *error = CFErrorCreate(kCFAllocatorDefault, kSecErrorDomain, errSecInteractionNotAllowed, NULL);
69 } else {
70 *error = (CFErrorRef)CFBridgingRetain(localError);
71 }
72 }
73 return ok;
74 }
75
76 void SecDbResetBackupManager() {
77 secnotice("SecDbBackup", "Resetting backup manager");
78 [SecDbBackupManager resetManager];
79 }
80
81 // Reading from disk is relatively expensive. Keep wrapped key in memory and just delete the unwrapped copy on lock
82 @interface InMemoryKCSK : NSObject
83 @property aks_ref_key_t refKey;
84 @property (nonatomic) NSData* wrappedKey;
85 @property (nonatomic) SFECKeyPair* key;
86 @end
87
88 @implementation InMemoryKCSK
89 - (void)dealloc
90 {
91 if (_refKey) {
92 free(_refKey);
93 }
94 }
95
96 + (instancetype)kcskWithRefKey:(aks_ref_key_t)refKey wrappedKey:(NSData*)wrappedKey key:(SFECKeyPair*)key
97 {
98 InMemoryKCSK* kcsk = [InMemoryKCSK new];
99 kcsk.refKey = refKey;
100 kcsk.wrappedKey = wrappedKey;
101 kcsk.key = key;
102 return kcsk;
103 }
104
105 @end
106
107 @interface SecDbBackupManager () {
108 dispatch_queue_t _queue;
109 keybag_handle_t _handle;
110 SecDbBackupBagIdentity* _bagIdentity;
111 NSMutableDictionary<NSNumber*, InMemoryKCSK*>* _cachedKCSKs;
112 }
113 @end
114
115 @implementation SecDbBackupManager
116
117 #pragma mark - Misc Helpers
118
119 - (NSData*)getSHA256OfData:(NSData*)data
120 {
121 NSMutableData* digest = [[NSMutableData alloc] initWithLength:CC_SHA512_DIGEST_LENGTH];
122 if (!CC_SHA512(data.bytes, (CC_LONG)data.length, digest.mutableBytes)) {
123 return nil;
124 }
125 return digest;
126 }
127
128 - (void)setBagIdentity:(SecDbBackupBagIdentity *)bagIdentity
129 {
130 _bagIdentity = bagIdentity;
131 }
132
133 - (SecDbBackupBagIdentity*)bagIdentity
134 {
135 return _bagIdentity;
136 }
137
138 - (bool)fillError:(NSError**)error code:(enum SecDbBackupErrorCode)code underlying:(NSError*)underlying description:(NSString*)format, ... NS_FORMAT_FUNCTION(4, 5)
139 {
140 if (error) {
141 va_list ap;
142 va_start(ap, format);
143 NSString* desc = [[NSString alloc] initWithFormat:format arguments:ap];
144 va_end(ap);
145 if (underlying) {
146 *error = [NSError errorWithDomain:KeychainBackupsErrorDomain code:code description:desc underlying:underlying];
147 } else {
148 *error = [NSError errorWithDomain:KeychainBackupsErrorDomain code:code description:desc];
149 }
150 }
151
152 // analyzer gets upset when a method taking an error** doesn't return a value
153 return true;
154 }
155
156 static SecDbBackupManager* staticManager;
157 + (instancetype)manager
158 {
159 static dispatch_once_t onceToken;
160 dispatch_once(&onceToken, ^{
161 staticManager = [SecDbBackupManager new];
162 });
163 return staticManager;
164 }
165
166 // Testing only please
167 + (void)resetManager
168 {
169 if (staticManager) {
170 secnotice("SecDbBackup", "Resetting backup manager");
171 staticManager = [[SecDbBackupManager alloc] init];
172 }
173 }
174
175 - (instancetype)init
176 {
177 if (!checkV12DevEnabled()) {
178 return nil;
179 }
180 if (self = [super init]) {
181 _queue = dispatch_queue_create("com.apple.security.secdbbackupmanager", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
182 _handle = bad_keybag_handle;
183 _cachedKCSKs = [NSMutableDictionary new];
184 }
185 return self;
186 }
187
188 - (SFECKeyPair*)getECKeyPairFromDERBytes:(void*)bytes length:(size_t)len error:(NSError**)error
189 {
190 if (!bytes || len == 0) {
191 [self fillError:error code:SecDbBackupInvalidArgument underlying:nil description:@"Need valid byte buffer to make EC keypair from"];
192 return nil;
193 }
194 CFTypeRef cftype = NULL;
195 CFErrorRef cferr = NULL;
196 const uint8_t* derp = der_decode_plist(kCFAllocatorDefault, &cftype, &cferr, bytes, bytes + len);
197 free(bytes);
198 if (derp == NULL || derp != (bytes + len) || cftype == NULL) {
199 [self fillError:error code:SecDbBackupMalformedKCSKDataOnDisk underlying:CFBridgingRelease(cferr) description:@"Unable to parse der data"];
200 return nil;
201 }
202
203 return [[SFECKeyPair alloc] initWithData:(__bridge_transfer NSData*)cftype
204 specifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]
205 error:error];
206 }
207
208 - (NSData*)currentBackupBagUUID
209 {
210 return [_bagIdentity.baguuid copy];
211 }
212
213 #pragma mark - Fixup And Verification
214
215 - (void)verifyBackupIntegrity:(bool)lightweight
216 completion:(void (^)(NSDictionary<NSString*, NSString*>* _Nonnull, NSError * _Nullable))completion
217 {
218 NSError* error = nil;
219 completion(@{@"summary" : @"Unimplemented"}, error);
220 }
221
222 #pragma mark - Backup Bag Management
223
224 // Get the bag's UUID from AKS and the hash from provided data. This must always be the original bag's data
225 - (SecDbBackupBagIdentity*)bagIdentityWithHandle:(keybag_handle_t)handle data:(NSData*)data error:(NSError**)error {
226 assert(data);
227 secnotice("SecDbBackup", "Getting bag identity");
228 SecDbBackupBagIdentity* identity = [SecDbBackupBagIdentity new];
229
230 uuid_t uuid = {0};
231 kern_return_t aksResult = aks_get_bag_uuid(handle, uuid);
232 if (aksResult != kAKSReturnSuccess) {
233 [self fillError:error code:SecDbBackupAKSFailure underlying:nil description:@"Unable to get keybag UUID (%d)", aksResult];
234 return nil;
235 }
236 identity.baguuid = [NSData dataWithBytes:uuid length:16];
237
238 NSData* digest = [self getSHA256OfData:data];
239 if (!digest) {
240 [self fillError:error code:SecDbBackupCryptoFailure underlying:nil description:@"CC_SHA512 returned failure, can't get bag hash"];
241 return nil;
242 }
243 identity.baghash = digest;
244
245 secnotice("SecDbBackup", "Obtained bag identity: %@", identity);
246
247 return identity;
248 }
249
250 - (NSData*)createBackupBagSecret:(NSError**)error
251 {
252 uint8_t* data = calloc(1, BACKUPBAG_PASSPHRASE_LENGTH);
253 if (!data) {
254 return nil; // Good luck allocating an error message
255 }
256
257 CCRNGStatus rngResult = CCRandomGenerateBytes(data, BACKUPBAG_PASSPHRASE_LENGTH);
258 if (rngResult != kCCSuccess) {
259 [self fillError:error code:SecDbBackupCryptoFailure underlying:nil description:@"Unable to generate random bytes (%d)", rngResult];
260 return nil;
261 }
262
263 NSData* secret = [NSData _newZeroingDataWithBytesNoCopy:data length:BACKUPBAG_PASSPHRASE_LENGTH deallocator:NSDataDeallocatorNone];
264 return secret;
265 }
266
267 - (keybag_handle_t)onQueueCreateBackupBagWithSecret:(NSData*)secret error:(NSError**)error
268 {
269 dispatch_assert_queue(_queue);
270
271 keybag_handle_t handle = bad_keybag_handle;
272 kern_return_t aksresult = aks_create_bag(secret.bytes, BACKUPBAG_PASSPHRASE_LENGTH, kAppleKeyStoreAsymmetricBackupBag, &handle);
273 if (aksresult != kAKSReturnSuccess) {
274 [self fillError:error code:SecDbBackupAKSFailure underlying:nil description:@"Unable to create keybag (%d)", aksresult];
275 return bad_keybag_handle;
276 }
277
278 // Make secret keys unavailable. Causes pubkeys to be destroyed so reload bag before use
279 aksresult = aks_lock_bag(handle);
280 if (aksresult != kAKSReturnSuccess) { // This would be rather surprising
281 [self fillError:error code:SecDbBackupAKSFailure underlying:nil description:@"Unable to lock keybag (%d)", aksresult];
282 aks_unload_bag(handle);
283 return bad_keybag_handle;
284 }
285
286 return handle;
287 }
288
289 - (BOOL)onQueueInTransaction:(SecDbConnectionRef)dbt saveBackupBag:(keybag_handle_t)handle asDefault:(BOOL)asDefault error:(NSError**)error
290 {
291 dispatch_assert_queue(_queue);
292
293 void* buf = NULL;
294 int buflen = 0;
295 kern_return_t aksResult = aks_save_bag(handle, &buf, &buflen);
296 if (aksResult != kAKSReturnSuccess) {
297 [self fillError:error code:SecDbBackupAKSFailure underlying:nil description:@"Unable to serialize keybag (%d)", aksResult];
298 return NO;
299 }
300 NSData* bagData = [NSData dataWithBytesNoCopy:buf length:buflen];
301
302 SecDbBackupBagIdentity* bagIdentity = [self bagIdentityWithHandle:handle data:bagData error:error];
303 if (!bagIdentity) {
304 return NO;
305 }
306 SecDbBackupBag* bag = [SecDbBackupBag new];
307 bag.bagIdentity = bagIdentity;
308 bag.keybag = bagData;
309
310 __block CFErrorRef cfError = NULL;
311 __block bool ok = true;
312 if (asDefault) {
313 ok &= SecDbPrepare(dbt, CFSTR("UPDATE backupbags SET defaultvalue = 0 WHERE defaultvalue = 1"), &cfError, ^(sqlite3_stmt *stmt) {
314 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
315 // FIXME: Should this be an error? Should something else happen?
316 secwarning("SecDbBackup: Marking existing bag as non-default");
317 });
318 });
319 }
320 if (!ok) {
321 return ok;
322 }
323 ok &= SecDbPrepare(dbt, CFSTR("INSERT INTO backupbags (backupUUID, backupbag, defaultvalue) VALUES (?,?,?)"), &cfError, ^(sqlite3_stmt *stmt) {
324 ok &= SecDbBindObject(stmt, 1, (__bridge CFDataRef)bag.bagIdentity.baguuid, &cfError);
325 ok &= SecDbBindObject(stmt, 2, (__bridge CFDataRef)bag.data, &cfError);
326 ok &= SecDbBindInt(stmt, 3, asDefault ? 1 : 0, &cfError);
327 ok &= SecDbStep(dbt, stmt, &cfError, NULL);
328 });
329
330 if (!ok) {
331 secerror("SecDbBackup: unable to save keybag to disk: %@", cfError);
332 [self fillError:error code:SecDbBackupWriteFailure underlying:CFBridgingRelease(cfError) description:@"Unable to save keybag to disk"];
333 }
334
335 return ok;
336 }
337
338 - (keybag_handle_t)onQueueLoadBackupBag:(NSUUID*)uuid error:(NSError**)error {
339 dispatch_assert_queue(_queue);
340
341 secnotice("SecDbBackup", "Attempting to load backup bag from disk");
342
343 __block CFErrorRef localErr = NULL;
344 __block bool ok = true;
345 __block NSData* readUUID;
346 __block NSData* readBagData;
347 __block unsigned found = 0;
348 ok &= kc_with_dbt_non_item_tables(false, &localErr, ^bool(SecDbConnectionRef dbt) {
349 NSString* sql = [NSString stringWithFormat:@"SELECT backupUUID, backupbag FROM backupbags WHERE %@", uuid ? @"backupUUID = ?" : @"defaultvalue = 1"];
350 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &localErr, ^(sqlite3_stmt *stmt) {
351 if (uuid) {
352 unsigned char uuidbytes[UUIDBYTESLENGTH] = {0};
353 [uuid getUUIDBytes:uuidbytes];
354 ok &= SecDbBindBlob(stmt, 1, uuidbytes, UUIDBYTESLENGTH, SQLITE_TRANSIENT, &localErr);
355 }
356 ok &= SecDbStep(dbt, stmt, &localErr, ^(bool *stop) {
357 if (found > 0) { // For uuids this should have violated constraints
358 secerror("Encountered more than one backup bag by %@", uuid ? @"backupUUID" : @"defaultvalue");
359 *stop = true;
360 }
361 readUUID = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
362 readBagData = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 1) length:sqlite3_column_bytes(stmt, 1)];
363 ++found;
364 });
365 });
366 return ok;
367 });
368
369 if (!ok) {
370 secerror("SecDbBackup: Unable to load backup bag from disk: %@", localErr);
371 [self fillError:error code:SecDbBackupWriteFailure underlying:CFBridgingRelease(localErr) description:@"Unable to load backup bag from disk"];
372 return bad_keybag_handle;
373 }
374
375 if (!found) {
376 [self fillError:error code:SecDbBackupNoBackupBagFound underlying:nil description:@"No backup bag found to load from disk"];
377 return bad_keybag_handle;
378 } else if (found > 1) {
379 [self fillError:error
380 code:uuid ? SecDbBackupDuplicateBagFound : SecDbBackupMultipleDefaultBagsFound
381 underlying:nil
382 description:@"More than one backup bag found"];
383 return bad_keybag_handle;
384 }
385
386 if (!readUUID || readUUID.length != UUIDBYTESLENGTH || !readBagData || !readBagData.length) {
387 [self fillError:error code:SecDbBackupMalformedBagDataOnDisk underlying:nil description:@"bags read from disk malformed"];
388 return bad_keybag_handle;
389 }
390
391 SecDbBackupBag* readBag = [[SecDbBackupBag alloc] initWithData:readBagData];
392 if (!readBag) {
393 [self fillError:error code:SecDbBackupDeserializationFailure underlying:nil description:@"bag from disk does not deserialize"];
394 return bad_keybag_handle;
395 }
396
397 secnotice("SecDbBackup", "Successfully read backup bag from disk; loading and verifying. Read bag ID: %@", readBag.bagIdentity);
398
399 keybag_handle_t handle = bad_keybag_handle;
400 kern_return_t aksResult = aks_load_bag(readBag.keybag.bytes, (int)readBag.keybag.length, &handle);
401 if (aksResult != kAKSReturnSuccess) {
402 [self fillError:error code:SecDbBackupAKSFailure underlying:nil description:@"Unable to load bag from disk (%d)", aksResult];
403 return bad_keybag_handle;
404 }
405
406 SecDbBackupBagIdentity* loadedID = [self bagIdentityWithHandle:handle data:readBag.keybag error:error];
407 if (!loadedID) {
408 aks_unload_bag(handle);
409 return bad_keybag_handle;
410 }
411
412 if (memcmp(loadedID.baguuid.bytes, readBag.bagIdentity.baguuid.bytes, UUIDBYTESLENGTH) ||
413 memcmp(loadedID.baguuid.bytes, readUUID.bytes, UUIDBYTESLENGTH)) {
414 [self fillError:error code:SecDbBackupUUIDMismatch underlying:nil description:@"Loaded UUID does not match UUIDs on disk"];
415 aks_unload_bag(handle);
416 return bad_keybag_handle;
417 }
418
419 if (memcmp(loadedID.baghash.bytes, readBag.bagIdentity.baghash.bytes, CC_SHA512_DIGEST_LENGTH)) {
420 [self fillError:error code:SecDbBackupDeserializationFailure underlying:nil description:@"Keybag hash does not match its identity's hash"];
421 return bad_keybag_handle;
422 }
423
424 secnotice("SecDbBackup", "Backup bag loaded and verified.");
425
426 // Must load readBag's identity because the hash from AKS is unstable.
427 // This is the hash of the original saved bag and is anchored in the KCSKes.
428 _bagIdentity = readBag.bagIdentity;
429
430 return handle;
431 }
432
433 - (BOOL)onQueueReloadDefaultBackupBagWithError:(NSError**)error
434 {
435 dispatch_assert_queue(_queue);
436 if (_handle != bad_keybag_handle) {
437 aks_unload_bag(_handle);
438 }
439
440 _handle = [self onQueueLoadBackupBag:nil error:error];
441 return _handle != bad_keybag_handle;
442 }
443
444 #pragma mark - KCSK Management
445
446 - (SecDbBackupKeyClassSigningKey*)createKCSKForKeyClass:(keyclass_t)class withWrapper:(SFAESKey*)wrapper error:(NSError**)error
447 {
448 if (!wrapper) {
449 [self fillError:error code:SecDbBackupInvalidArgument underlying:nil description:@"Need wrapper for KCSK"];
450 return nil;
451 }
452
453 SecDbBackupKeyClassSigningKey* kcsk = [SecDbBackupKeyClassSigningKey new];
454 kcsk.keyClass = class;
455
456 SFECKeyPair* keypair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
457 kcsk.publicKey = [keypair.publicKey.keyData copy];
458
459 // Create a DER-encoded dictionary of bag identity
460 void* der_blob;
461 size_t der_len;
462 CFErrorRef cfErr = NULL;
463 NSDictionary* identDict = @{@"baguuid" : _bagIdentity.baguuid, @"baghash" : _bagIdentity.baghash};
464 NSData* identData = (__bridge_transfer NSData*)CFPropertyListCreateDERData(NULL, (__bridge CFDictionaryRef)identDict, &cfErr);
465 aks_operation_optional_params(NULL, 0, identData.bytes, identData.length, NULL, 0, &der_blob, &der_len);
466
467 // Create ref key with embedded bag identity DER data
468 aks_ref_key_t refkey = NULL;
469 kern_return_t aksResult = aks_ref_key_create(KEYBAG_DEVICE, class, key_type_sym, der_blob, der_len, &refkey);
470 free(der_blob);
471 if (aksResult != kAKSReturnSuccess) {
472 [self fillError:error code:SecDbBackupAKSFailure underlying:nil
473 description:@"Unable to create AKS ref key for KCSK class %d: %d", class, aksResult];
474 return nil;
475 }
476
477 size_t refkeyblobsize = 0;
478 const uint8_t* refkeyblob = aks_ref_key_get_blob(refkey, &refkeyblobsize);
479 kcsk.aksRefKey = [NSData dataWithBytes:refkeyblob length:refkeyblobsize];
480
481 size_t wrappedKeyLen = 0;
482 void* wrappedKey = NULL;
483 NSData* keypairAsData = keypair.keyData;
484 aksResult = aks_ref_key_encrypt(refkey, NULL, 0, keypairAsData.bytes, keypairAsData.length, &wrappedKey, &wrappedKeyLen);
485 if (aksResult != kAKSReturnSuccess) {
486 [self fillError:error code:SecDbBackupAKSFailure underlying:nil
487 description:@"Unable to encrypt KCSK class %d with AKS ref key: %d", class, aksResult];
488 return nil;
489 }
490 aks_ref_key_free(&refkey);
491 kcsk.aksWrappedKey = [NSData dataWithBytesNoCopy:wrappedKey length:wrappedKeyLen];
492
493 // Also add DER-encoded bag identity here
494 SFAuthenticatedEncryptionOperation* op = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256]];
495 SFAuthenticatedCiphertext* backupwrapped = [op encrypt:keypair.keyData withKey:wrapper additionalAuthenticatedData:identData error:error];
496 kcsk.backupWrappedKey = [NSKeyedArchiver archivedDataWithRootObject:backupwrapped requiringSecureCoding:YES error:error];
497 if (!kcsk.backupWrappedKey) {
498 return nil;
499 }
500
501 return kcsk;
502 }
503
504 - (BOOL)inTransaction:(SecDbConnectionRef)dbt writeKCSKToKeychain:(SecDbBackupKeyClassSigningKey*)kcsk error:(NSError**)error
505 {
506 __block bool ok = true;
507 __block CFErrorRef cfError = NULL;
508 NSString* sql = @"INSERT INTO backupkeyclasssigningkeys (keyclass, backupUUID, signingkey) VALUES (?, ?, ?)";
509 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
510 ok &= SecDbBindInt(stmt, 1, kcsk.keyClass, &cfError);
511 ok &= SecDbBindObject(stmt, 2, (__bridge CFTypeRef)(self->_bagIdentity.baguuid), &cfError);
512 ok &= SecDbBindObject(stmt, 3, (__bridge CFTypeRef)(kcsk.data), &cfError);
513 ok &= SecDbStep(dbt, stmt, &cfError, NULL);
514 });
515
516 if (!ok) {
517 secerror("SecDbBackup: Unable to write KCSK for class %d to keychain: %@", kcsk.keyClass, cfError);
518 [self fillError:error code:SecDbBackupWriteFailure underlying:CFBridgingRelease(cfError) description:@"Unable to write KCSK for class %d to keychain", kcsk.keyClass];
519 }
520
521 return ok;
522 }
523
524 - (InMemoryKCSK*)onQueueReadKCSKFromDiskForClass:(keyclass_t)keyclass error:(NSError**)error
525 {
526 dispatch_assert_queue(_queue);
527 __block bool ok = true;
528 __block CFErrorRef cfError = NULL;
529 __block NSData* readUUID;
530 __block NSData* readKCSK;
531 NSString* sql = @"SELECT backupUUID, signingkey FROM backupkeyclasssigningkeys WHERE keyclass = ?";
532 ok &= kc_with_dbt_non_item_tables(NO, &cfError, ^bool(SecDbConnectionRef dbt) {
533 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
534 ok &= SecDbBindInt(stmt, 1, keyclass, &cfError);
535 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) {
536 readUUID = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)];
537 readKCSK = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 1) length:sqlite3_column_bytes(stmt, 1)];
538 });
539 });
540 return ok;
541 });
542 if (!readKCSK || !readUUID) {
543 [self fillError:error code:SecDbBackupNoKCSKFound underlying:nil description:@"KCSK for class %d not on disk", keyclass];
544 return nil;
545 }
546
547 SecDbBackupKeyClassSigningKey* kcsk = [[SecDbBackupKeyClassSigningKey alloc] initWithData:readKCSK];
548 if (!kcsk) {
549 [self fillError:error code:SecDbBackupMalformedKCSKDataOnDisk underlying:nil description:@"Retrieved KCSK blob but it didn't become a KCSK"];
550 return nil;
551 }
552
553 aks_ref_key_t refkey = NULL;
554 kern_return_t aksResult = aks_ref_key_create_with_blob(KEYBAG_DEVICE, kcsk.aksRefKey.bytes, kcsk.aksRefKey.length, &refkey);
555 if (aksResult != kAKSReturnSuccess) {
556 [self fillError:error
557 code:SecDbBackupAKSFailure
558 underlying:nil
559 description:@"Failed to create refkey from KCSK blob for class %d: %d", keyclass, aksResult];
560 return nil;
561 }
562
563 size_t externalDataLen = 0;
564 const uint8_t* externalData = aks_ref_key_get_external_data(refkey, &externalDataLen);
565 NSData* derBagIdent = [NSData dataWithBytes:externalData length:externalDataLen];
566 CFErrorRef cfErr = NULL;
567 NSDictionary* identData = (__bridge_transfer NSDictionary*)CFPropertyListCreateWithDERData(NULL, (__bridge CFDataRef)derBagIdent, 0, NULL, &cfErr);
568 if (!identData || ![_bagIdentity.baghash isEqualToData:identData[@"baghash"]] || ![_bagIdentity.baguuid isEqualToData:identData[@"baguuid"]]) {
569 secerror("SecDbBackup: KCSK ref key embedded bag identity does not match loaded bag. %@ vs %@", identData, _bagIdentity);
570 [self fillError:error code:SecDbBackupMalformedKCSKDataOnDisk underlying:CFBridgingRelease(cfErr) description:@"KCSK ref key embedded bag identity does not match loaded bag."];
571 return nil;
572 }
573
574 // AKS refkey claims in its external data to belong to our backup bag. Let's see if the claim holds up: use the key.
575 void* keypairBytes = NULL;
576 size_t keypairLength = 0;
577 aksResult = aks_ref_key_decrypt(refkey, NULL, 0, kcsk.aksWrappedKey.bytes, kcsk.aksWrappedKey.length, &keypairBytes, &keypairLength);
578 if (aksResult == kSKSReturnNoPermission) {
579 [self fillError:error code:SecDbBackupKeychainLocked underlying:nil description:@"Unable to unwrap KCSK private key for class %d. Locked", keyclass];
580 return nil;
581 } else if (aksResult != kAKSReturnSuccess) {
582 // Failure could indicate key was corrupted or tampered with
583 [self fillError:error
584 code:SecDbBackupAKSFailure
585 underlying:nil
586 description:@"AKS did not unwrap KCSK private key for class %d: %d", keyclass, aksResult];
587 return nil;
588 }
589
590 SFECKeyPair* keypair = [self getECKeyPairFromDERBytes:keypairBytes length:keypairLength error:error];
591 return keypair ? [InMemoryKCSK kcskWithRefKey:refkey wrappedKey:kcsk.aksWrappedKey key:keypair] : nil;
592 }
593
594 - (SFECKeyPair*)onQueueFetchKCSKForKeyclass:(keyclass_t)keyclass error:(NSError**)error
595 {
596 dispatch_assert_queue(_queue);
597 assert(error);
598 assert(_bagIdentity);
599 assert(_handle != bad_keybag_handle);
600
601 InMemoryKCSK* cached = _cachedKCSKs[@(keyclass)];
602 if (cached.key) {
603 return cached.key;
604 }
605
606 if (cached) {
607 secnotice("SecDbBackup", "Cached but wrapped KCSK found for class %d, unwrapping", keyclass);
608 void* keybytes = NULL;
609 size_t keylen = 0;
610 kern_return_t aksResult = aks_ref_key_decrypt(cached.refKey, NULL, 0, cached.wrappedKey.bytes, cached.wrappedKey.length, &keybytes, &keylen);
611 if (aksResult == kAKSReturnSuccess) {
612 cached.key = [self getECKeyPairFromDERBytes:keybytes length:keylen error:error];
613 return cached.key;
614 } else {
615 secerror("SecDbBackup: Cached KCSK isn't unwrapping key material. This is a bug.");
616 }
617 }
618
619 secnotice("SecDbBackup", "No cached KCSK for class %d, reading from disk", keyclass);
620 cached = [self onQueueReadKCSKFromDiskForClass:keyclass error:error];
621 if (!cached.key) {
622 secerror("SecDbBackup: Failed to obtain KCSK for class %d: %@", keyclass, *error);
623 if ((*error).code != SecDbBackupKeychainLocked) {
624 seccritical("SecDbBackup: KCSK unavailable, cannot backup-wrap class %d items. Need to perform recovery.", keyclass);
625 // TODO: We're borked. Need to recover from this.
626 }
627 }
628 _cachedKCSKs[@(keyclass)] = cached;
629 return cached.key;
630 }
631
632 #pragma mark - Recovery Set Management
633
634 - (BOOL)onQueueInTransaction:(SecDbConnectionRef)dbt writeRecoverySetToKeychain:(SecDbBackupRecoverySet*)set error:(NSError**)error
635 {
636 dispatch_assert_queue(_queue);
637 __block bool ok = true;
638 __block CFErrorRef cfError = NULL;
639
640 NSString* sql = @"INSERT INTO backuprecoverysets (backupUUID, recoverytype, recoveryset) VALUES (?, ?, ?)";
641 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) {
642 ok &= SecDbBindObject(stmt, 1, (__bridge CFDataRef)set.bagIdentity.baguuid, &cfError);
643 ok &= SecDbBindObject(stmt, 2, (__bridge CFNumberRef)@(set.recoveryType), &cfError);
644 ok &= SecDbBindObject(stmt, 3, (__bridge CFDataRef)set.data, &cfError);
645 ok &= SecDbStep(dbt, stmt, &cfError, NULL);
646 });
647
648 if (!ok) {
649 secerror("SecDbBackup: Unable to write recovery set to keychain: %@", cfError);
650 [self fillError:error code:SecDbBackupWriteFailure underlying:CFBridgingRelease(cfError) description:@"Unable to write recovery set to keychain"];
651 }
652
653 return ok;
654 }
655
656 - (SecDbBackupRecoverySet*)onQueueInTransaction:(SecDbConnectionRef)dbt createRecoverySetWithBagSecret:(NSData*)secret
657 forType:(SecDbBackupRecoveryType)type error:(NSError**)error
658 {
659 dispatch_assert_queue(_queue);
660 if (!secret) {
661 [self fillError:error code:SecDbBackupInvalidArgument underlying:nil description:@"Can't create recovery set without secret"];
662 return nil;
663 }
664
665 SecDbBackupRecoverySet* set;
666 switch (type) {
667 case SecDbBackupRecoveryTypeAKS:
668 set = [self inTransaction:dbt createAKSTypeRecoverySetWithBagSecret:secret handle:_handle error:error];
669 break;
670 case SecDbBackupRecoveryTypeCylon:
671 secerror("SecDbBackup: Cylon recovery type not yet implemented");
672 [self fillError:error code:SecDbBackupUnknownOption underlying:nil description:@"Recovery type Cylon not yet implemented"];
673 break;
674 case SecDbBackupRecoveryTypeRecoveryKey:
675 secerror("SecDbBackup: RecoveryKey recovery type not yet implemented");
676 [self fillError:error code:SecDbBackupUnknownOption underlying:nil description:@"Recovery type RecoveryKey not yet implemented"];
677 break;
678 default:
679 secerror("SecDbBackup: Unknown type %ld", (long)type);
680 [self fillError:error code:SecDbBackupUnknownOption underlying:nil description:@"Recovery type %li unknown", (long)type];
681 break;
682 }
683
684 return set;
685 }
686
687 - (SecDbBackupRecoverySet*)inTransaction:(SecDbConnectionRef)dbt createAKSTypeRecoverySetWithBagSecret:(NSData*)secret handle:(keybag_handle_t)handle error:(NSError**)error
688 {
689 SecDbBackupRecoverySet* set = [SecDbBackupRecoverySet new];
690 set.recoveryType = SecDbBackupRecoveryTypeAKS;
691 set.bagIdentity = _bagIdentity;
692
693 NSError* cryptoError;
694 SFAESKeySpecifier* specifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
695 SFAESKey* KCSKSecret = [[SFAESKey alloc] initRandomKeyWithSpecifier:specifier error:&cryptoError];
696 if (!KCSKSecret) {
697 [self fillError:error code:SecDbBackupCryptoFailure underlying:cryptoError description:@"Unable to create AKS recovery set"];
698 return nil;
699 }
700
701 // We explicitly do NOT want akpu. For the rest, create and write all KCSKs
702 for (NSNumber* class in @[@(key_class_ak), @(key_class_ck), @(key_class_dk), @(key_class_aku), @(key_class_cku), @(key_class_dku)]) {
703 SecDbBackupKeyClassSigningKey* kcsk = [self createKCSKForKeyClass:[class intValue] withWrapper:KCSKSecret error:error];
704 if (!kcsk) {
705 secerror("SecDbBackup: Unable to create KCSK for class %@: %@", class, *error);
706 return nil;
707 }
708 if (![self inTransaction:dbt writeKCSKToKeychain:kcsk error:error]) {
709 secerror("SecDbBackup: Unable to write KCSK for class %@ to keychain: %@", class, *error);
710 return nil;
711 }
712 }
713
714 SFAESKey* recoverykey = [[SFAESKey alloc] initRandomKeyWithSpecifier:specifier error:&cryptoError];
715 if (!recoverykey) {
716 [self fillError:error code:SecDbBackupCryptoFailure underlying:cryptoError description:@"Unable to create recovery key"];
717 return nil;
718 }
719
720 SFAuthenticatedEncryptionOperation* op = [[SFAuthenticatedEncryptionOperation alloc]
721 initWithKeySpecifier:[[SFAESKeySpecifier alloc]
722 initWithBitSize:SFAESKeyBitSize256]];
723 SFAuthenticatedCiphertext* wrappedsecret = [op encrypt:secret withKey:recoverykey error:&cryptoError];
724 if (!wrappedsecret) {
725 secerror("SecDbBackup: Unable to wrap keybag secret: %@", cryptoError);
726 [self fillError:error code:SecDbBackupCryptoFailure underlying:cryptoError description:@"Unable to wrap keybag secret"];
727 return nil;
728 }
729 set.wrappedBagSecret = [NSKeyedArchiver archivedDataWithRootObject:wrappedsecret requiringSecureCoding:YES error:error];
730
731 SFAuthenticatedCiphertext* wrappedkcsksecret = [op encrypt:KCSKSecret.keyData withKey:recoverykey error:&cryptoError];
732 if (!wrappedkcsksecret) {
733 secerror("SecDbBackup: Unable to wrap KCSK secret: %@", cryptoError);
734 [self fillError:error code:SecDbBackupCryptoFailure underlying:cryptoError description:@"Unable to wrap KCSK secret"];
735 return nil;
736 }
737 set.wrappedKCSKSecret = [NSKeyedArchiver archivedDataWithRootObject:wrappedkcsksecret requiringSecureCoding:YES error:error];
738
739 NSMutableData* wrappedrecoverykey = [[NSMutableData alloc] initWithLength:APPLE_KEYSTORE_MAX_SYM_WRAPPED_KEY_LEN];
740 if (![SecAKSObjCWrappers aksEncryptWithKeybag:KEYBAG_DEVICE keyclass:key_class_aku plaintext:recoverykey.keyData outKeyclass:nil ciphertext:wrappedrecoverykey error:&cryptoError]) {
741 secerror("SecDbBackup: Unable to wrap recovery key to AKS: %@", cryptoError);
742 [self fillError:error code:SecDbBackupAKSFailure underlying:cryptoError description:@"Unable to wrap recovery key to AKS"];
743 return nil;
744 }
745 set.wrappedRecoveryKey = [wrappedrecoverykey copy];
746
747 return set;
748 }
749
750 #pragma mark - Backup System Initialization / Maintenance
751
752 - (BOOL)createOrLoadBackupInfrastructure:(NSError**)error {
753 assert(error);
754 __block BOOL ok = true;
755 __block NSError* localError;
756 dispatch_sync(_queue, ^{
757 ok = [self onQueueCreateOrLoadBackupInfrastructure:&localError];
758 });
759
760 if (localError) {
761 *error = localError;
762 }
763 return ok;
764 }
765
766 // TODO: if this creates the infrastructure, kick off a fixup routine
767 // TODO: if not, make sure we actually delete stuff. Nested transactions are not a thing (use checkpointing or delete explicitly)
768
769 - (BOOL)onQueueCreateOrLoadBackupInfrastructure:(NSError**)error {
770 dispatch_assert_queue(_queue);
771 assert(error);
772 if (self->_handle != bad_keybag_handle) {
773 return true;
774 }
775
776 self->_handle = [self onQueueLoadBackupBag:nil error:error];
777 if (self->_handle != bad_keybag_handle) {
778 secnotice("SecDbBackup", "Keybag found and loaded");
779 return true;
780 } else if (self->_handle == bad_keybag_handle && (*error).code != SecDbBackupNoBackupBagFound) {
781 return false;
782 }
783 *error = nil;
784
785 __block BOOL ok = YES;
786 __block CFErrorRef cfError = NULL;
787 __block NSError* localError;
788 secnotice("SecDbBackup", "CreateOrLoad: No backup bag found, attempting to create new infrastructure");
789 if (ok && !SecAKSDoWithUserBagLockAssertion(&cfError, ^{
790 ok &= kc_with_dbt_non_item_tables(YES, &cfError, ^bool(SecDbConnectionRef dbt) {
791 ok &= kc_transaction(dbt, &cfError, ^bool{
792 NSData* secret = [self createBackupBagSecret:&localError];
793 if (!secret) {
794 return false;
795 }
796
797 self->_handle = [self onQueueCreateBackupBagWithSecret:secret error:&localError];
798 if (self->_handle == bad_keybag_handle) {
799 return false;
800 }
801 secnotice("SecDbBackup", "CreateOrLoad: Successfully created backup bag");
802
803 if (![self onQueueInTransaction:dbt saveBackupBag:self->_handle asDefault:YES error:&localError]) {
804 return false;
805 }
806 secnotice("SecDbBackup", "CreateOrLoad: Successfully saved backup bag");
807
808 if (![self onQueueReloadDefaultBackupBagWithError:&localError]) {
809 return false;
810 }
811 secnotice("SecDbBackup", "CreateOrLoad: Successfully reloaded backup bag");
812
813 SecDbBackupRecoverySet* set = [self onQueueInTransaction:dbt
814 createRecoverySetWithBagSecret:secret
815 forType:SecDbBackupRecoveryTypeAKS
816 error:&localError];
817 if (!set) {
818 secnotice("SecDbBackup", "CreateOrLoad: Successfully created recovery set");
819 return false;
820 }
821
822 if (![self onQueueInTransaction:dbt writeRecoverySetToKeychain:set error:&localError]) {
823 return false;
824 }
825 secnotice("SecDbBackup", "CreateOrLoad: Successfully saved recovery set");
826
827 return true;
828 });
829 return ok;
830 });
831 })) { // could not perform action with lock assertion
832 static dispatch_once_t once;
833 static sec_action_t action;
834 dispatch_once(&once, ^{
835 action = sec_action_create("keybag_locked_during_backup_setup_complaint", 5);
836 sec_action_set_handler(action, ^{
837 secerror("SecDbBackup: Cannot obtain AKS lock assertion so cannot setup backup infrastructure");
838 });
839 });
840 sec_action_perform(action);
841 [self fillError:&localError code:SecDbBackupKeychainLocked underlying:nil
842 description:@"Unable to initialize backup infrastructure, keychain locked"];
843 ok = NO;
844 }
845
846 if (!ok) {
847 self->_bagIdentity = nil;
848 aks_unload_bag(self->_handle);
849 self->_handle = bad_keybag_handle;
850 }
851
852 if (ok) {
853 secnotice("SecDbBackup", "Hurray! Successfully created backup infrastructure");
854 } else {
855 assert(localError || cfError);
856 if (localError) {
857 secerror("SecDbBackup: Could not initialize backup infrastructure: %@", localError);
858 *error = localError;
859 } else if (cfError) {
860 secerror("SecDbBackup: Could not initialize backup infrastructure: %@", cfError);
861 [self fillError:error code:SecDbBackupSetupFailure underlying:CFBridgingRelease(cfError)
862 description:@"Unable to initialize backup infrastructure"];
863 } else {
864 secerror("SecDbBackup: Could not initialize backup infrastructure but have no error");
865 [self fillError:error code:SecDbBackupSetupFailure underlying:nil
866 description:@"Unable to initialize backup infrastructure (not sure why)"];
867 }
868 CFReleaseNull(cfError);
869 }
870
871 return ok;
872 }
873
874 #pragma mark - Item Encryption
875
876 - (SecDbBackupWrappedKey*)wrapItemKey:(SFAESKey*)key forKeyclass:(keyclass_t)keyclass error:(NSError**)error
877 {
878 assert(error);
879 // We don't care about rolled keyclasses; we care about semantic classes
880 if (keyclass > key_class_last) {
881 static dispatch_once_t rolledkeyclassnotifytoken;
882 static sec_action_t rolledkeyclassnotifyaction;
883 dispatch_once(&rolledkeyclassnotifytoken, ^{
884 rolledkeyclassnotifyaction = sec_action_create("rolledkeyclassnotifyaction", 300);
885 sec_action_set_handler(rolledkeyclassnotifyaction, ^{
886 secnotice("SecDbBackup", "Rolled keyclass for item wrap, %d -> %d", keyclass, SecAKSSanitizedKeyclass(keyclass));
887 });
888 });
889 sec_action_perform(rolledkeyclassnotifyaction);
890 keyclass = SecAKSSanitizedKeyclass(keyclass);
891 }
892
893 if (keyclass == key_class_akpu) {
894 secwarning("SecDbBackup: Don't tempt me Frodo!");
895 [self fillError:error code:SecDbBackupInvalidArgument underlying:nil description:@"Do not call wrapItemKey with class akpu"];
896 return nil;
897 }
898
899 if (![self createOrLoadBackupInfrastructure:error]) {
900 if ((*error).domain != (__bridge NSString*)kSecErrorDomain || (*error).code != errSecInteractionNotAllowed) {
901 secerror("SecDbBackup: Could not create/load backup infrastructure: %@", *error);
902 }
903 return nil;
904 }
905
906 __block SecDbBackupWrappedKey* backupWrappedKey;
907
908 __block NSMutableData* wrappedKey = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_ASYM_WRAPPED_KEY_LEN];
909 __block NSError* localError;
910 dispatch_sync(_queue, ^{
911 if (![self onQueueCreateOrLoadBackupInfrastructure:&localError]) {
912 return;
913 }
914 if (![SecAKSObjCWrappers aksEncryptWithKeybag:self->_handle
915 keyclass:keyclass
916 plaintext:key.keyData
917 outKeyclass:nil
918 ciphertext:wrappedKey
919 error:&localError]) {
920 return;
921 }
922 SFSignedData* wrappedAndSigned = [self onQueueSignData:wrappedKey withKCSKForKeyclass:keyclass error:&localError];
923 if (!wrappedAndSigned) {
924 if (localError.code != SecDbBackupKeychainLocked) {
925 secerror("SecDbBackup: Unable to sign item for class %d: %@", keyclass, localError);
926 return;
927 }
928 }
929 backupWrappedKey = [SecDbBackupWrappedKey new];
930 backupWrappedKey.wrappedKey = [NSKeyedArchiver archivedDataWithRootObject:wrappedAndSigned requiringSecureCoding:YES error:&localError];
931 backupWrappedKey.baguuid = self->_bagIdentity.baguuid;
932 });
933
934 if (localError) {
935 secerror("SecDbBackup: Unable to wrap-and-sign item of class %d: %@", keyclass, localError);
936 *error = localError;
937 return nil;
938 }
939
940 return backupWrappedKey;
941 }
942
943 - (SFSignedData*)onQueueSignData:(NSMutableData*)data withKCSKForKeyclass:(keyclass_t)keyclass error:(NSError**)error
944 {
945 dispatch_assert_queue(_queue);
946 SFECKeyPair* kcsk = [self onQueueFetchKCSKForKeyclass:keyclass error:error];
947 if (!kcsk) {
948 return nil;
949 }
950
951 SFEC_X962SigningOperation* op = [[SFEC_X962SigningOperation alloc] initWithKeySpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
952 return [op sign:data withKey:kcsk error:error];
953 }
954
955 #pragma mark - Metadata Key Encryption
956
957 - (SecDbBackupWrappedKey*)wrapMetadataKey:(_SFAESKey *)key forKeyclass:(keyclass_t)keyclass error:(NSError**)error
958 {
959 // We don't care about rolled keyclasses; we care about semantic classes
960 if (keyclass > key_class_last) {
961 secnotice("SecDbBackup", "Rolled keyclass for metadata wrap, %d -> %d", keyclass, SecAKSSanitizedKeyclass(keyclass));
962 keyclass = SecAKSSanitizedKeyclass(keyclass);
963 }
964 // For now, this is identical to item encryption. This will likely change when ACLs are implemented
965 return [self wrapItemKey:key forKeyclass:keyclass error:error];
966 }
967
968 #pragma mark - Testing Helpers
969
970 - (keybag_handle_t)createBackupBagWithSecret:(NSData*)secret error:(NSError**)error
971 {
972 assert(error);
973 __block keybag_handle_t handle = bad_keybag_handle;
974 __block NSError* localError;
975 dispatch_sync(_queue, ^{
976 handle = [self onQueueCreateBackupBagWithSecret:secret error:&localError];
977 });
978 if (localError) {
979 *error = localError;
980 } else if (handle == bad_keybag_handle) {
981 [self fillError:error code:SecDbBackupTestCodeFailure underlying:nil description:@"Unable to create backup bag, but no reason"];
982 }
983 return handle;
984 }
985
986 - (BOOL)saveBackupBag:(keybag_handle_t)handle asDefault:(BOOL)asDefault error:(NSError**)error
987 {
988 assert(error);
989 __block bool ok = true;
990 __block NSError* localErr;
991 __block CFErrorRef cfError = NULL;
992 dispatch_sync(_queue, ^{
993 ok &= kc_with_dbt_non_item_tables(YES, &cfError, ^bool(SecDbConnectionRef dbt) {
994 ok &= kc_transaction(dbt, &cfError, ^bool{
995 ok &= [self onQueueInTransaction:dbt saveBackupBag:handle asDefault:asDefault error:&localErr];
996 return ok;
997 });
998 return ok;
999 });
1000 });
1001
1002 if (!ok) {
1003 if (cfError) {
1004 [self fillError:error code:SecDbBackupTestCodeFailure underlying:CFBridgingRelease(cfError) description:@"Unable to save keybag to disk"];
1005 } else if (localErr) {
1006 *error = localErr;
1007 } else if (!localErr) {
1008 [self fillError:error code:SecDbBackupTestCodeFailure underlying:nil description:@"Unable to save keybag to disk but who knows why"];
1009 }
1010 }
1011 return ok;
1012 }
1013
1014 - (keybag_handle_t)loadBackupBag:(NSUUID*)uuid error:(NSError**)error {
1015 __block keybag_handle_t handle = bad_keybag_handle;
1016 __block NSError* localError;
1017 dispatch_sync(_queue, ^{
1018 handle = [self onQueueLoadBackupBag:uuid error:&localError];
1019 });
1020 if (error && localError) {
1021 *error = localError;
1022 }
1023 return handle;
1024 }
1025
1026 - (SecDbBackupRecoverySet*)createRecoverySetWithBagSecret:(NSData*)secret forType:(SecDbBackupRecoveryType)type error:(NSError**)error
1027 {
1028 __block SecDbBackupRecoverySet* set;
1029 __block BOOL ok = YES;
1030 __block NSError* localError;
1031 __block CFErrorRef cfError = NULL;
1032 dispatch_sync(_queue, ^{
1033 ok &= kc_with_dbt_non_item_tables(true, &cfError, ^bool(SecDbConnectionRef dbt) {
1034 ok &= kc_transaction(dbt, &cfError, ^bool{
1035 set = [self onQueueInTransaction:dbt createRecoverySetWithBagSecret:secret forType:type error:&localError];
1036 return set != nil;
1037 });
1038 return ok;
1039 });
1040 });
1041 if (error && cfError) {
1042 *error = CFBridgingRelease(cfError);
1043 } else if (error && localError) {
1044 *error = localError;
1045 }
1046 CFReleaseNull(cfError);
1047
1048 return set;
1049 }
1050
1051 - (SFECKeyPair*)fetchKCSKForKeyclass:(keyclass_t)keyclass error:(NSError**)error
1052 {
1053 __block SFECKeyPair* keypair;
1054 __block NSError* localError;
1055 dispatch_sync(_queue, ^{
1056 keypair = [self onQueueFetchKCSKForKeyclass:keyclass error:&localError];
1057 });
1058 if (localError && error) {
1059 *error = localError;
1060 }
1061
1062 return keypair;
1063 }
1064
1065 @end
1066
1067 #else // SECDB_BACKUPS_ENABLED is false
1068 #pragma mark - Stubs for when backups are disabled
1069
1070 @implementation SecDbBackupManager
1071
1072 + (instancetype)manager
1073 {
1074 return nil;
1075 }
1076
1077 - (NSData* _Nullable)currentBackupBagUUID {return nil;}
1078 - (SecDbBackupWrappedKey* _Nullable)wrapItemKey:(id)key forKeyclass:(keyclass_t)keyclass error:(NSError**)error {return nil;}
1079 - (SecDbBackupWrappedKey* _Nullable)wrapMetadataKey:(SFAESKey*)key forKeyclass:(keyclass_t)keyclass error:(NSError**)error {return nil;}
1080 - (void)verifyBackupIntegrity:(bool)lightweight
1081 completion:(void (^)(NSDictionary<NSString*, NSString*>* results, NSError* _Nullable error))completion
1082 {
1083 completion(nil, [NSError errorWithDomain:KeychainBackupsErrorDomain
1084 code:SecDbBackupNotSupported
1085 userInfo:@{NSLocalizedDescriptionKey : @"platform doesn't do backups"}]);
1086 }
1087
1088 @end
1089
1090 bool SecDbBackupCreateOrLoadBackupInfrastructure(CFErrorRef* error) {return true;}
1091 void SecDbResetBackupManager() {}
1092
1093 #endif // SECDB_BACKUPS_ENABLED