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