]> git.saurik.com Git - apple/security.git/blob - secdxctests/KeychainAPITests.m
Security-59754.41.1.tar.gz
[apple/security.git] / secdxctests / KeychainAPITests.m
1 /*
2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "KeychainXCTest.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
27 #import "CKKS.h"
28 #import "SecDbKeychainItemV7.h"
29 #import "SecItemPriv.h"
30 #include "SecItemInternal.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 <XCTest/XCTest.h>
40 #import <OCMock/OCMock.h>
41 #include <dispatch/dispatch.h>
42 #include <utilities/SecDb.h>
43 #include <sys/stat.h>
44 #include <utilities/SecFileLocations.h>
45 #include "der_plist.h"
46 #import "SecItemRateLimit_tests.h"
47
48 #if USE_KEYSTORE
49
50 @interface KeychainAPITests : KeychainXCTest
51 @end
52
53 @implementation KeychainAPITests
54
55 + (void)setUp
56 {
57 [super setUp];
58 }
59
60 - (NSString*)nameOfTest
61 {
62 return [self.name componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ]"]][1];
63 }
64
65 - (void)setUp
66 {
67 [super setUp];
68 // KeychainXCTest already sets up keychain with custom test-named directory
69 }
70
71 - (void)testReturnValuesInSecItemUpdate
72 {
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)
79 };
80
81 NSDictionary* updateQueryWithNoReturn = @{ (id)kSecClass : (id)kSecClassGenericPassword,
82 (id)kSecAttrAccount : @"TestAccount",
83 (id)kSecAttrService : @"TestService",
84 (id)kSecUseDataProtectionKeychain : @(YES)
85 };
86
87 CFTypeRef result = NULL;
88
89 // Add the item
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);
93
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");
96
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
100
101 #if TARGET_OS_OSX
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");
105
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");
109
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");
113
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");
117 #else
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");
121
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");
125
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");
129
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");
133 #endif
134 }
135
136 - (void)testBadTypeInParams
137 {
138 NSMutableDictionary *attrs = @{
139 (id)kSecClass: (id)kSecClassGenericPassword,
140 (id)kSecUseDataProtectionKeychain: @YES,
141 (id)kSecAttrLabel: @"testentry",
142 }.mutableCopy;
143
144 SecItemDelete((CFDictionaryRef)attrs);
145 XCTAssertEqual(errSecSuccess, SecItemAdd((CFDictionaryRef)attrs, NULL));
146 XCTAssertEqual(errSecSuccess, SecItemDelete((CFDictionaryRef)attrs));
147
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));
152 }
153
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],
160 key : value,
161 };
162 NSDictionary* badupdate = @{key : value};
163
164 NSDictionary* okquery = @{
165 (id)kSecClass : (id)kSecClassGenericPassword,
166 (id)kSecAttrService : @"AppClipTestService",
167 (id)kSecUseDataProtectionKeychain : @YES,
168 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
169 };
170 NSDictionary* okupdate = @{(id)kSecAttrService : @"DifferentService"};
171
172 if (SecItemAdd((__bridge CFDictionaryRef)badquery, NULL) != errSecParam) {
173 XCTFail("SecItemAdd did not return errSecParam");
174 return NO;
175 }
176 if (SecItemCopyMatching((__bridge CFDictionaryRef)badquery, NULL) != errSecParam) {
177 XCTFail("SecItemCopyMatching did not return errSecParam");
178 return NO;
179 }
180 if (SecItemUpdate((__bridge CFDictionaryRef)badquery, (__bridge CFDictionaryRef)okupdate) != errSecParam) {
181 XCTFail("SecItemUpdate with bad query did not return errSecParam");
182 return NO;
183 }
184 if (SecItemUpdate((__bridge CFDictionaryRef)okquery, (__bridge CFDictionaryRef)badupdate) != errSecParam) {
185 XCTFail("SecItemUpdate with bad update did not return errSecParam");
186 return NO;
187 }
188 if (SecItemDelete((__bridge CFDictionaryRef)badquery) != errSecParam) {
189 XCTFail("SecItemDelete did not return errSecParam");
190 return NO;
191 }
192 return YES;
193 }
194
195 // Expand this, rdar://problem/59297616
196 - (void)testNotAllowedToPassInternalAttributes {
197 XCTAssert([self passInternalAttributeToKeychainAPIsWithKey:(__bridge NSString*)kSecAttrAppClipItem value:@YES], @"Expect errSecParam for 'clip' attribute");
198 }
199
200 #pragma mark - Corruption Tests
201
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
218 };
219
220 dispatch_semaphore_t sema = NULL;
221
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)
225 {
226 dispatch_semaphore_signal(sema);
227 }
228
229 - (void)testCorruptionHandler {
230 __security_simulatecrash_enable(false);
231
232 SecDbCorruptionExitHandler = SecDbTestCorruptionHandler;
233 sema = dispatch_semaphore_create(0);
234
235 secd_test_setup_temp_keychain([[NSString stringWithFormat:@"%@-bad", [self nameOfTest]] UTF8String], ^{
236 CFStringRef keychain_path_cf = __SecKeychainCopyPath();
237
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);
241
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);
245 });
246
247 CFReleaseNull(keychain_path_cf);
248 });
249
250 NSDictionary* query = @{(id)kSecClass : (id)kSecClassGenericPassword,
251 (id)kSecAttrAccount : @"TestAccount",
252 (id)kSecAttrService : @"TestService",
253 (id)kSecUseDataProtectionKeychain : @(YES),
254 (id)kSecReturnAttributes : @(YES)
255 };
256
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");
261
262 sema = NULL;
263 SecDbResetCorruptionExitHandler();
264 CFReleaseNull(result);
265
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);
269 }
270
271 - (void)testRecoverFromCorruption {
272 __security_simulatecrash_enable(false);
273
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");
279 fclose(file);
280 XCTAssertEqual(stat(filename, &before), 0, "Unable to stat newly created file");
281 });
282
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");
286 fclose(file);
287 });
288
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)
295 } mutableCopy];
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);
300
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);
305
306 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess, @"Should have deleted item from keychain");
307
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");
311 });
312
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");
317 fclose(file);
318 XCTAssertEqual(stat(filename, &after), 0, "Unable to stat newly created file");
319 });
320
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");
323 } else {
324 XCTAssertLessThan(before.st_birthtimespec.tv_sec, after.st_birthtimespec.tv_sec, "db was not deleted and recreated");
325 }
326 }
327
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];
335
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,
351
352 (id)kSecReturnAttributes : @YES,
353 } mutableCopy];
354
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");
359
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");
362
363 NSDictionary* queryFind = @{
364 (id)kSecClass : (id)kSecClassInternetPassword,
365 (id)kSecUseDataProtectionKeychain : @YES,
366 (id)kSecAttrAccount : @"test-account",
367 };
368
369 NSMutableDictionary* queryFindOneWithJustAttributes = [[NSMutableDictionary alloc] initWithDictionary:queryFind];
370 queryFindOneWithJustAttributes[(id)kSecReturnAttributes] = @YES;
371
372 NSMutableDictionary* queryFindAllWithJustAttributes = [[NSMutableDictionary alloc] initWithDictionary:queryFindOneWithJustAttributes];
373 queryFindAllWithJustAttributes[(id)kSecMatchLimit] = (id)kSecMatchLimitAll;
374
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",
381 };
382
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",
390 };
391
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");
394
395 result = (NSDictionary*)CFBridgingRelease(cfresult);
396 XCTAssertNotNil(result, "Should have some sort of result");
397
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");
404
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");
411
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");
419
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");
424
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");
431
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");
445
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");
453
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");
458
459 NSData* newHistoryContents = [@"gone" dataUsingEncoding:NSUTF8StringEncoding];
460
461 NSDictionary* updateQuery = @{
462 (id)kSecDataInetExtraHistory : newHistoryContents,
463 };
464
465 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)queryFind, (__bridge CFDictionaryRef)updateQuery), errSecSuccess, "Should be able to update a history field");
466
467 // And find it again
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");
471
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");
478 }
479
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);
487
488 CFErrorRef error = NULL;
489 size_t plistSize = der_sizeof_plist(date, &error);
490 XCTAssert(error == NULL);
491 XCTAssertGreaterThan(plistSize, 0);
492
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);
499
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"]);
507
508 CFReleaseNull(error);
509 free(der);
510 }
511
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);
519
520 CFErrorRef error = NULL;
521 size_t plistSize = der_sizeof_plist(date, &error);
522 XCTAssert(error == NULL);
523 XCTAssertGreaterThan(plistSize, 0);
524
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);
530
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);
536 }
537
538 #pragma mark - SecItemRateLimit
539
540 // This is not super accurate in BATS, so put some margin around what you need
541 - (void)sleepAlternativeForXCTest:(double)interval
542 {
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);
546 });
547 dispatch_semaphore_wait(localsema, DISPATCH_TIME_FOREVER);
548 }
549
550 - (void)testSecItemRateLimitTimePasses {
551 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
552 [rl forceEnabled: true];
553
554 for (int idx = 0; idx < rl.roCapacity; ++idx) {
555 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
556 }
557
558 for (int idx = 0; idx < rl.rwCapacity; ++idx) {
559 XCTAssertTrue(isModifyingAPIRateWithinLimits());
560 }
561
562 [self sleepAlternativeForXCTest: 2];
563 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
564 XCTAssertTrue(isModifyingAPIRateWithinLimits());
565
566 [SecItemRateLimit resetStaticRateLimit];
567 }
568
569 - (void)testSecItemRateLimitResetAfterExceed {
570 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
571 [rl forceEnabled: true];
572
573 for (int idx = 0; idx < rl.roCapacity; ++idx) {
574 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
575 }
576 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
577 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
578
579 for (int idx = 0; idx < rl.rwCapacity; ++idx) {
580 XCTAssertTrue(isModifyingAPIRateWithinLimits());
581 }
582 XCTAssertFalse(isModifyingAPIRateWithinLimits());
583 XCTAssertTrue(isModifyingAPIRateWithinLimits());
584
585 [SecItemRateLimit resetStaticRateLimit];
586 }
587
588 - (void)testSecItemRateLimitMultiplier {
589 SecItemRateLimit* rl = [SecItemRateLimit getStaticRateLimit];
590 [rl forceEnabled: true];
591
592 int ro_iterations_before = 0;
593 for (; ro_iterations_before < rl.roCapacity; ++ro_iterations_before) {
594 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
595 }
596 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
597
598 int rw_iterations_before = 0;
599 for (; rw_iterations_before < rl.rwCapacity; ++rw_iterations_before) {
600 XCTAssertTrue(isModifyingAPIRateWithinLimits());
601 }
602 XCTAssertFalse(isModifyingAPIRateWithinLimits());
603
604
605 int ro_iterations_after = 0;
606 for (; ro_iterations_after < rl.roCapacity; ++ro_iterations_after) {
607 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
608 }
609 XCTAssertFalse(isReadOnlyAPIRateWithinLimits());
610
611 int rw_iterations_after = 0;
612 for (; rw_iterations_after < rl.rwCapacity; ++rw_iterations_after) {
613 XCTAssertTrue(isModifyingAPIRateWithinLimits());
614 }
615 XCTAssertFalse(isModifyingAPIRateWithinLimits());
616
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];
620 }
621
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];
626
627 for (int idx = 0; idx < 3 * MAX(rl.roCapacity, rl.rwCapacity); ++idx) {
628 XCTAssertTrue(isReadOnlyAPIRateWithinLimits());
629 XCTAssertTrue(isModifyingAPIRateWithinLimits());
630 }
631
632 [SecItemRateLimit resetStaticRateLimit];
633 }
634
635 @end
636
637 #endif