]> git.saurik.com Git - apple/security.git/blob - secdxctests/KeychainCryptoTests.m
Security-59754.80.3.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 "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