]> git.saurik.com Git - apple/security.git/blob - secdxctests/KeychainAPITests.m
Security-59306.101.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 #import "SecItemServer.h"
31 #import "spi.h"
32 #import "SecDbKeychainSerializedItemV7.h"
33 #import "SecDbKeychainSerializedMetadata.h"
34 #import "SecDbKeychainSerializedSecretData.h"
35 #import "SecDbKeychainSerializedAKSWrappedKey.h"
36 #import <utilities/SecCFWrappers.h>
37 #import <SecurityFoundation/SFEncryptionOperation.h>
38 #import <XCTest/XCTest.h>
39 #import <OCMock/OCMock.h>
40 #include <dispatch/dispatch.h>
41 #include <utilities/SecDb.h>
42 #include <sys/stat.h>
43 #include <utilities/SecFileLocations.h>
44
45 void* testlist = NULL;
46
47 #if USE_KEYSTORE
48
49 @interface KeychainAPITests : KeychainXCTest
50 @end
51
52 @implementation KeychainAPITests
53
54 + (void)setUp
55 {
56 [super setUp];
57 SecCKKSDisable();
58 securityd_init(NULL);
59 }
60
61 - (NSString*)nameOfTest
62 {
63 return [self.name componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" ]"]][1];
64 }
65
66 - (void)setUp
67 {
68 [super setUp];
69 // KeychainXCTest already sets up keychain with custom test-named directory
70 }
71
72 - (void)testReturnValuesInSecItemUpdate
73 {
74 NSDictionary* addQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
75 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
76 (id)kSecAttrAccount : @"TestAccount",
77 (id)kSecAttrService : @"TestService",
78 (id)kSecUseDataProtectionKeychain : @(YES),
79 (id)kSecReturnAttributes : @(YES)
80 };
81
82 NSDictionary* updateQueryWithNoReturn = @{ (id)kSecClass : (id)kSecClassGenericPassword,
83 (id)kSecAttrAccount : @"TestAccount",
84 (id)kSecAttrService : @"TestService",
85 (id)kSecUseDataProtectionKeychain : @(YES)
86 };
87
88 CFTypeRef result = NULL;
89
90 // Add the item
91 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)addQuery, &result), errSecSuccess, @"Should have succeeded in adding test item to keychain");
92 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
93 CFReleaseNull(result);
94
95 // And we can update the item
96 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithNoReturn, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"otherpassword" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with clean update query");
97
98 // great, a normal update works
99 // now let's do updates with various queries which include return parameters to ensure they succeed on macOS and throw errors on iOS.
100 // 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
101
102 #if TARGET_OS_OSX
103 NSMutableDictionary* updateQueryWithReturnAttributes = updateQueryWithNoReturn.mutableCopy;
104 updateQueryWithReturnAttributes[(id)kSecReturnAttributes] = @(YES);
105 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnAttributes, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-attributes" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return attributes query");
106
107 NSMutableDictionary* updateQueryWithReturnData = updateQueryWithNoReturn.mutableCopy;
108 updateQueryWithReturnAttributes[(id)kSecReturnData] = @(YES);
109 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnData, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-data" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return data query");
110
111 NSMutableDictionary* updateQueryWithReturnRef = updateQueryWithNoReturn.mutableCopy;
112 updateQueryWithReturnAttributes[(id)kSecReturnRef] = @(YES);
113 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)updateQueryWithReturnRef, (__bridge CFDictionaryRef)@{(id)kSecValueData: [@"return-ref" dataUsingEncoding:NSUTF8StringEncoding]}), errSecSuccess, "failed to update item with return ref query");
114
115 NSMutableDictionary* updateQueryWithReturnPersistentRef = updateQueryWithNoReturn.mutableCopy;
116 updateQueryWithReturnAttributes[(id)kSecReturnPersistentRef] = @(YES);
117 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 #else
119 NSMutableDictionary* updateQueryWithReturnAttributes = updateQueryWithNoReturn.mutableCopy;
120 updateQueryWithReturnAttributes[(id)kSecReturnAttributes] = @(YES);
121 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
123 NSMutableDictionary* updateQueryWithReturnData = updateQueryWithNoReturn.mutableCopy;
124 updateQueryWithReturnData[(id)kSecReturnData] = @(YES);
125 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
127 NSMutableDictionary* updateQueryWithReturnRef = updateQueryWithNoReturn.mutableCopy;
128 updateQueryWithReturnRef[(id)kSecReturnRef] = @(YES);
129 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
131 NSMutableDictionary* updateQueryWithReturnPersistentRef = updateQueryWithNoReturn.mutableCopy;
132 updateQueryWithReturnPersistentRef[(id)kSecReturnPersistentRef] = @(YES);
133 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");
134 #endif
135 }
136
137 - (void)testBadTypeInParams
138 {
139 NSMutableDictionary *attrs = @{
140 (id)kSecClass: (id)kSecClassGenericPassword,
141 (id)kSecUseDataProtectionKeychain: @YES,
142 (id)kSecAttrLabel: @"testentry",
143 }.mutableCopy;
144
145 SecItemDelete((CFDictionaryRef)attrs);
146 XCTAssertEqual(errSecSuccess, SecItemAdd((CFDictionaryRef)attrs, NULL));
147 XCTAssertEqual(errSecSuccess, SecItemDelete((CFDictionaryRef)attrs));
148
149 // We try to fool SecItem API with unexpected type of kSecAttrAccessControl attribute in query and it should not crash.
150 attrs[(id)kSecAttrAccessControl] = @"string, no SecAccessControlRef!";
151 XCTAssertEqual(errSecParam, SecItemAdd((CFDictionaryRef)attrs, NULL));
152 XCTAssertEqual(errSecParam, SecItemDelete((CFDictionaryRef)attrs));
153 }
154
155 #pragma mark - Corruption Tests
156
157 const uint8_t keychain_data[] = {
158 0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x01, 0x02, 0x03,
159 0x04, 0x5f, 0x10, 0x1b, 0x4e, 0x53, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77,
160 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x63, 0x65,
161 0x73, 0x73, 0x50, 0x61, 0x6e, 0x65, 0x6c, 0x5f, 0x10, 0x1d, 0x4e, 0x53,
162 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x20, 0x46, 0x72, 0x61, 0x6d, 0x65,
163 0x20, 0x41, 0x62, 0x6f, 0x75, 0x74, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20,
164 0x4d, 0x61, 0x63, 0x5f, 0x10, 0x1c, 0x32, 0x38, 0x20, 0x33, 0x37, 0x33,
165 0x20, 0x33, 0x34, 0x36, 0x20, 0x32, 0x39, 0x30, 0x20, 0x30, 0x20, 0x30,
166 0x20, 0x31, 0x34, 0x34, 0x30, 0x20, 0x38, 0x37, 0x38, 0x20, 0x5f, 0x10,
167 0x1d, 0x35, 0x36, 0x38, 0x20, 0x33, 0x39, 0x35, 0x20, 0x33, 0x30, 0x37,
168 0x20, 0x33, 0x37, 0x39, 0x20, 0x30, 0x20, 0x30, 0x20, 0x31, 0x34, 0x34,
169 0x30, 0x20, 0x38, 0x37, 0x38, 0x20, 0x08, 0x0d, 0x2b, 0x4b, 0x6a, 0x00,
170 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
171 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
172 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8a
173 };
174
175 dispatch_semaphore_t sema = NULL;
176
177 // The real corruption exit handler should xpc_transaction_exit_clean,
178 // let's be certain it does not. Also make sure exit handler gets called at all
179 static void SecDbTestCorruptionHandler(void)
180 {
181 dispatch_semaphore_signal(sema);
182 }
183
184 - (void)testCorruptionHandler {
185 __security_simulatecrash_enable(false);
186
187 SecDbCorruptionExitHandler = SecDbTestCorruptionHandler;
188 sema = dispatch_semaphore_create(0);
189
190 secd_test_setup_temp_keychain([[NSString stringWithFormat:@"%@-bad", [self nameOfTest]] UTF8String], ^{
191 CFStringRef keychain_path_cf = __SecKeychainCopyPath();
192
193 CFStringPerformWithCString(keychain_path_cf, ^(const char *keychain_path) {
194 int fd = open(keychain_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
195 XCTAssert(fd > -1, "Could not open fd to write keychain: %{darwin.errno}d", errno);
196
197 size_t written = write(fd, keychain_data, sizeof(keychain_data));
198 XCTAssertEqual(written, sizeof(keychain_data), "Write garbage to disk, got %lu instead of %lu: %{darwin.errno}d", written, sizeof(keychain_data), errno);
199 XCTAssertEqual(close(fd), 0, "Close keychain file failed: %{darwin.errno}d", errno);
200 });
201
202 CFReleaseNull(keychain_path_cf);
203 });
204
205 NSDictionary* query = @{(id)kSecClass : (id)kSecClassGenericPassword,
206 (id)kSecAttrAccount : @"TestAccount",
207 (id)kSecAttrService : @"TestService",
208 (id)kSecUseDataProtectionKeychain : @(YES),
209 (id)kSecReturnAttributes : @(YES)
210 };
211
212 CFTypeRef result = NULL;
213 // Real keychain should xpc_transaction_exit_clean() after this, but we nerfed it
214 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), errSecNotAvailable, "Expected badness from corrupt keychain");
215 XCTAssertEqual(dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC)), 0, "Timed out waiting for corruption exit handler");
216
217 sema = NULL;
218 SecDbResetCorruptionExitHandler();
219 CFReleaseNull(result);
220
221 NSString* markerpath = [NSString stringWithFormat:@"%@-iscorrupt", CFBridgingRelease(__SecKeychainCopyPath())];
222 struct stat info = {};
223 XCTAssertEqual(stat([markerpath UTF8String], &info), 0, "Unable to stat corruption marker: %{darwin.errno}d", errno);
224 }
225
226 - (void)testRecoverFromCorruption {
227 __security_simulatecrash_enable(false);
228
229 // Setup does a reset, but that doesn't create the db yet so let's sneak in first
230 __block struct stat before = {};
231 WithPathInKeychainDirectory(CFSTR("keychain-2.db"), ^(const char *filename) {
232 FILE* file = fopen(filename, "w");
233 XCTAssert(file != NULL, "Didn't get a FILE pointer");
234 fclose(file);
235 XCTAssertEqual(stat(filename, &before), 0, "Unable to stat newly created file");
236 });
237
238 WithPathInKeychainDirectory(CFSTR("keychain-2.db-iscorrupt"), ^(const char *filename) {
239 FILE* file = fopen(filename, "w");
240 XCTAssert(file != NULL, "Didn't get a FILE pointer");
241 fclose(file);
242 });
243
244 NSMutableDictionary* query = [@{(id)kSecClass : (id)kSecClassGenericPassword,
245 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
246 (id)kSecAttrAccount : @"TestAccount",
247 (id)kSecAttrService : @"TestService",
248 (id)kSecUseDataProtectionKeychain : @(YES),
249 (id)kSecReturnAttributes : @(YES)
250 } mutableCopy];
251 CFTypeRef result = NULL;
252 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, &result), errSecSuccess, @"Should have added item to keychain");
253 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemAdd");
254 CFReleaseNull(result);
255
256 query[(id)kSecValueData] = nil;
257 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, &result), errSecSuccess, @"Should have found item in keychain");
258 XCTAssertNotNil((__bridge id)result, @"Should have received a dictionary back from SecItemCopyMatching");
259 CFReleaseNull(result);
260
261 XCTAssertEqual(SecItemDelete((__bridge CFDictionaryRef)query), errSecSuccess, @"Should have deleted item from keychain");
262
263 WithPathInKeychainDirectory(CFSTR("keychain-2.db-iscorrupt"), ^(const char *filename) {
264 struct stat markerinfo = {};
265 XCTAssertNotEqual(stat(filename, &markerinfo), 0, "Expected not to find corruption marker after killing keychain");
266 });
267
268 __block struct stat after = {};
269 WithPathInKeychainDirectory(CFSTR("keychain-2.db"), ^(const char *filename) {
270 FILE* file = fopen(filename, "w");
271 XCTAssert(file != NULL, "Didn't get a FILE pointer");
272 fclose(file);
273 XCTAssertEqual(stat(filename, &after), 0, "Unable to stat newly created file");
274 });
275
276 if (before.st_birthtimespec.tv_sec == after.st_birthtimespec.tv_sec) {
277 XCTAssertLessThan(before.st_birthtimespec.tv_nsec, after.st_birthtimespec.tv_nsec, "db was not deleted and recreated");
278 } else {
279 XCTAssertLessThan(before.st_birthtimespec.tv_sec, after.st_birthtimespec.tv_sec, "db was not deleted and recreated");
280 }
281 }
282
283 @end
284
285 #endif