2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "KeychainXCTest.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
28 #import "SecDbKeychainItemV7.h"
29 #import "SecItemPriv.h"
30 #include "SecItemInternal.h"
31 #import "SecItemServer.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 <XCTest/XCTest.h>
40 #import <OCMock/OCMock.h>
41 #include <dispatch/dispatch.h>
42 #include <utilities/SecDb.h>
44 #include <utilities/SecFileLocations.h>
45 #include "der_plist.h"
46 #import "SecItemRateLimit_tests.h"
50 @interface KeychainAPITests : KeychainXCTest
53 @implementation KeychainAPITests
60 - (NSString*)nameOfTest
62 return [self.name componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ]"]][1];
68 // KeychainXCTest already sets up keychain with custom test-named directory
71 - (void)testReturnValuesInSecItemUpdate
73 NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
74 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
75 (id)kSecAttrAccount : @"TestAccount",
76 (id)kSecAttrService : @"TestService",
77 (id)kSecUseDataProtectionKeychain : @(YES),
78 (id)kSecReturnAttributes : @(YES)
81 NSDictionary* updateQueryWithNoReturn = @{ (id)kSecClass : (id)kSecClassGenericPassword,
82 (id)kSecAttrAccount : @"TestAccount",
83 (id)kSecAttrService : @"TestService",
84 (id)kSecUseDataProtectionKeychain : @(YES)
87 CFTypeRef result = NULL;
90 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain");
91 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
92 CFReleaseNull(result);
94 // And we can update the item
95 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithNoReturn, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with clean update query");
97 // great, a normal update works
98 // now let's do updates with various queries which include return parameters to ensure they succeed on macOS and throw errors on iOS.
99 // this is a status-quo compromise between changing iOS match macOS (which has lamé no-op characteristics) and changing macOS to match iOS, which risks breaking existing clients
102 NSMutableDictionary* updateQueryWithReturnAttributes = updateQueryWithNoReturn.mutableCopy;
103 updateQueryWithReturnAttributes[(id)kSecReturnAttributes] = @(YES);
104 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnAttributes, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-attributes" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return attributes query");
106 NSMutableDictionary* updateQueryWithReturnData = updateQueryWithNoReturn.mutableCopy;
107 updateQueryWithReturnAttributes[(id)kSecReturnData] = @(YES);
108 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnData, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-data" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return data query");
110 NSMutableDictionary* updateQueryWithReturnRef = updateQueryWithNoReturn.mutableCopy;
111 updateQueryWithReturnAttributes[(id)kSecReturnRef] = @(YES);
112 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnRef, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-ref" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return ref query");
114 NSMutableDictionary* updateQueryWithReturnPersistentRef = updateQueryWithNoReturn.mutableCopy;
115 updateQueryWithReturnAttributes[(id)kSecReturnPersistentRef] = @(YES);
116 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnPersistentRef, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-persistent-ref" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return persistent ref query");
118 NSMutableDictionary* updateQueryWithReturnAttributes = updateQueryWithNoReturn.mutableCopy;
119 updateQueryWithReturnAttributes[(id)kSecReturnAttributes] = @(YES);
120 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnAttributes, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-attributes" dataUsingEncoding:NSUTF8StringEncoding]}), errSecParam, "failed to generate error updating item with return attributes query");
122 NSMutableDictionary* updateQueryWithReturnData = updateQueryWithNoReturn.mutableCopy;
123 updateQueryWithReturnData[(id)kSecReturnData] = @(YES);
124 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnData, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-data" dataUsingEncoding:NSUTF8StringEncoding]}), errSecParam, "failed to generate error updating item with return data query");
126 NSMutableDictionary* updateQueryWithReturnRef = updateQueryWithNoReturn.mutableCopy;
127 updateQueryWithReturnRef[(id)kSecReturnRef] = @(YES);
128 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnRef, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-ref" dataUsingEncoding:NSUTF8StringEncoding]}), errSecParam, "failed to generate error updating item with return ref query");
130 NSMutableDictionary* updateQueryWithReturnPersistentRef = updateQueryWithNoReturn.mutableCopy;
131 updateQueryWithReturnPersistentRef[(id)kSecReturnPersistentRef] = @(YES);
132 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnPersistentRef, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-persistent-ref" dataUsingEncoding:NSUTF8StringEncoding]}), errSecParam, "failed to generate error updating item with return persistent ref query");
136 - (void)testBadTypeInParams
138 NSMutableDictionary *attrs = @{
139 (id)kSecClass: (id)kSecClassGenericPassword,
140 (id)kSecUseDataProtectionKeychain: @YES,
141 (id)kSecAttrLabel: @"testentry",
144 SecItemDelete((CFDictionaryRef)attrs);
145 XCTAssertEqual(errSecSuccess, SecItemAdd((CFDictionaryRef)attrs, NULL));
146 XCTAssertEqual(errSecSuccess, SecItemDelete((CFDictionaryRef)attrs));
148 // We try to fool SecItem API with unexpected type of kSecAttrAccessControl attribute in query and it should not crash.
149 attrs[(id)kSecAttrAccessControl] = @"string, no SecAccessControlRef!";
150 XCTAssertEqual(errSecParam, SecItemAdd((CFDictionaryRef)attrs, NULL));
151 XCTAssertEqual(errSecParam, SecItemDelete((CFDictionaryRef)attrs));
154 - (BOOL)passInternalAttributeToKeychainAPIsWithKey:(id)key value:(id)value {
155 NSDictionary* badquery = @{
156 (id)kSecClass : (id)kSecClassGenericPassword,
157 (id)kSecAttrService : @"AppClipTestService",
158 (id)kSecUseDataProtectionKeychain : @YES,
159 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
162 NSDictionary* badupdate = @{key : value};
164 NSDictionary* okquery = @{
165 (id)kSecClass : (id)kSecClassGenericPassword,
166 (id)kSecAttrService : @"AppClipTestService",
167 (id)kSecUseDataProtectionKeychain : @YES,
168 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
170 NSDictionary* okupdate = @{(id)kSecAttrService : @"DifferentService"};
172 if (SecItemAdd((__bridge CFDictionaryRef)badquery, NULL) != errSecParam) {
173 XCTFail("SecItemAdd did not return errSecParam");
176 if (SecItemCopyMatching((__bridge CFDictionaryRef)badquery, NULL) != errSecParam) {
177 XCTFail("SecItemCopyMatching did not return errSecParam");
180 if (SecItemUpdate((__bridge CFDictionaryRef)badquery, (__bridge CFDictionaryRef)okupdate) != errSecParam) {
181 XCTFail("SecItemUpdate with bad query did not return errSecParam");
184 if (SecItemUpdate((__bridge CFDictionaryRef)okquery, (__bridge CFDictionaryRef)badupdate) != errSecParam) {
185 XCTFail("SecItemUpdate with bad update did not return errSecParam");
188 if (SecItemDelete((__bridge CFDictionaryRef)badquery) != errSecParam) {
189 XCTFail("SecItemDelete did not return errSecParam");
195 // Expand this, rdar://problem/59297616
196 - (void)testNotAllowedToPassInternalAttributes {
197 XCTAssert([self passInternalAttributeToKeychainAPIsWithKey:(__bridge NSString*)kSecAttrAppClipItem value:@YES], @"Expect errSecParam for 'clip' attribute");
200 #pragma mark - Corruption Tests
202 const uint8_t keychain_data[] = {
203 0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x01, 0x02, 0x03,
204 0x04, 0x5f, 0x10, 0x1b, 0x4e, 0x53, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,
205 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x63, 0x65,
206 0x73, 0x73, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x5f, 0x10, 0x1d, 0x4e, 0x53,
207 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65,
208 0x20, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20,
209 0x4d, 0x61, 0x63, 0x5f, 0x10, 0x1c, 0x32, 0x38, 0x20, 0x33, 0x37, 0x33,
210 0x20, 0x33, 0x34, 0x36, 0x20, 0x32, 0x39, 0x30, 0x20, 0x30, 0x20, 0x30,
211 0x20, 0x31, 0x34, 0x34, 0x30, 0x20, 0x38, 0x37, 0x38, 0x20, 0x5f, 0x10,
212 0x1d, 0x35, 0x36, 0x38, 0x20, 0x33, 0x39, 0x35, 0x20, 0x33, 0x30, 0x37,
213 0x20, 0x33, 0x37, 0x39, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x34, 0x34,
214 0x30, 0x20, 0x38, 0x37, 0x38, 0x20, 0x08, 0x0d, 0x2b, 0x4b, 0x6a, 0x00,
215 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
216 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a
220 dispatch_semaphore_t sema = NULL;
222 // The real corruption exit handler should xpc_transaction_exit_clean,
223 // let's be certain it does not. Also make sure exit handler gets called at all
224 static void SecDbTestCorruptionHandler(void)
226 dispatch_semaphore_signal(sema);
229 - (void)testCorruptionHandler {
230 __security_simulatecrash_enable(false);
232 SecDbCorruptionExitHandler = SecDbTestCorruptionHandler;
233 sema = dispatch_semaphore_create(0);
235 secd_test_setup_temp_keychain([[NSString stringWithFormat:@"%@-bad", [self nameOfTest]] UTF8String], ^{
236 CFStringRef keychain_path_cf = __SecKeychainCopyPath();
238 CFStringPerformWithCString(keychain_path_cf, ^(const char *keychain_path) {
239 int fd = open(keychain_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
240 XCTAssert(fd > -1, "Could not open fd to write keychain: %{darwin.errno}d", errno);
242 size_t written = write(fd, keychain_data, sizeof(keychain_data));
243 XCTAssertEqual(written, sizeof(keychain_data), "Write garbage to disk, got %lu instead of %lu: %{darwin.errno}d", written, sizeof(keychain_data), errno);
244 XCTAssertEqual(close(fd), 0, "Close keychain file failed: %{darwin.errno}d", errno);
247 CFReleaseNull(keychain_path_cf);
250 NSDictionary* query = @{(id)kSecClass : (id)kSecClassGenericPassword,
251 (id)kSecAttrAccount : @"TestAccount",
252 (id)kSecAttrService : @"TestService",
253 (id)kSecUseDataProtectionKeychain : @(YES),
254 (id)kSecReturnAttributes : @(YES)
257 CFTypeRef result = NULL;
258 // Real keychain should xpc_transaction_exit_clean() after this, but we nerfed it
259 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), errSecNotAvailable, "Expected badness from corrupt keychain");
260 XCTAssertEqual(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC)), 0, "Timed out waiting for corruption exit handler");
263 SecDbResetCorruptionExitHandler();
264 CFReleaseNull(result);
266 NSString* markerpath = [NSString stringWithFormat:@"%@-iscorrupt", CFBridgingRelease(__SecKeychainCopyPath())];
267 struct stat info = {};
268 XCTAssertEqual(stat([markerpath UTF8String], &info), 0, "Unable to stat corruption marker: %{darwin.errno}d", errno);
271 - (void)testRecoverFromCorruption {
272 __security_simulatecrash_enable(false);
274 // Setup does a reset, but that doesn't create the db yet so let's sneak in first
275 __block struct stat before = {};
276 WithPathInKeychainDirectory(CFSTR("keychain-2.db"), ^(const char *filename) {
277 FILE* file = fopen(filename, "w");
278 XCTAssert(file != NULL, "Didn't get a FILE pointer");
280 XCTAssertEqual(stat(filename, &before), 0, "Unable to stat newly created file");
283 WithPathInKeychainDirectory(CFSTR("keychain-2.db-iscorrupt"), ^(const char *filename) {
284 FILE* file = fopen(filename, "w");
285 XCTAssert(file != NULL, "Didn't get a FILE pointer");
289 NSMutableDictionary* query = [@{(id)kSecClass : (id)kSecClassGenericPassword,
290 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
291 (id)kSecAttrAccount : @"TestAccount",
292 (id)kSecAttrService : @"TestService",
293 (id)kSecUseDataProtectionKeychain : @(YES),
294 (id)kSecReturnAttributes : @(YES)
296 CFTypeRef result = NULL;
297 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, &result), errSecSuccess, @"Should have added item to keychain");
298 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
299 CFReleaseNull(result);
301 query[(id)kSecValueData] = nil;
302 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), errSecSuccess, @"Should have found item in keychain");
303 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching");
304 CFReleaseNull(result);
306 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess, @"Should have deleted item from keychain");
308 WithPathInKeychainDirectory(CFSTR("keychain-2.db-iscorrupt"), ^(const char *filename) {
309 struct stat markerinfo = {};
310 XCTAssertNotEqual(stat(filename, &markerinfo), 0, "Expected not to find corruption marker after killing keychain");
313 __block struct stat after = {};
314 WithPathInKeychainDirectory(CFSTR("keychain-2.db"), ^(const char *filename) {
315 FILE* file = fopen(filename, "w");
316 XCTAssert(file != NULL, "Didn't get a FILE pointer");
318 XCTAssertEqual(stat(filename, &after), 0, "Unable to stat newly created file");
321 if (before.st_birthtimespec.tv_sec == after.st_birthtimespec.tv_sec) {
322 XCTAssertLessThan(before.st_birthtimespec.tv_nsec, after.st_birthtimespec.tv_nsec, "db was not deleted and recreated");
324 XCTAssertLessThan(before.st_birthtimespec.tv_sec, after.st_birthtimespec.tv_sec, "db was not deleted and recreated");
328 - (void)testInetBinaryFields {
329 NSData* note = [@"OBVIOUS_NOTES_DATA" dataUsingEncoding:NSUTF8StringEncoding];
330 NSData* history = [@"OBVIOUS_HISTORY_DATA" dataUsingEncoding:NSUTF8StringEncoding];
331 NSData* client0 = [@"OBVIOUS_CLIENT0_DATA" dataUsingEncoding:NSUTF8StringEncoding];
332 NSData* client1 = [@"OBVIOUS_CLIENT1_DATA" dataUsingEncoding:NSUTF8StringEncoding];
333 NSData* client2 = [@"OBVIOUS_CLIENT2_DATA" dataUsingEncoding:NSUTF8StringEncoding];
334 NSData* client3 = [@"OBVIOUS_CLIENT3_DATA" dataUsingEncoding:NSUTF8StringEncoding];
336 NSData* originalPassword = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
337 NSMutableDictionary* query = [@{
338 (id)kSecClass : (id)kSecClassInternetPassword,
339 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
340 (id)kSecUseDataProtectionKeychain : @YES,
341 (id)kSecAttrDescription : @"desc",
342 (id)kSecAttrServer : @"server",
343 (id)kSecAttrAccount : @"test-account",
344 (id)kSecValueData : originalPassword,
345 (id)kSecDataInetExtraNotes : note,
346 (id)kSecDataInetExtraHistory : history,
347 (id)kSecDataInetExtraClientDefined0 : client0,
348 (id)kSecDataInetExtraClientDefined1 : client1,
349 (id)kSecDataInetExtraClientDefined2 : client2,
350 (id)kSecDataInetExtraClientDefined3 : client3,
352 (id)kSecReturnAttributes : @YES,
355 CFTypeRef cfresult = nil;
356 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, &cfresult), errSecSuccess, "Should be able to add an item using new binary fields");
357 NSDictionary* result = (NSDictionary*)CFBridgingRelease(cfresult);
358 XCTAssertNotNil(result, "Should have some sort of result");
360 XCTAssertNil(result[(id)kSecDataInetExtraNotes], "Notes field should not be returned as an attribute from add");
361 XCTAssertNil(result[(id)kSecDataInetExtraHistory], "Notes field should not be returned as an attribute from add");
363 NSDictionary* queryFind = @{
364 (id)kSecClass : (id)kSecClassInternetPassword,
365 (id)kSecUseDataProtectionKeychain : @YES,
366 (id)kSecAttrAccount : @"test-account",
369 NSMutableDictionary* queryFindOneWithJustAttributes = [[NSMutableDictionary alloc] initWithDictionary:queryFind];
370 queryFindOneWithJustAttributes[(id)kSecReturnAttributes] = @YES;
372 NSMutableDictionary* queryFindAllWithJustAttributes = [[NSMutableDictionary alloc] initWithDictionary:queryFindOneWithJustAttributes];
373 queryFindAllWithJustAttributes[(id)kSecMatchLimit] = (id)kSecMatchLimitAll;
375 NSDictionary* queryFindOneWithAttributesAndData = @{
376 (id)kSecClass : (id)kSecClassInternetPassword,
377 (id)kSecUseDataProtectionKeychain : @YES,
378 (id)kSecReturnAttributes : @YES,
379 (id)kSecReturnData: @YES,
380 (id)kSecAttrAccount : @"test-account",
383 NSDictionary* queryFindAllWithAttributesAndData = @{
384 (id)kSecClass : (id)kSecClassInternetPassword,
385 (id)kSecUseDataProtectionKeychain : @YES,
386 (id)kSecReturnAttributes : @YES,
387 (id)kSecReturnData: @YES,
388 (id)kSecMatchLimit : (id)kSecMatchLimitAll,
389 (id)kSecAttrAccount : @"test-account",
392 /* Copy with a single record limite, but with attributes only */
393 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)queryFindOneWithJustAttributes, &cfresult), errSecSuccess, "Should be able to find an item");
395 result = (NSDictionary*)CFBridgingRelease(cfresult);
396 XCTAssertNotNil(result, "Should have some sort of result");
398 XCTAssertNil(result[(id)kSecDataInetExtraNotes], "Notes field should not be returned as an attribute from copymatching when finding a single item");
399 XCTAssertNil(result[(id)kSecDataInetExtraHistory], "Notes field should not be returned as an attribute from copymatching when finding a single item");
400 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined0], "ClientDefined0 field should not be returned as an attribute from copymatching when finding a single item");
401 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined1], "ClientDefined1 field should not be returned as an attribute from copymatching when finding a single item");
402 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined2], "ClientDefined2 field should not be returned as an attribute from copymatching when finding a single item");
403 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined3], "ClientDefined3 field should not be returned as an attribute from copymatching when finding a single item");
405 /* Copy with no limit, but with attributes only */
406 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)queryFindAllWithJustAttributes, &cfresult), errSecSuccess, "Should be able to find an item");
407 NSArray* arrayResult = (NSArray*)CFBridgingRelease(cfresult);
408 XCTAssertNotNil(arrayResult, "Should have some sort of result");
409 XCTAssertTrue([arrayResult isKindOfClass:[NSArray class]], "Should have received an array back from copymatching");
410 XCTAssertEqual(arrayResult.count, 1, "Array should have one element");
412 result = arrayResult[0];
413 XCTAssertNil(result[(id)kSecDataInetExtraNotes], "Notes field should not be returned as an attribute from copymatching when finding all items");
414 XCTAssertNil(result[(id)kSecDataInetExtraHistory], "Notes field should not be returned as an attribute from copymatching when finding all items");
415 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined0], "ClientDefined0 field should not be returned as an attribute from copymatching when finding all items");
416 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined1], "ClientDefined1 field should not be returned as an attribute from copymatching when finding all items");
417 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined2], "ClientDefined2 field should not be returned as an attribute from copymatching when finding all items");
418 XCTAssertNil(result[(id)kSecDataInetExtraClientDefined3], "ClientDefined3 field should not be returned as an attribute from copymatching when finding all items");
420 /* Copy with single-record limit, but with attributes and data */
421 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)queryFindOneWithAttributesAndData, &cfresult), errSecSuccess, "Should be able to find an item");
422 result = (NSDictionary*)CFBridgingRelease(cfresult);
423 XCTAssertNotNil(result, "Should have some sort of result");
425 XCTAssertEqualObjects(note, result[(id)kSecDataInetExtraNotes], "Notes field should be returned as data");
426 XCTAssertEqualObjects(history, result[(id)kSecDataInetExtraHistory], "History field should be returned as data");
427 XCTAssertEqualObjects(client0, result[(id)kSecDataInetExtraClientDefined0], "Client Defined 0 field should be returned as data");
428 XCTAssertEqualObjects(client1, result[(id)kSecDataInetExtraClientDefined1], "Client Defined 1 field should be returned as data");
429 XCTAssertEqualObjects(client2, result[(id)kSecDataInetExtraClientDefined2], "Client Defined 2 field should be returned as data");
430 XCTAssertEqualObjects(client3, result[(id)kSecDataInetExtraClientDefined3], "Client Defined 3 field should be returned as data");
432 /* Copy with no limit, but with attributes and data */
433 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)queryFindAllWithAttributesAndData, &cfresult), errSecSuccess, "Should be able to find an item");
434 arrayResult = (NSArray*)CFBridgingRelease(cfresult);
435 XCTAssertNotNil(arrayResult, "Should have some sort of result");
436 XCTAssertTrue([arrayResult isKindOfClass:[NSArray class]], "Should have received an array back from copymatching");
437 XCTAssertEqual(arrayResult.count, 1, "Array should have one element");
438 result = arrayResult[0];
439 XCTAssertEqualObjects(note, result[(id)kSecDataInetExtraNotes], "Notes field should be returned as data");
440 XCTAssertEqualObjects(history, result[(id)kSecDataInetExtraHistory], "History field should be returned as data");
441 XCTAssertEqualObjects(client0, result[(id)kSecDataInetExtraClientDefined0], "Client Defined 0 field should be returned as data");
442 XCTAssertEqualObjects(client1, result[(id)kSecDataInetExtraClientDefined1], "Client Defined 1 field should be returned as data");
443 XCTAssertEqualObjects(client2, result[(id)kSecDataInetExtraClientDefined2], "Client Defined 2 field should be returned as data");
444 XCTAssertEqualObjects(client3, result[(id)kSecDataInetExtraClientDefined3], "Client Defined 3 field should be returned as data");
446 /* Copy just looking for the password */
447 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)@{
448 (id)kSecClass : (id)kSecClassInternetPassword,
449 (id)kSecUseDataProtectionKeychain : @YES,
450 (id)kSecReturnData: @YES,
451 (id)kSecAttrAccount : @"test-account",
452 }, &cfresult), errSecSuccess, "Should be able to find an item");
454 NSData* password = (NSData*)CFBridgingRelease(cfresult);
455 XCTAssertNotNil(password, "Should have some sort of password");
456 XCTAssertTrue([password isKindOfClass:[NSData class]], "Password is a data");
457 XCTAssertEqualObjects(originalPassword, password, "Should still be able to fetch the original password");
459 NSData* newHistoryContents = [@"gone" dataUsingEncoding:NSUTF8StringEncoding];
461 NSDictionary* updateQuery = @{
462 (id)kSecDataInetExtraHistory : newHistoryContents,
465 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)queryFind, (__bridge CFDictionaryRef)updateQuery), errSecSuccess, "Should be able to update a history field");
468 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)queryFindOneWithAttributesAndData, &cfresult), errSecSuccess, "Should be able to find an item");
469 result = (NSDictionary*)CFBridgingRelease(cfresult);
470 XCTAssertNotNil(result, "Should have some sort of result");
472 XCTAssertEqualObjects(note, result[(id)kSecDataInetExtraNotes], "Notes field should be returned as data");
473 XCTAssertEqualObjects(newHistoryContents, result[(id)kSecDataInetExtraHistory], "History field should be updated");
474 XCTAssertEqualObjects(client0, result[(id)kSecDataInetExtraClientDefined0], "Client Defined 0 field should be returned as data");
475 XCTAssertEqualObjects(client1, result[(id)kSecDataInetExtraClientDefined1], "Client Defined 1 field should be returned as data");
476 XCTAssertEqualObjects(client2, result[(id)kSecDataInetExtraClientDefined2], "Client Defined 2 field should be returned as data");
477 XCTAssertEqualObjects(client3, result[(id)kSecDataInetExtraClientDefined3], "Client Defined 3 field should be returned as data");
480 // When this test starts failing, hopefully rdar://problem/60332379 got fixed
481 - (void)testBadDateCausesDERDecodeValidationError {
482 // Wonky time calculation hastily stolen from SecGregorianDateGetAbsoluteTime and tweaked
483 // As of right now this causes CFCalendarDecomposeAbsoluteTime with Zulu calendar to give a seemingly incorrect date which then causes DER date validation issues
484 CFAbsoluteTime absTime = (CFAbsoluteTime)(((-(1902 * 365) + -38) * 24 + 0) * 60 + -1) * 60 + 1;
485 absTime -= 0.0004; // Just to make sure the nanoseconds keep getting encoded/decoded properly
486 CFDateRef date = CFDateCreate(NULL, absTime);
488 CFErrorRef error = NULL;
489 size_t plistSize = der_sizeof_plist(date, &error);
490 XCTAssert(error == NULL);
491 XCTAssertGreaterThan(plistSize, 0);
493 // Encode without repair does not validate dates because that changes behavior I do not want to fiddle with
494 uint8_t* der = calloc(1, plistSize);
495 uint8_t* der_end = der + plistSize;
496 uint8_t* result = der_encode_plist(date, &error, der, der_end);
497 XCTAssert(error == NULL);
498 XCTAssertEqual(der, result);
500 // ...but decoding does and will complain
501 CFPropertyListRef decoded = NULL;
502 XCTAssert(der_decode_plist(NULL, &decoded, &error, der, der_end) == NULL);
503 XCTAssert(error != NULL);
504 XCTAssertEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus);
505 NSString* description = CFBridgingRelease(CFErrorCopyDescription(error));
506 XCTAssert([description containsString:@"Invalid date"]);
508 CFReleaseNull(error);
512 // When this test starts failing, hopefully rdar://problem/60332379 got fixed
513 - (void)testBadDateWithDEREncodingRepairProducesDefaultValue {
514 // Wonky time calculation hastily stolen from SecGregorianDateGetAbsoluteTime and tweaked
515 // As of right now this causes CFCalendarDecomposeAbsoluteTime with Zulu calendar to give a seemingly incorrect date which then causes DER date validation issues
516 CFAbsoluteTime absTime = (CFAbsoluteTime)(((-(1902 * 365) + -38) * 24 + 0) * 60 + -1) * 60 + 1;
517 absTime -= 0.0004; // Just to make sure the nanoseconds keep getting encoded/decoded properly
518 CFDateRef date = CFDateCreate(NULL, absTime);
520 CFErrorRef error = NULL;
521 size_t plistSize = der_sizeof_plist(date, &error);
522 XCTAssert(error == NULL);
523 XCTAssertGreaterThan(plistSize, 0);
525 uint8_t* der = calloc(1, plistSize);
526 uint8_t* der_end = der + plistSize;
527 uint8_t* encoderesult = der_encode_plist_repair(date, &error, true, der, der_end);
528 XCTAssert(error == NULL);
529 XCTAssertEqual(der, encoderesult);
531 CFPropertyListRef decoded = NULL;
532 const uint8_t* decoderesult = der_decode_plist(NULL, &decoded, &error, der, der_end);
533 XCTAssertEqual(der_end, decoderesult);
534 XCTAssertEqual(CFGetTypeID(decoded), CFDateGetTypeID());
535 XCTAssertEqualWithAccuracy(CFDateGetAbsoluteTime(decoded), 0, 60 * 60 * 24);
538 #pragma mark - SecItemRateLimit
540 // This is not super accurate in BATS, so put some margin around what you need
541 - (void)sleepAlternativeForXCTest:(double)interval
543 dispatch_semaphore_t localsema = dispatch_semaphore_create(0);
544 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * interval), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
545 dispatch_semaphore_signal(localsema);
547 dispatch_semaphore_wait(localsema, DISPATCH_TIME_FOREVER);
550 - (void)testSecItemRateLimitTimePasses {
551 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
552 [rl forceEnabled: true];
554 for (int idx = 0; idx < rl.roCapacity; ++idx) {
555 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
558 for (int idx = 0; idx < rl.rwCapacity; ++idx) {
559 XCTAssertTrue(isModifyingAPIRateWithinLimits());
562 [self sleepAlternativeForXCTest: 2];
563 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
564 XCTAssertTrue(isModifyingAPIRateWithinLimits());
566 [SecItemRateLimit resetStaticRateLimit];
569 - (void)testSecItemRateLimitResetAfterExceed {
570 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
571 [rl forceEnabled: true];
573 for (int idx = 0; idx < rl.roCapacity; ++idx) {
574 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
576 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
577 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
579 for (int idx = 0; idx < rl.rwCapacity; ++idx) {
580 XCTAssertTrue(isModifyingAPIRateWithinLimits());
582 XCTAssertFalse(isModifyingAPIRateWithinLimits());
583 XCTAssertTrue(isModifyingAPIRateWithinLimits());
585 [SecItemRateLimit resetStaticRateLimit];
588 - (void)testSecItemRateLimitMultiplier {
589 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
590 [rl forceEnabled: true];
592 int ro_iterations_before = 0;
593 for (; ro_iterations_before < rl.roCapacity; ++ro_iterations_before) {
594 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
596 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
598 int rw_iterations_before = 0;
599 for (; rw_iterations_before < rl.rwCapacity; ++rw_iterations_before) {
600 XCTAssertTrue(isModifyingAPIRateWithinLimits());
602 XCTAssertFalse(isModifyingAPIRateWithinLimits());
605 int ro_iterations_after = 0;
606 for (; ro_iterations_after < rl.roCapacity; ++ro_iterations_after) {
607 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
609 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
611 int rw_iterations_after = 0;
612 for (; rw_iterations_after < rl.rwCapacity; ++rw_iterations_after) {
613 XCTAssertTrue(isModifyingAPIRateWithinLimits());
615 XCTAssertFalse(isModifyingAPIRateWithinLimits());
617 XCTAssertEqualWithAccuracy(rl.limitMultiplier * ro_iterations_before, ro_iterations_after, 1);
618 XCTAssertEqualWithAccuracy(rl.limitMultiplier * rw_iterations_before, rw_iterations_after, 1);
619 [SecItemRateLimit resetStaticRateLimit];
622 // We stipulate that this test is run on an internal release.
623 // If this were a platform binary limits would be enforced, but it should not be so they should not.
624 - (void)testSecItemRateLimitInternalPlatformBinariesOnly {
625 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
627 for (int idx = 0; idx < 3 * MAX(rl.roCapacity, rl.rwCapacity); ++idx) {
628 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
629 XCTAssertTrue(isModifyingAPIRateWithinLimits());
632 [SecItemRateLimit resetStaticRateLimit];