/ secdxctests / KeychainCryptoTests.m
KeychainCryptoTests.m
1 /* 2 * Copyright (c) 2017 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 "KeychainXCTest.h" 25 #import "SecDbKeychainItem.h" 26 #import "SecdTestKeychainUtilities.h" 27 #import "CKKS.h" 28 #import "SecDbKeychainItemV7.h" 29 #import "SecDbKeychainMetadataKeyStore.h" 30 #import "SecItemPriv.h" 31 #import "SecItemServer.h" 32 #import "spi.h" 33 #import "SecDbKeychainSerializedItemV7.h" 34 #import "SecDbKeychainSerializedMetadata.h" 35 #import "SecDbKeychainSerializedMetadataKey.h" 36 #import "SecDbKeychainSerializedSecretData.h" 37 #import "SecDbKeychainSerializedAKSWrappedKey.h" 38 #import <utilities/SecCFWrappers.h> 39 #import <SecurityFoundation/SFEncryptionOperation.h> 40 #import <SecurityFoundation/SFCryptoServicesErrors.h> 41 #import <XCTest/XCTest.h> 42 #import <OCMock/OCMock.h> 43 #import <notify.h> 44 45 @interface SecDbKeychainItemV7 () 46 47 + (SFAESKeySpecifier*)keySpecifier; 48 49 @end 50 51 #if USE_KEYSTORE 52 #include "OSX/utilities/SecAKSWrappers.h" 53 54 @interface KeychainCryptoTests : KeychainXCTest 55 @end 56 57 @implementation KeychainCryptoTests 58 59 static keyclass_t parse_keyclass(CFTypeRef value) { 60 if (!value || CFGetTypeID(value) != CFStringGetTypeID()) { 61 return 0; 62 } 63 64 if (CFEqual(value, kSecAttrAccessibleWhenUnlocked)) { 65 return key_class_ak; 66 } 67 else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlock)) { 68 return key_class_ck; 69 } 70 else if (CFEqual(value, kSecAttrAccessibleAlwaysPrivate)) { 71 return key_class_dk; 72 } 73 else if (CFEqual(value, kSecAttrAccessibleWhenUnlockedThisDeviceOnly)) { 74 return key_class_aku; 75 } 76 else if (CFEqual(value, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)) { 77 return key_class_cku; 78 } 79 else if (CFEqual(value, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate)) { 80 return key_class_dku; 81 } 82 else if (CFEqual(value, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)) { 83 return key_class_akpu; 84 } 85 else { 86 return 0; 87 } 88 } 89 90 - (NSDictionary* _Nullable)addTestItemExpecting:(OSStatus)code account:(NSString*)account accessible:(NSString*)accessible 91 { 92 NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword, 93 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 94 (id)kSecAttrAccount : account, 95 (id)kSecAttrService : [NSString stringWithFormat:@"%@-Service", account], 96 (id)kSecAttrAccessible : (id)accessible, 97 (id)kSecUseDataProtectionKeychain : @(YES), 98 (id)kSecReturnAttributes : @(YES), 99 }; 100 CFTypeRef result = NULL; 101 102 if(code == errSecSuccess) { 103 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), code, @"Should have succeeded in adding test item to keychain"); 104 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd"); 105 } else { 106 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), code, @"Should have failed to adding test item to keychain with code %d", (int)code); 107 XCTAssertNil((__bridge id)result, @"Should not have received a dictionary back from SecItemAdd"); 108 } 109 110 return CFBridgingRelease(result); 111 } 112 113 - (NSDictionary* _Nullable)findTestItemExpecting:(OSStatus)code account:(NSString*)account 114 { 115 NSDictionary* findQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword, 116 (id)kSecAttrAccount : account, 117 (id)kSecAttrService : [NSString stringWithFormat:@"%@-Service", account], 118 (id)kSecUseDataProtectionKeychain : @(YES), 119 (id)kSecReturnAttributes : @(YES), 120 }; 121 CFTypeRef result = NULL; 122 123 if(code == errSecSuccess) { 124 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), code, @"Should have succeeded in finding test tiem"); 125 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching"); 126 } else { 127 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), code, @"Should have failed to find items in keychain with code %d", (int)code); 128 XCTAssertNotNil((__bridge id)result, @"Should not have received a dictionary back from SecItemCopyMatching"); 129 } 130 131 return CFBridgingRelease(result); 132 } 133 134 135 - (void)testBasicEncryptDecrypt 136 { 137 CFDataRef enc = NULL; 138 CFErrorRef error = NULL; 139 SecAccessControlRef ac = NULL; 140 141 NSDictionary* secretData = @{(id)kSecValueData : @"secret here"}; 142 CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{}; 143 144 ac = SecAccessControlCreate(NULL, &error); 145 XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error); 146 XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error); 147 XCTAssertTrue(SecAccessControlSetProtection(ac, kSecAttrAccessibleWhenUnlocked, &error), @"failed to set access control protection with error: %@", error); 148 XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error); 149 150 XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error); 151 XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function"); 152 XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error); 153 CFReleaseNull(ac); 154 155 CFMutableDictionaryRef attributes = NULL; 156 uint32_t version = 0; 157 NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5]; 158 const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword); 159 NSArray* dummyArray = [NSArray array]; 160 161 keyclass_t keyclass = 0; 162 XCTAssertTrue(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, enc, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, true, &keyclass, &error), @"failed to decrypt data with error: %@", error); 163 XCTAssertNil((__bridge id)error, @"encountered error attempting to decrypt data: %@", (__bridge id)error); 164 XCTAssertEqual(keyclass, key_class_ak, @"failed to get back the keyclass from decryption"); 165 166 CFTypeRef aclProtection = ac ? SecAccessControlGetProtection(ac) : NULL; 167 XCTAssertNotNil((__bridge id)aclProtection, @"failed to get ACL from keychain item decryption"); 168 if (aclProtection) { 169 XCTAssertTrue(CFEqual(aclProtection, kSecAttrAccessibleWhenUnlocked), @"the acl we got back from decryption does not match what we put in"); 170 } 171 CFReleaseNull(ac); 172 173 CFReleaseNull(error); 174 CFReleaseNull(enc); 175 } 176 177 - (void)testGetMetadataThenData 178 { 179 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 180 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 181 (id)kSecAttrAccount : @"TestAccount", 182 (id)kSecAttrService : @"TestService", 183 (id)kSecUseDataProtectionKeychain : @(YES) }; 184 185 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 186 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 187 188 NSMutableDictionary* metadataQuery = item.mutableCopy; 189 [metadataQuery removeObjectForKey:(id)kSecValueData]; 190 metadataQuery[(id)kSecReturnAttributes] = @(YES); 191 CFTypeRef foundItem = NULL; 192 result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem); 193 XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain"); 194 195 NSMutableDictionary* dataQuery = [(__bridge NSDictionary*)foundItem mutableCopy]; 196 dataQuery[(id)kSecReturnData] = @(YES); 197 dataQuery[(id)kSecClass] = (id)kSecClassGenericPassword; 198 dataQuery[(id)kSecUseDataProtectionKeychain] = @(YES); 199 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 200 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added to the keychain"); 201 202 NSData* foundData = (__bridge NSData*)foundItem; 203 if ([foundData isKindOfClass:[NSData class]]) { 204 NSString* foundPassword = [[NSString alloc] initWithData:(__bridge NSData*)foundItem encoding:NSUTF8StringEncoding]; 205 XCTAssertEqualObjects(foundPassword, @"password", @"found password (%@) does not match the expected password", foundPassword); 206 } 207 else { 208 XCTAssertTrue(false, @"found data is not the expected class: %@", foundData); 209 } 210 } 211 212 - (void)testGetReference 213 { 214 NSDictionary* keyParams = @{ (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits : @(1024) }; 215 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL); 216 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey, 217 (id)kSecValueRef : (__bridge id)key, 218 (id)kSecAttrLabel : @"TestLabel", 219 (id)kSecUseDataProtectionKeychain : @(YES) }; 220 221 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 222 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 223 224 NSMutableDictionary* refQuery = item.mutableCopy; 225 [refQuery removeObjectForKey:(id)kSecValueData]; 226 refQuery[(id)kSecReturnRef] = @(YES); 227 CFTypeRef foundItem = NULL; 228 result = SecItemCopyMatching((__bridge CFDictionaryRef)refQuery, &foundItem); 229 XCTAssertEqual(result, 0, @"failed to find the reference for the item we just added in the keychain"); 230 231 NSData* originalKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation(key, NULL); 232 NSData* foundKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation((SecKeyRef)foundItem, NULL); 233 XCTAssertEqualObjects(originalKeyData, foundKeyData, @"found key does not match the key we put in the keychain"); 234 } 235 236 - (void)testMetadataQueriesDoNotGetSecret 237 { 238 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 239 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 240 (id)kSecAttrAccount : @"TestAccount", 241 (id)kSecAttrService : @"TestService", 242 (id)kSecUseDataProtectionKeychain : @(YES) }; 243 244 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 245 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 246 247 NSMutableDictionary* metadataQuery = item.mutableCopy; 248 [metadataQuery removeObjectForKey:(id)kSecValueData]; 249 metadataQuery[(id)kSecReturnAttributes] = @(YES); 250 CFTypeRef foundItem = NULL; 251 result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem); 252 XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain"); 253 254 NSData* data = [(__bridge NSDictionary*)foundItem valueForKey:(id)kSecValueData]; 255 XCTAssertNil(data, @"unexpectedly found data in a metadata query"); 256 } 257 258 - (void)testDeleteItem 259 { 260 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 261 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 262 (id)kSecAttrAccount : @"TestAccount", 263 (id)kSecAttrService : @"TestService", 264 (id)kSecUseDataProtectionKeychain : @(YES) }; 265 266 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 267 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 268 269 NSMutableDictionary* dataQuery = item.mutableCopy; 270 [dataQuery removeObjectForKey:(id)kSecValueData]; 271 dataQuery[(id)kSecReturnData] = @(YES); 272 CFTypeRef foundItem = NULL; 273 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 274 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 275 276 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 277 XCTAssertEqual(result, 0, @"failed to delete item"); 278 } 279 280 - (SecDbKeychainSerializedItemV7*)serializedItemWithPassword:(NSString*)password metadataAttributes:(NSDictionary*)metadata 281 { 282 NSError* error; 283 SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithSecretAttributes:@{(id)kSecValueData : password} metadataAttributes:metadata tamperCheck:[[NSUUID UUID] UUIDString] keyclass:9]; 284 [item encryptMetadataWithKeybag:0 error:&error]; 285 XCTAssertNil(error, "error encrypting metadata with keybag 0"); 286 [item encryptSecretDataWithKeybag:0 accessControl:SecAccessControlCreate(NULL, NULL) acmContext:nil error:&error]; 287 XCTAssertNil(error, "error encrypting secret data with keybag 0"); 288 SecDbKeychainSerializedItemV7* serializedItem = [[SecDbKeychainSerializedItemV7 alloc] init]; 289 serializedItem.encryptedMetadata = item.encryptedMetadataBlob; 290 serializedItem.encryptedSecretData = item.encryptedSecretDataBlob; 291 serializedItem.keyclass = 9; 292 return serializedItem; 293 } 294 295 - (void)testTamperChecksThwartTampering 296 { 297 SecDbKeychainSerializedItemV7* serializedItem1 = [self serializedItemWithPassword:@"first password" metadataAttributes:nil]; 298 SecDbKeychainSerializedItemV7* serializedItem2 = [self serializedItemWithPassword:@"second password" metadataAttributes:nil]; 299 300 serializedItem1.encryptedSecretData = serializedItem2.encryptedSecretData; 301 NSData* tamperedSerializedItemBlob = serializedItem1.data; 302 303 NSError* error = nil; 304 SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithData:tamperedSerializedItemBlob decryptionKeybag:0 error:&error]; 305 XCTAssertNil(item, @"unexpectedly deserialized an item blob which has been tampered"); 306 XCTAssertNotNil(error, @"failed to get an error when deserializing tampered item blob"); 307 } 308 309 - (void)testCacheExpiration 310 { 311 312 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 313 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 314 (id)kSecAttrAccount : @"TestAccount", 315 (id)kSecAttrService : @"TestService", 316 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked, 317 (id)kSecUseDataProtectionKeychain : @YES }; 318 319 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 320 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 321 322 NSMutableDictionary* dataQuery = item.mutableCopy; 323 [dataQuery removeObjectForKey:(id)kSecValueData]; 324 dataQuery[(id)kSecReturnData] = @(YES); 325 326 CFTypeRef foundItem = NULL; 327 328 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 329 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 330 CFReleaseNull(foundItem); 331 332 self.lockState = LockStateLockedAndDisallowAKS; 333 334 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 335 XCTAssertEqual(result, errSecInteractionNotAllowed, @"get the lock error"); 336 XCTAssertEqual(foundItem, NULL, @"got item anyway: %@", foundItem); 337 338 self.lockState = LockStateUnlocked; 339 340 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 341 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 342 CFReleaseNull(foundItem); 343 344 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 345 XCTAssertEqual(result, 0, @"failed to delete item"); 346 } 347 348 - (void)trashMetadataClassAKey 349 { 350 __block CFErrorRef cferror = NULL; 351 kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) { 352 CFStringRef sql = CFSTR("UPDATE metadatakeys SET data = ? WHERE keyclass = '6'"); 353 NSData* garbage = [@"super bad key" dataUsingEncoding:NSUTF8StringEncoding]; 354 SecDbPrepare(dbt, sql, &cferror, ^(sqlite3_stmt *stmt) { 355 SecDbBindObject(stmt, 1, (__bridge CFDataRef)garbage, &cferror); 356 SecDbStep(dbt, stmt, &cferror, NULL); 357 XCTAssertEqual(cferror, NULL, "Should be no error trashing class A metadatakey"); 358 CFReleaseNull(cferror); 359 }); 360 XCTAssertEqual(cferror, NULL, "Should be no error completing SecDbPrepare for trashing class A metadatakey"); 361 return true; 362 }); 363 CFReleaseNull(cferror); 364 365 [[SecDbKeychainMetadataKeyStore sharedStore] dropClassAKeys]; 366 } 367 368 - (void)deleteMetadataClassAKey 369 { 370 CFErrorRef cferror = NULL; 371 372 kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) { 373 CFErrorRef errref = NULL; 374 SecDbExec(dbt, CFSTR("DELETE FROM metadatakeys WHERE keyclass = '6'"), &errref); 375 XCTAssertEqual(errref, NULL, "Should be no error deleting class A metadatakey"); 376 CFReleaseNull(errref); 377 return true; 378 }); 379 CFReleaseNull(cferror); 380 381 [[SecDbKeychainMetadataKeyStore sharedStore] dropClassAKeys]; 382 } 383 384 - (void)checkDatabaseExistenceOfMetadataKey:(keyclass_t)keyclass shouldExist:(bool)shouldExist value:(NSData*)expectedData 385 { 386 CFErrorRef cferror = NULL; 387 __block NSData* wrappedKey; 388 kc_with_dbt(true, &cferror, ^bool(SecDbConnectionRef dbt) { 389 __block CFErrorRef errref = NULL; 390 391 NSString* sql = [NSString stringWithFormat:@"SELECT data, actualKeyclass FROM metadatakeys WHERE keyclass = %d", keyclass]; 392 __block bool ok = true; 393 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &errref, ^(sqlite3_stmt *stmt) { 394 ok &= SecDbStep(dbt, stmt, &errref, ^(bool *stop) { 395 wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)]; 396 }); 397 }); 398 399 XCTAssertTrue(ok, "Should have completed all operations correctly"); 400 XCTAssertEqual(errref, NULL, "Should be no error trying to find class A metadatakey"); 401 402 if(shouldExist) { 403 XCTAssertNotNil(wrappedKey, "Metadata class key should exist"); 404 if (expectedData) { 405 XCTAssertEqualObjects(wrappedKey, expectedData); 406 } 407 } else { 408 XCTAssertNil(wrappedKey, "Metadata class key should not exist"); 409 } 410 CFReleaseNull(errref); 411 return true; 412 }); 413 CFReleaseNull(cferror); 414 } 415 416 - (void)testKeychainCorruptionCopyMatching 417 { 418 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 419 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 420 (id)kSecAttrAccount : @"TestAccount", 421 (id)kSecAttrService : @"TestService", 422 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked, 423 (id)kSecUseDataProtectionKeychain : @YES }; 424 425 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 426 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 427 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:nil]; 428 429 NSMutableDictionary* dataQuery = item.mutableCopy; 430 [dataQuery removeObjectForKey:(id)kSecValueData]; 431 dataQuery[(id)kSecReturnData] = @(YES); 432 433 CFTypeRef foundItem = NULL; 434 435 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 436 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 437 CFReleaseNull(foundItem); 438 439 [self trashMetadataClassAKey]; 440 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:[@"super bad key" dataUsingEncoding:NSUTF8StringEncoding]]; 441 442 /* when metadata corrupted, we should not find the item */ 443 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 444 XCTAssertEqual(result, errSecItemNotFound, @"failed to find the data for the item we just added in the keychain"); 445 CFReleaseNull(foundItem); 446 447 // Just calling SecItemCopyMatching shouldn't have created a new metadata key 448 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:[@"super bad key" dataUsingEncoding:NSUTF8StringEncoding]]; 449 450 /* CopyMatching will delete corrupt pre-emptively */ 451 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 452 XCTAssertEqual(result, -25300, @"corrupt item was not deleted for us"); 453 } 454 455 - (void)testKeychainDeletionCopyMatching 456 { 457 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 458 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 459 (id)kSecAttrAccount : @"TestAccount", 460 (id)kSecAttrService : @"TestService", 461 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked, 462 (id)kSecUseDataProtectionKeychain : @YES }; 463 464 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 465 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 466 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:true value:nil]; 467 468 NSMutableDictionary* dataQuery = item.mutableCopy; 469 [dataQuery removeObjectForKey:(id)kSecValueData]; 470 dataQuery[(id)kSecReturnData] = @(YES); 471 472 CFTypeRef foundItem = NULL; 473 474 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 475 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 476 CFReleaseNull(foundItem); 477 478 [self deleteMetadataClassAKey]; 479 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:false value:nil]; 480 481 /* when metadata corrupted, we should not find the item */ 482 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 483 XCTAssertEqual(result, errSecItemNotFound, @"failed to find the data for the item we just added in the keychain"); 484 CFReleaseNull(foundItem); 485 486 // Just calling SecItemCopyMatching shouldn't have created a new metadata key 487 [self checkDatabaseExistenceOfMetadataKey:key_class_ak shouldExist:false value:nil]; 488 489 /* CopyMatching will delete corrupt pre-emptively */ 490 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 491 XCTAssertEqual(result, -25300, @"corrupt item was not deleted for us"); 492 } 493 494 - (void)testKeychainCorruptionAddOverCorruptedEntry 495 { 496 __security_simulatecrash_enable(false); 497 498 CFTypeRef foundItem = NULL; 499 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 500 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 501 (id)kSecAttrAccount : @"TestAccount", 502 (id)kSecAttrService : @"TestService", 503 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked, 504 (id)kSecUseDataProtectionKeychain : @YES }; 505 506 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 507 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 508 509 NSMutableDictionary* dataQuery = item.mutableCopy; 510 [dataQuery removeObjectForKey:(id)kSecValueData]; 511 dataQuery[(id)kSecReturnData] = @(YES); 512 513 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 514 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 515 CFReleaseNull(foundItem); 516 517 [self trashMetadataClassAKey]; 518 519 result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 520 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 521 522 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 523 XCTAssertEqual(result, 0, @"failed to delete item"); 524 } 525 526 - (void)testKeychainCorruptionUpdateCorruptedEntry 527 { 528 __security_simulatecrash_enable(false); 529 530 CFTypeRef foundItem = NULL; 531 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 532 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 533 (id)kSecAttrAccount : @"TestAccount", 534 (id)kSecAttrService : @"TestService", 535 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked, 536 (id)kSecUseDataProtectionKeychain : @YES }; 537 538 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 539 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 540 541 NSMutableDictionary* dataQuery = item.mutableCopy; 542 [dataQuery removeObjectForKey:(id)kSecValueData]; 543 dataQuery[(id)kSecReturnData] = @(YES); 544 545 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 546 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain"); 547 CFReleaseNull(foundItem); 548 549 [self trashMetadataClassAKey]; 550 551 NSMutableDictionary* updateQuery = item.mutableCopy; 552 updateQuery[(id)kSecValueData] = NULL; 553 NSDictionary *updateData = @{ 554 (id)kSecValueData : [@"foo" dataUsingEncoding:NSUTF8StringEncoding], 555 }; 556 557 result = SecItemUpdate((__bridge CFDictionaryRef)updateQuery, 558 (__bridge CFDictionaryRef)updateData ); 559 XCTAssertEqual(result, errSecItemNotFound, @"failed to add test item to keychain"); 560 561 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery); 562 XCTAssertEqual(result, 0, @"failed to delete item"); 563 } 564 565 - (void)testNoCrashWhenMetadataDecryptionFails 566 { 567 CFDataRef enc = NULL; 568 CFErrorRef error = NULL; 569 SecAccessControlRef ac = NULL; 570 571 self.allowDecryption = NO; 572 573 NSDictionary* secretData = @{(id)kSecValueData : @"secret here"}; 574 CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{}; 575 576 ac = SecAccessControlCreate(NULL, &error); 577 XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error); 578 XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error); 579 XCTAssertTrue(SecAccessControlSetProtection(ac, kSecAttrAccessibleWhenUnlocked, &error), @"failed to set access control protection with error: %@", error); 580 XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error); 581 582 XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error); 583 XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function"); 584 XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error); 585 CFReleaseNull(ac); 586 587 CFMutableDictionaryRef attributes = NULL; 588 uint32_t version = 0; 589 NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5]; 590 const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword); 591 NSArray* dummyArray = [NSArray array]; 592 593 keyclass_t keyclass = 0; 594 XCTAssertNoThrow(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, enc, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, true, &keyclass, &error), @"unexpected exception when decryption fails"); 595 XCTAssertEqual(keyclass, key_class_ak, @"failed to get back the keyclass when decryption failed"); 596 597 self.allowDecryption = YES; 598 } 599 600 #if 0 601 // these tests fail until we address <rdar://problem/37523001> Fix keychain lock state check to be both secure and fast for EDU mode 602 - (void)testOperationsDontUseCachedKeysWhileLockedWithAKSAvailable // simulating the backup situation 603 { 604 self.lockState = LockStateLockedAndAllowAKS; 605 606 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 607 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 608 (id)kSecAttrAccount : @"TestAccount", 609 (id)kSecAttrService : @"TestService", 610 (id)kSecUseDataProtectionKeychain : @(YES) }; 611 612 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 613 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 614 615 NSMutableDictionary* metadataQuery = item.mutableCopy; 616 [metadataQuery removeObjectForKey:(id)kSecValueData]; 617 metadataQuery[(id)kSecReturnAttributes] = @(YES); 618 CFTypeRef foundItem = NULL; 619 result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem); 620 XCTAssertEqual(result, 0, @"failed to find the metadata for the item we just added in the keychain"); 621 622 XCTAssertTrue(self.didAKSDecrypt, @"we did not go through AKS to decrypt the metadata key while locked - bad!"); 623 624 NSMutableDictionary* dataQuery = item.mutableCopy; 625 dataQuery[(id)kSecReturnData] = @(YES); 626 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem); 627 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added to the keychain"); 628 629 NSData* foundData = (__bridge NSData*)foundItem; 630 if ([foundData isKindOfClass:[NSData class]]) { 631 NSString* foundPassword = [[NSString alloc] initWithData:(__bridge NSData*)foundItem encoding:NSUTF8StringEncoding]; 632 XCTAssertEqualObjects(foundPassword, @"password", @"found password (%@) does not match the expected password", foundPassword); 633 } 634 else { 635 XCTAssertTrue(false, @"found data is not the expected class: %@", foundData); 636 } 637 } 638 639 - (void)testNoResultsWhenLocked 640 { 641 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword, 642 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 643 (id)kSecAttrAccount : @"TestAccount", 644 (id)kSecAttrService : @"TestService", 645 (id)kSecUseDataProtectionKeychain : @(YES) }; 646 647 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL); 648 XCTAssertEqual(result, 0, @"failed to add test item to keychain"); 649 650 self.lockState = LockStateLockedAndDisallowAKS; 651 652 NSMutableDictionary* metadataQuery = item.mutableCopy; 653 [metadataQuery removeObjectForKey:(id)kSecValueData]; 654 metadataQuery[(id)kSecReturnAttributes] = @(YES); 655 CFTypeRef foundItem = NULL; 656 result = SecItemCopyMatching((__bridge CFDictionaryRef)metadataQuery, &foundItem); 657 XCTAssertEqual(foundItem, NULL, @"somehow still got results when AKS was locked"); 658 } 659 #endif 660 661 + (NSData*)fakeDecrypt:(SFAuthenticatedCiphertext*)ciphertext withKey:(SFSymmetricKey*)key error:(NSError**)error 662 { 663 return nil; 664 } 665 666 - (void)testRecoverFromBadMetadataKey 667 { 668 __security_simulatecrash_enable(false); 669 670 // Disable caching, so we can change AKS encrypt/decrypt 671 id mockSecDbKeychainMetadataKeyStore = OCMClassMock([SecDbKeychainMetadataKeyStore class]); 672 OCMStub([mockSecDbKeychainMetadataKeyStore cachingEnabled]).andReturn(false); 673 674 NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword, 675 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 676 (id)kSecAttrAccount : @"TestAccount", 677 (id)kSecAttrService : @"TestService", 678 (id)kSecUseDataProtectionKeychain : @(YES), 679 (id)kSecReturnAttributes : @(YES), 680 }; 681 682 NSDictionary* findQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword, 683 (id)kSecAttrAccount : @"TestAccount", 684 (id)kSecAttrService : @"TestService", 685 (id)kSecUseDataProtectionKeychain : @(YES), 686 (id)kSecReturnAttributes : @(YES), 687 }; 688 689 #if TARGET_OS_OSX 690 NSDictionary* updateQuery = findQuery; 691 #else 692 // iOS won't tolerate kSecReturnAttributes in SecItemUpdate 693 NSDictionary* updateQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword, 694 (id)kSecAttrAccount : @"TestAccount", 695 (id)kSecAttrService : @"TestService", 696 (id)kSecUseDataProtectionKeychain : @(YES), 697 }; 698 #endif 699 700 NSDictionary* addQuery2 = @{ (id)kSecClass : (id)kSecClassGenericPassword, 701 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding], 702 (id)kSecAttrAccount : @"TestAccount-second", 703 (id)kSecAttrService : @"TestService-second", 704 (id)kSecUseDataProtectionKeychain : @(YES), 705 (id)kSecReturnAttributes : @(YES), 706 }; 707 708 NSDictionary* findQuery2 = @{ (id)kSecClass : (id)kSecClassGenericPassword, 709 (id)kSecAttrAccount : @"TestAccount-second", 710 (id)kSecAttrService : @"TestService-second", 711 (id)kSecUseDataProtectionKeychain : @(YES), 712 (id)kSecReturnAttributes : @(YES), 713 }; 714 715 CFTypeRef result = NULL; 716 717 // Add the item 718 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain"); 719 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd"); 720 CFReleaseNull(result); 721 722 // Add a second item, for fun and profit 723 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery2, &result), 724 errSecSuccess, 725 @"Should have succeeded in adding test2 item to keychain"); 726 727 // And we can find te item 728 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item"); 729 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching"); 730 CFReleaseNull(result); 731 732 // And we can update the item 733 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery, 734 (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding]}), 735 errSecSuccess, 736 "Should be able to update an item"); 737 738 // And find it again 739 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item"); 740 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching"); 741 CFReleaseNull(result); 742 743 // And we can find the second item 744 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result), 745 errSecSuccess, @"Should be able to find second item"); 746 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching for item 2"); 747 CFReleaseNull(result); 748 749 /////////////////////////////////////////////////////////////////////////////////// 750 // Now, the metadata keys go corrupt (fake that by changing the underlying AKS key) 751 [self setNewFakeAKSKey:[NSData dataWithBytes:"NotthesameAKSkeyas0123456789etc" length:32]]; 752 753 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound, 754 "should have received errSecItemNotFound when metadata keys are invalid"); 755 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound, 756 "Multiple finds of the same item should receive errSecItemNotFound when metadata keys are invalid"); 757 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecItemNotFound, 758 "Multiple finds of the same item should receive errSecItemNotFound when metadata keys are invalid"); 759 760 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result), 761 errSecItemNotFound, @"Should not be able to find corrupt second item"); 762 XCTAssertNil((__bridge id)result, @"Should have received no data back from SICM for corrupt item"); 763 764 // Updating the now-corrupt item should fail 765 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery, 766 (__bridge CFDictionaryRef)@{ (id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding] }), 767 errSecItemNotFound, 768 "Should not be able to update a corrupt item"); 769 770 // Re-add the item (should succeed) 771 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain"); 772 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd"); 773 CFReleaseNull(result); 774 775 // And we can find it again 776 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery, &result), errSecSuccess, @"Should be able to find item"); 777 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd"); 778 CFReleaseNull(result); 779 780 // And update it 781 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQuery, 782 (__bridge CFDictionaryRef)@{ (id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding] }), 783 errSecSuccess, 784 "Should be able to update a fixed item"); 785 786 ///////////// 787 // And our second item, which is wrapped under an old key, can't be found 788 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result), 789 errSecItemNotFound, @"Should not be able to find corrupt second item"); 790 XCTAssertNil((__bridge id)result, @"Should have received no data back from SICM for corrupt item"); 791 792 // But can be re-added 793 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery2, &result), 794 errSecSuccess, 795 @"Should have succeeded in adding test2 item to keychain after corruption"); 796 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd for item 2 (after corruption)"); 797 CFReleaseNull(result); 798 799 // And we can find the second item again 800 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findQuery2, &result), 801 errSecSuccess, @"Should be able to find second item after re-add"); 802 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching for item 2 (after re-add)"); 803 CFReleaseNull(result); 804 805 [mockSecDbKeychainMetadataKeyStore stopMocking]; 806 } 807 808 // If a metadata key is created during a database transaction which is later rolled back, it shouldn't be cached for use later. 809 - (void)testMetadataKeyDoesntOutliveTxionRollback { 810 NSString* testAccount = @"TestAccount"; 811 NSString* otherAccount = @"OtherAccount"; 812 NSString* thirdAccount = @"ThirdAccount"; 813 [self addTestItemExpecting:errSecSuccess account:testAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlock]; 814 [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil]; 815 [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:false value:nil]; 816 817 // This should fail, and not create a CKU metadata key 818 [self addTestItemExpecting:errSecDuplicateItem account:testAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; 819 [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil]; 820 [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:false value:nil]; 821 822 // But successfully creating a new CKU item should create the key 823 [self addTestItemExpecting:errSecSuccess account:otherAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; 824 [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil]; 825 [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:true value:nil]; 826 827 // Drop all metadata key caches 828 [SecDbKeychainMetadataKeyStore resetSharedStore]; 829 830 [self findTestItemExpecting:errSecSuccess account:testAccount]; 831 [self findTestItemExpecting:errSecSuccess account:otherAccount]; 832 833 // Adding another CKU item now should be fine 834 [self addTestItemExpecting:errSecSuccess account:thirdAccount accessible:(id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; 835 [self checkDatabaseExistenceOfMetadataKey:key_class_ck shouldExist:true value:nil]; 836 [self checkDatabaseExistenceOfMetadataKey:key_class_cku shouldExist:true value:nil]; 837 838 // Drop all metadata key caches once more, to ensure we can find all three items from the persisted keys 839 [SecDbKeychainMetadataKeyStore resetSharedStore]; 840 841 [self findTestItemExpecting:errSecSuccess account:testAccount]; 842 [self findTestItemExpecting:errSecSuccess account:otherAccount]; 843 [self findTestItemExpecting:errSecSuccess account:thirdAccount]; 844 } 845 846 - (void)testRecoverDataFromBadKeyclassStorage 847 { 848 NSDictionary* metadataAttributesInput = @{@"TestMetadata" : @"TestValue"}; 849 SecDbKeychainSerializedItemV7* serializedItem = [self serializedItemWithPassword:@"password" metadataAttributes:metadataAttributesInput]; 850 serializedItem.keyclass = (serializedItem.keyclass | key_class_last + 1); 851 852 NSError* error = nil; 853 SecDbKeychainItemV7* item = [[SecDbKeychainItemV7 alloc] initWithData:serializedItem.data decryptionKeybag:0 error:&error]; 854 NSDictionary* metadataAttributesOut = [item metadataAttributesWithError:&error]; 855 XCTAssertEqualObjects(metadataAttributesOut, metadataAttributesInput, @"failed to retrieve metadata with error: %@", error); 856 XCTAssertNil(error, @"error encountered attempting to retrieve metadata: %@", error); 857 } 858 859 - (NSData*)performItemEncryptionWithAccessibility:(CFStringRef)accessibility 860 { 861 SecAccessControlRef ac = NULL; 862 CFDataRef enc = NULL; 863 CFErrorRef error = NULL; 864 865 NSDictionary* secretData = @{(id)kSecValueData : @"secret here"}; 866 CFDictionaryRef emptyDict = (__bridge CFDictionaryRef)@{}; 867 868 ac = SecAccessControlCreate(NULL, &error); 869 XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error); 870 XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error); 871 XCTAssertTrue(SecAccessControlSetProtection(ac, accessibility, &error), @"failed to set access control protection with error: %@", error); 872 XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error); 873 874 XCTAssertTrue(ks_encrypt_data(KEYBAG_DEVICE, ac, NULL, (__bridge CFDictionaryRef)secretData, emptyDict, emptyDict, &enc, true, &error), @"failed to encrypt data with error: %@", error); 875 XCTAssertTrue(enc != NULL, @"failed to get encrypted data from encryption function"); 876 XCTAssertNil((__bridge id)error, @"encountered error attempting to encrypt data: %@", (__bridge id)error); 877 CFReleaseNull(ac); 878 879 return (__bridge_transfer NSData*)enc; 880 } 881 882 - (void)performMetadataDecryptionOfData:(NSData*)encryptedData verifyingAccessibility:(CFStringRef)accessibility 883 { 884 CFErrorRef error = NULL; 885 CFMutableDictionaryRef attributes = NULL; 886 uint32_t version = 0; 887 888 SecAccessControlRef ac = SecAccessControlCreate(NULL, &error); 889 XCTAssertNotNil((__bridge id)ac, @"failed to create access control with error: %@", (__bridge id)error); 890 XCTAssertNil((__bridge id)error, @"encountered error attempting to create access control: %@", (__bridge id)error); 891 XCTAssertTrue(SecAccessControlSetProtection(ac, accessibility, &error), @"failed to set access control protection with error: %@", error); 892 XCTAssertNil((__bridge id)error, @"encountered error attempting to set access control protection: %@", (__bridge id)error); 893 894 keyclass_t keyclass = 0; 895 NSData* dummyACM = [NSData dataWithBytes:"dummy" length:5]; 896 const SecDbClass* class = kc_class_with_name(kSecClassGenericPassword); 897 NSArray* dummyArray = [NSArray array]; 898 899 XCTAssertTrue(ks_decrypt_data(KEYBAG_DEVICE, kAKSKeyOpDecrypt, &ac, (__bridge CFDataRef _Nonnull)dummyACM, (__bridge CFDataRef)encryptedData, class, (__bridge CFArrayRef)dummyArray, &attributes, &version, false, &keyclass, &error), @"failed to decrypt data with error: %@", error); 900 XCTAssertNil((__bridge id)error, @"encountered error attempting to decrypt data: %@", (__bridge id)error); 901 XCTAssertEqual(keyclass & key_class_last, parse_keyclass(accessibility), @"failed to get back the keyclass from decryption"); 902 903 CFReleaseNull(error); 904 } 905 906 - (void)performMetadataEncryptDecryptWithAccessibility:(CFStringRef)accessibility 907 { 908 NSData* encryptedData = [self performItemEncryptionWithAccessibility:accessibility]; 909 910 [SecDbKeychainMetadataKeyStore resetSharedStore]; 911 912 [self performMetadataDecryptionOfData:encryptedData verifyingAccessibility:accessibility]; 913 } 914 915 - (void)testMetadataClassKeyDecryptionWithSimulatedAKSRolledKeys 916 { 917 self.simulateRolledAKSKey = YES; 918 919 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenUnlocked]; 920 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_ak | key_class_last + 1); 921 922 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAfterFirstUnlock]; 923 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_ck | key_class_last + 1); 924 925 #pragma clang diagnostic push 926 #pragma clang diagnostic ignored "-Wdeprecated-declarations" 927 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAlways]; 928 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_dk | key_class_last + 1); 929 #pragma clang diagnostic pop 930 931 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenUnlockedThisDeviceOnly]; 932 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_aku | key_class_last + 1); 933 934 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]; 935 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_cku | key_class_last + 1); 936 937 #pragma clang diagnostic push 938 #pragma clang diagnostic ignored "-Wdeprecated-declarations" 939 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleAlwaysThisDeviceOnly]; 940 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_dku | key_class_last + 1); 941 #pragma clang diagnostic pop 942 943 [self performMetadataEncryptDecryptWithAccessibility:kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly]; 944 XCTAssertEqual(self.keyclassUsedForAKSDecryption, key_class_akpu | key_class_last + 1); 945 } 946 947 - (void)testUpgradingMetadataKeyEntry 948 { 949 // first, force the creation of a metadata key 950 NSData* encryptedData = [self performItemEncryptionWithAccessibility:kSecAttrAccessibleWhenUnlocked]; 951 952 // now let's jury-rig this metadata key to look like an old one with no actualKeyclass information 953 __block CFErrorRef error = NULL; 954 __block bool ok = true; 955 ok &= kc_with_dbt(true, &error, ^bool(SecDbConnectionRef dbt) { 956 if (checkV12DevEnabled()) { // item is in new format, turn it into an old format item 957 NSString* sql = [NSString stringWithFormat:@"SELECT metadatakeydata FROM metadatakeys WHERE keyclass = %d", key_class_ak]; 958 __block NSData* key; 959 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt* stmt) { 960 ok &= SecDbStep(dbt, stmt, &error, ^(bool *stop) { 961 NSData* wrappedKey = [[NSData alloc] initWithBytes:sqlite3_column_blob(stmt, 0) length:sqlite3_column_bytes(stmt, 0)]; 962 SecDbKeychainSerializedMetadataKey* mdkdata = [[SecDbKeychainSerializedMetadataKey alloc] initWithData:wrappedKey]; 963 key = mdkdata.akswrappedkey; 964 }); 965 }); 966 sql = [NSString stringWithFormat:@"UPDATE metadatakeys SET actualKeyclass = 0, data = ?, metadatakeydata = ? WHERE keyclass = %d", key_class_ak]; 967 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt *stmt) { 968 ok &= SecDbBindBlob(stmt, 1, key.bytes, key.length, SQLITE_TRANSIENT, &error); 969 ok &= SecDbStep(dbt, stmt, &error, NULL); 970 }); 971 } else { 972 NSString* sql = [NSString stringWithFormat:@"UPDATE metadatakeys SET actualKeyclass = %d WHERE keyclass = %d", 0, key_class_ak]; 973 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)sql, &error, ^(sqlite3_stmt* stmt) { 974 ok &= SecDbStep(dbt, stmt, &error, NULL); 975 }); 976 } 977 return ok; 978 }); 979 980 // now, let's simulate AKS rejecting the decryption, and see if we recover and also update the database 981 self.simulateRolledAKSKey = YES; 982 [SecDbKeychainMetadataKeyStore resetSharedStore]; 983 [self performMetadataDecryptionOfData:encryptedData verifyingAccessibility:kSecAttrAccessibleWhenUnlocked]; 984 } 985 986 @end 987 988 #endif