SecDbKeychainMetadataKeyStore.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 "SecDbKeychainMetadataKeyStore.h" 25 #import <dispatch/dispatch.h> 26 #import <utilities/SecAKSWrappers.h> 27 #import <notify.h> 28 #import "SecItemServer.h" 29 #import "SecAKSObjCWrappers.h" 30 #import "SecDbBackupManager.h" 31 #import "SecDbKeychainSerializedMetadataKey.h" 32 #include "SecItemDb.h" // kc_transaction 33 #include "CheckV12DevEnabled.h" 34 #import "sec_action.h" 35 36 NS_ASSUME_NONNULL_BEGIN 37 38 static SecDbKeychainMetadataKeyStore*_Nullable sharedStore = nil; 39 static dispatch_queue_t sharedMetadataStoreQueue; 40 static void initializeSharedMetadataStoreQueue(void) { 41 static dispatch_once_t onceToken; 42 dispatch_once(&onceToken, ^{ 43 sharedMetadataStoreQueue = dispatch_queue_create("metadata_store", DISPATCH_QUEUE_SERIAL); 44 }); 45 } 46 47 @interface SecDbKeychainMetadataKeyStore () 48 @property dispatch_queue_t queue; 49 @property NSMutableDictionary* keysDict; 50 @property int keybagNotificationToken; 51 @end 52 53 @implementation SecDbKeychainMetadataKeyStore 54 55 + (void)resetSharedStore 56 { 57 initializeSharedMetadataStoreQueue(); 58 dispatch_sync(sharedMetadataStoreQueue, ^{ 59 if(sharedStore) { 60 [sharedStore dropAllKeys]; 61 } 62 sharedStore = nil; 63 }); 64 } 65 66 + (instancetype)sharedStore 67 { 68 __block SecDbKeychainMetadataKeyStore* ret; 69 initializeSharedMetadataStoreQueue(); 70 dispatch_sync(sharedMetadataStoreQueue, ^{ 71 if(!sharedStore) { 72 sharedStore = [[self alloc] _init]; 73 } 74 75 ret = sharedStore; 76 }); 77 78 return ret; 79 } 80 81 + (bool)cachingEnabled 82 { 83 return true; 84 } 85 86 - (instancetype)_init 87 { 88 if (self = [super init]) { 89 _keysDict = [[NSMutableDictionary alloc] init]; 90 _queue = dispatch_queue_create("SecDbKeychainMetadataKeyStore", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); 91 _keybagNotificationToken = NOTIFY_TOKEN_INVALID; 92 93 __weak __typeof(self) weakSelf = self; 94 notify_register_dispatch(kUserKeybagStateChangeNotification, &_keybagNotificationToken, _queue, ^(int inToken) { 95 bool locked = true; 96 CFErrorRef error = NULL; 97 if (!SecAKSGetIsLocked(&locked, &error)) { 98 secerror("SecDbKeychainMetadataKeyStore: error getting lock state: %@", error); 99 CFReleaseNull(error); 100 } 101 102 if (locked) { 103 [weakSelf _onQueueDropClassAKeys]; 104 } 105 }); 106 } 107 108 return self; 109 } 110 111 - (void)dealloc { 112 if (_keybagNotificationToken != NOTIFY_TOKEN_INVALID) { 113 notify_cancel(_keybagNotificationToken); 114 _keybagNotificationToken = NOTIFY_TOKEN_INVALID; 115 } 116 } 117 118 - (void)dropClassAKeys 119 { 120 dispatch_sync(_queue, ^{ 121 [self _onQueueDropClassAKeys]; 122 }); 123 } 124 125 - (void)_onQueueDropClassAKeys 126 { 127 dispatch_assert_queue(_queue); 128 129 secnotice("SecDbKeychainMetadataKeyStore", "dropping class A metadata keys"); 130 _keysDict[@(key_class_ak)] = nil; 131 _keysDict[@(key_class_aku)] = nil; 132 _keysDict[@(key_class_akpu)] = nil; 133 } 134 135 - (void)dropAllKeys 136 { 137 dispatch_sync(_queue, ^{ 138 [self _onQueueDropAllKeys]; 139 }); 140 } 141 142 - (void)_onQueueDropAllKeys 143 { 144 dispatch_assert_queue(_queue); 145 146 secnotice("SecDbKeychainMetadataKeyStore", "dropping all metadata keys"); 147 [_keysDict removeAllObjects]; 148 } 149 150 // Return SFAESKey and actual keyclass if NSData decrypts, or nil if it does not and populate error 151 - (SFAESKey* _Nullable)validateWrappedKey:(NSData*)wrapped 152 forKeyClass:(keyclass_t)keyclass 153 actualKeyClass:(keyclass_t*)outKeyclass 154 keybag:(keybag_handle_t)keybag 155 keySpecifier:(SFAESKeySpecifier*)specifier 156 error:(NSError**)error 157 { 158 keyclass_t classToUse = keyclass; 159 if (*outKeyclass != key_class_none && *outKeyclass != keyclass) { 160 classToUse = *outKeyclass; 161 } 162 163 NSMutableData* unwrapped = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_KEY_LEN]; 164 SFAESKey* key = NULL; 165 NSError *localError = nil; 166 if ([SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:classToUse ciphertext:wrapped outKeyclass:NULL plaintext:unwrapped error:&localError]) { 167 key = [[SFAESKey alloc] initWithData:unwrapped specifier:specifier error:&localError]; 168 if (!key) { 169 secerror("SecDbKeychainItemV7: AKS decrypted metadata blob for class %d but could not turn it into a key: %@", classToUse, localError); 170 } 171 } 172 #if USE_KEYSTORE 173 else if (classToUse < key_class_last && *outKeyclass == key_class_none) { 174 *outKeyclass = classToUse | (key_class_last + 1); 175 if ([SecAKSObjCWrappers aksDecryptWithKeybag:keybag keyclass:*outKeyclass ciphertext:wrapped outKeyclass:NULL plaintext:unwrapped error:&localError]) { 176 key = [[SFAESKey alloc] initWithData:unwrapped specifier:specifier error:&localError]; 177 } 178 } 179 #endif 180 if (!key) { 181 // Don't be noisy for mundane error 182 if (!([localError.domain isEqualToString:(__bridge NSString*)kSecErrorDomain] && localError.code == errSecInteractionNotAllowed)) { 183 secerror("SecDbKeychainItemV7: Unable to create key from retrieved data: %@", localError); 184 } 185 if (error) { 186 *error = localError; 187 } 188 } 189 190 return key; 191 } 192 193 - (SFAESKey* _Nullable)newKeyForKeyclass:(keyclass_t)keyclass 194 withKeybag:(keybag_handle_t)keybag 195 keySpecifier:(SFAESKeySpecifier*)specifier 196 database:(SecDbConnectionRef)dbt 197 error:(NSError**)error 198 { 199 NSError *localError = nil; 200 SFAESKey* key = [[SFAESKey alloc] initRandomKeyWithSpecifier:specifier error:&localError]; 201 if (!key) { 202 if (error) { 203 *error = localError; 204 } 205 return nil; 206 } 207 return [self writeKey:key ForKeyclass:keyclass withKeybag:keybag keySpecifier:specifier database:dbt error:error]; 208 } 209 210 - (SFAESKey* _Nullable)writeKey:(SFAESKey*)key 211 ForKeyclass:(keyclass_t)keyclass 212 withKeybag:(keybag_handle_t)keybag 213 keySpecifier:(SFAESKeySpecifier*)specifier 214 database:(SecDbConnectionRef)dbt 215 error:(NSError**)error 216 { 217 NSError *localError = nil; 218 dispatch_assert_queue(_queue); 219 220 NSMutableData* wrappedKey = [NSMutableData dataWithLength:APPLE_KEYSTORE_MAX_SYM_WRAPPED_KEY_LEN]; 221 keyclass_t outKeyclass = keyclass; 222 223 if (![SecAKSObjCWrappers aksEncryptWithKeybag:keybag keyclass:keyclass plaintext:key.keyData outKeyclass:&outKeyclass ciphertext:wrappedKey error:&localError]) { 224 secerror("SecDbMetadataKeyStore: Unable to encrypt new metadata key to keybag: %@", localError); 225 if (error) { 226 *error = localError; 227 } 228 return nil; 229 } 230 231 NSData* mdkdatablob; 232 if (checkV12DevEnabled()) { 233 SecDbBackupWrappedKey* backupWrappedKey; 234 if (SecAKSSanitizedKeyclass(keyclass) != key_class_akpu) { 235 backupWrappedKey = [[SecDbBackupManager manager] wrapMetadataKey:key forKeyclass:keyclass error:&localError]; 236 if (!backupWrappedKey) { 237 secerror("SecDbMetadataKeyStore: Unable to encrypt new metadata key to backup infrastructure: %@", localError); 238 if (error) { 239 *error = localError; 240 } 241 return nil; 242 } 243 } 244 245 SecDbKeychainSerializedMetadataKey* metadatakeydata = [SecDbKeychainSerializedMetadataKey new]; 246 metadatakeydata.keyclass = keyclass; 247 metadatakeydata.actualKeyclass = outKeyclass; 248 metadatakeydata.baguuid = backupWrappedKey.baguuid; 249 metadatakeydata.akswrappedkey = wrappedKey; 250 metadatakeydata.backupwrappedkey = backupWrappedKey.wrappedKey; 251 mdkdatablob = [metadatakeydata data]; 252 } 253 254 __block bool ok = true; 255 __block CFErrorRef cfErr = NULL; 256 NSString* sql; 257 if (checkV12DevEnabled()) { 258 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data, metadatakeydata) VALUES (?,?,?,?)"; 259 } else { 260 sql = @"INSERT OR REPLACE INTO metadatakeys (keyclass, actualKeyclass, data) VALUES (?,?,?)"; 261 } 262 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfErr, ^(sqlite3_stmt *stmt) { 263 ok &= SecDbBindObject(stmt, 1, (__bridge CFNumberRef)@(keyclass), &cfErr); 264 ok &= SecDbBindObject(stmt, 2, (__bridge CFNumberRef)@(outKeyclass), &cfErr); 265 if (!checkV12DevEnabled()) { 266 ok &= SecDbBindBlob(stmt, 3, wrappedKey.bytes, wrappedKey.length, SQLITE_TRANSIENT, &cfErr); 267 } else { 268 // Leave stmt param 3 unbound so SQLite will NULL it out 269 ok &= SecDbBindBlob(stmt, 4, mdkdatablob.bytes, mdkdatablob.length, SQLITE_TRANSIENT, &cfErr); 270 } 271 ok &= SecDbStep(dbt, stmt, &cfErr, NULL); 272 }); 273 274 if (!ok) { 275 secerror("Failed to write new metadata key for %d: %@", keyclass, cfErr); 276 BridgeCFErrorToNSErrorOut(error, cfErr); 277 return nil; 278 } 279 280 return key; 281 } 282 283 - (BOOL)readKeyDataForClass:(keyclass_t)keyclass 284 fromDb:(SecDbConnectionRef)dbt 285 actualKeyclass:(keyclass_t*)actualKeyclass 286 oldFormatData:(NSData**)oldFmt 287 newFormatData:(NSData**)newFmt 288 error:(NSError**)error 289 { 290 dispatch_assert_queue(_queue); 291 292 NSString* sql; 293 if (checkV12DevEnabled()) { 294 sql = @"SELECT data, actualKeyclass, metadatakeydata FROM metadatakeys WHERE keyclass = ?"; 295 } else { 296 sql = @"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = ?"; 297 } 298 299 __block NSData* wrappedKey; 300 __block NSData* mdkdatablob; 301 __block bool ok = true; 302 __block bool found = false; 303 __block CFErrorRef cfError = NULL; 304 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &cfError, ^(sqlite3_stmt *stmt) { 305 ok &= SecDbBindObject(stmt, 1, (__bridge CFNumberRef)@(keyclass), &cfError); 306 ok &= SecDbStep(dbt, stmt, &cfError, ^(bool *stop) { 307 wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)]; 308 *actualKeyclass = sqlite3_column_int(stmt, 1); 309 mdkdatablob = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 2) length:sqlite3_column_bytes(stmt, 2)]; 310 found = true; 311 }); 312 }); 313 314 // ok && !found means no error is passed back, which is specifically handled in keyForKeyclass 315 if (!ok || !found) { 316 BridgeCFErrorToNSErrorOut(error, cfError); 317 *actualKeyclass = key_class_none; 318 return NO; 319 } 320 321 *oldFmt = wrappedKey; 322 *newFmt = mdkdatablob; 323 return YES; 324 } 325 326 - (SFAESKey* _Nullable)fetchKeyForClass:(keyclass_t)keyclass 327 fromDb:(SecDbConnectionRef)dbt 328 keybag:(keybag_handle_t)keybag 329 specifier:(SFAESKeySpecifier*)keySpecifier 330 allowWrites:(BOOL)allowWrites 331 error:(NSError**)error 332 { 333 dispatch_assert_queue(_queue); 334 335 NSData* wrappedKey; 336 NSData* mdkdatablob; 337 keyclass_t actualKeyClass = key_class_none; 338 if (![self readKeyDataForClass:keyclass fromDb:dbt actualKeyclass:&actualKeyClass oldFormatData:&wrappedKey newFormatData:&mdkdatablob error:error]) { 339 return nil; 340 } 341 342 // each entry should be either old format or new format. Otherwise this is a bug. 343 if (!(wrappedKey.length == 0 ^ mdkdatablob.length == 0)) { 344 if (error) { 345 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecInternal userInfo:@{NSLocalizedDescriptionKey: @"Metadata key blob both old-world and new-world"}]; 346 } 347 return nil; 348 } 349 350 SFAESKey* key; 351 BOOL rewrite = NO; 352 keyclass_t classFromDisk = key_class_none; 353 if (wrappedKey.length > 0) { // old format read 354 classFromDisk = actualKeyClass; 355 key = [self validateWrappedKey:wrappedKey forKeyClass:keyclass actualKeyClass:&actualKeyClass keybag:keybag keySpecifier:keySpecifier error:error]; 356 if (!key) { 357 return nil; 358 } 359 if (checkV12DevEnabled()) { 360 rewrite = YES; 361 } 362 } else if (mdkdatablob.length > 0) { // new format read 363 SecDbKeychainSerializedMetadataKey* mdkdata = [[SecDbKeychainSerializedMetadataKey alloc] initWithData:mdkdatablob]; 364 if (!mdkdata) { 365 // bad read, key corrupt? 366 if (error) { 367 *error = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"New-format metadata key blob didn't deserialize"}]; 368 } 369 return nil; 370 } 371 372 // Ignore the old-read actualKeyClass and use blob 373 actualKeyClass = mdkdata.actualKeyclass; 374 classFromDisk = mdkdata.actualKeyclass; 375 key = [self validateWrappedKey:mdkdata.akswrappedkey forKeyClass:keyclass actualKeyClass:&actualKeyClass keybag:keybag keySpecifier:keySpecifier error:error]; 376 if (!key) { 377 return nil; 378 } 379 380 if (!mdkdata.backupwrappedkey || ![mdkdata.baguuid isEqual:[[SecDbBackupManager manager] currentBackupBagUUID]]) { 381 rewrite = YES; 382 secnotice("SecDbMetadataKeyStore", "Metadata key for %d has no or mismatching backup data; will rewrite.", keyclass); 383 } 384 385 } else { // Wait, there's a row for this class but not something which might be a key? 386 secnotice("SecDbMetadataKeyStore", "No metadata key found on disk despite existing row. That's odd."); 387 return nil; 388 } 389 390 if (allowWrites && (rewrite || classFromDisk != actualKeyClass)) { 391 NSError* localError; 392 if (![self writeKey:key ForKeyclass:keyclass withKeybag:keybag keySpecifier:keySpecifier database:dbt error:&localError]) { 393 // if this fails we can try again in future 394 secwarning("SecDbMetadataKeyStore: Unable to rewrite metadata key for %d to new format: %@", keyclass, localError); 395 localError = nil; 396 } 397 } 398 399 return key; 400 } 401 402 - (SFAESKey* _Nullable)keyForKeyclass:(keyclass_t)keyclass 403 keybag:(keybag_handle_t)keybag 404 keySpecifier:(SFAESKeySpecifier*)keySpecifier 405 allowWrites:(BOOL)allowWrites 406 error:(NSError**)error 407 { 408 if (!error) { 409 secerror("keyForKeyclass called without error param, this is a bug"); 410 return nil; 411 } 412 413 static __thread BOOL reentrant = NO; 414 NSAssert(!reentrant, @"re-entering -[%@ %@] - that shouldn't happen!", NSStringFromClass(self.class), NSStringFromSelector(_cmd)); 415 reentrant = YES; 416 417 keyclass = SecAKSSanitizedKeyclass(keyclass); 418 419 __block SFAESKey* key = nil; 420 __block NSError* nsErrorLocal = nil; 421 __block CFErrorRef cfError = NULL; 422 __block bool ok = true; 423 dispatch_sync(_queue, ^{ 424 // try our cache first and rejoice if that succeeds 425 bool allowCaching = [SecDbKeychainMetadataKeyStore cachingEnabled]; 426 427 key = allowCaching ? self->_keysDict[@(keyclass)] : nil; 428 if (key) { 429 return; // Cache contains validated key for class, excellent! 430 } 431 432 // Key not in cache. Open a transaction to find or optionally (re)create key. Transactions can be nested, so this is fine. 433 ok &= kc_with_dbt_non_item_tables(true, &cfError, ^bool(SecDbConnectionRef dbt) { 434 key = [self fetchKeyForClass:keyclass 435 fromDb:dbt 436 keybag:keybag 437 specifier:keySpecifier 438 allowWrites:allowWrites 439 error:&nsErrorLocal]; 440 441 // The code for this conditional is a little convoluted because I want the "keychain locked" message to take precedence over the "not allowed to create one" message. 442 if (!key && ([nsErrorLocal.domain isEqualToString:(__bridge NSString*)kSecErrorDomain] && nsErrorLocal.code == errSecInteractionNotAllowed)) { 443 static sec_action_t logKeychainLockedMessageAction; 444 static dispatch_once_t keychainLockedMessageOnceToken; 445 dispatch_once(&keychainLockedMessageOnceToken, ^{ 446 logKeychainLockedMessageAction = sec_action_create("keychainlockedlogmessage", 1); 447 sec_action_set_handler(logKeychainLockedMessageAction, ^{ 448 secerror("SecDbKeychainItemV7: cannot decrypt metadata key because the keychain is locked (%ld)", (long)nsErrorLocal.code); 449 }); 450 }); 451 sec_action_perform(logKeychainLockedMessageAction); 452 } else if (!key && !allowWrites) { 453 secwarning("SecDbMetadataKeyStore: Unable to load metadatakey for class %d from disk (%@) and not allowed to create new one", keyclass, nsErrorLocal); 454 if (!nsErrorLocal) { 455 // If this is at creation time we are allowed to create so won't be here 456 // If this is at fetch time and we have a missing or bad key then the item /is/ dead 457 nsErrorLocal = [NSError errorWithDomain:(id)kSecErrorDomain code:errSecDecode userInfo:@{NSLocalizedDescriptionKey: @"Unable to find a suitable metadata key and not permitted to create one"}]; 458 } 459 return false; 460 // If this error is errSecDecode, then it's failed authentication and likely will forever. Other errors are scary. If !key and !error then no key existed. 461 } else if ((!key && !nsErrorLocal) || (!key && [nsErrorLocal.domain isEqualToString:NSOSStatusErrorDomain] && nsErrorLocal.code == errSecDecode)) { 462 secwarning("SecDbMetadataKeyStore: unable to use key (%ld), will attempt to create new one", (long)nsErrorLocal.code); 463 nsErrorLocal = nil; 464 key = [self newKeyForKeyclass:keyclass withKeybag:keybag keySpecifier:keySpecifier database:dbt error:&nsErrorLocal]; 465 if (!key) { 466 secerror("SecDbMetadataKeyStore: unable to create or save new key: %@", nsErrorLocal); 467 return false; 468 } 469 } else if (!key) { 470 secerror("SecDbMetadataKeyStore: scary error encountered: %@", nsErrorLocal); 471 } else if (allowCaching) { 472 self->_keysDict[@(keyclass)] = key; // Only cache keys fetched from disk 473 } 474 return !!key; 475 }); // kc_with_dbt 476 }); // our queue 477 478 if (!ok || !key) { 479 if (nsErrorLocal) { 480 *error = nsErrorLocal; 481 CFReleaseNull(cfError); 482 } else { 483 BridgeCFErrorToNSErrorOut(error, cfError); 484 } 485 assert(*error); // Triggers only in testing, which is by design not to break production 486 key = nil; 487 } 488 489 reentrant = NO; 490 491 return key; 492 } 493 494 @end 495 496 NS_ASSUME_NONNULL_END