]> git.saurik.com Git - apple/security.git/blob - secdxctests/SFCredentialStoreTests.m
Security-59306.61.1.tar.gz
[apple/security.git] / secdxctests / SFCredentialStoreTests.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 "SFKeychainServer.h"
26 #import "SecCDKeychain.h"
27 #import "SecFileLocations.h"
28 #import <Foundation/Foundation.h>
29 #import <Foundation/NSXPCConnection_Private.h>
30 #import <XCTest/XCTest.h>
31 #import <SecurityFoundation/SFKeychain.h>
32 #import <OCMock/OCMock.h>
33
34 #if USE_KEYSTORE
35
36 @interface SFCredentialStore (UnitTestingForwardDeclarations)
37
38 - (instancetype)_init;
39
40 - (id<NSXPCProxyCreating>)_serverConnectionWithError:(NSError**)error;
41
42 @end
43
44 @interface SFKeychainServer (UnitTestingForwardDeclarations)
45
46 @property (readonly, getter=_keychain) SecCDKeychain* keychain;
47
48 @end
49
50 @interface SFKeychainServerConnection (UnitTestingRedeclarations)
51
52 - (instancetype)initWithKeychain:(SecCDKeychain*)keychain xpcConnection:(NSXPCConnection*)connection;
53
54 @end
55
56 @interface SecCDKeychain (UnitTestingRedeclarations)
57
58 - (NSData*)_onQueueGetDatabaseKeyDataWithError:(NSError**)error;
59
60 @end
61
62 @interface KeychainNoXPCServerProxy : NSObject <NSXPCProxyCreating>
63
64 @property (readonly) SFKeychainServer* server;
65
66 @end
67
68 @implementation KeychainNoXPCServerProxy {
69 SFKeychainServer* _server;
70 SFKeychainServerFakeConnection* _connection;
71 }
72
73 @synthesize server = _server;
74
75 - (instancetype)init
76 {
77 if (self = [super init]) {
78 NSURL* persistentStoreURL = (__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"CDKeychain");
79 NSBundle* resourcesBundle = [NSBundle bundleWithPath:@"/System/Library/Keychain/KeychainResources.bundle"];
80 NSURL* managedObjectModelURL = [resourcesBundle URLForResource:@"KeychainModel" withExtension:@"momd"];
81 _server = [[SFKeychainServer alloc] initWithStorageURL:persistentStoreURL modelURL:managedObjectModelURL encryptDatabase:false];
82 _connection = [[SFKeychainServerFakeConnection alloc] initWithKeychain:_server.keychain xpcConnection:nil];
83 }
84
85 return self;
86 }
87
88 - (id)remoteObjectProxy
89 {
90 return _server;
91 }
92
93 - (id)remoteObjectProxyWithErrorHandler:(void (^)(NSError*))handler
94 {
95 return _connection;
96 }
97
98 @end
99
100 @interface SFCredentialStoreTests : KeychainXCTest
101 @end
102
103 @implementation SFCredentialStoreTests {
104 SFCredentialStore* _credentialStore;
105 }
106
107 + (void)setUp
108 {
109 [super setUp];
110
111 id credentialStoreMock = OCMClassMock([SFCredentialStore class]);
112 [[[[credentialStoreMock stub] andCall:@selector(serverProxyWithError:) onObject:self] ignoringNonObjectArgs] _serverConnectionWithError:NULL];
113 }
114
115 + (id)serverProxyWithError:(NSError**)error
116 {
117 return [[KeychainNoXPCServerProxy alloc] init];
118 }
119
120 - (void)setUp
121 {
122 [super setUp];
123 self.keychainPartialMock = OCMPartialMock([(SFKeychainServer*)[[self.class serverProxyWithError:nil] server] _keychain]);
124 [[[[self.keychainPartialMock stub] andCall:@selector(getDatabaseKeyDataithError:) onObject:self] ignoringNonObjectArgs] _onQueueGetDatabaseKeyDataWithError:NULL];
125
126 _credentialStore = [[SFCredentialStore alloc] _init];
127 }
128
129 - (BOOL)passwordCredential:(SFPasswordCredential*)firstCredential matchesCredential:(SFPasswordCredential*)secondCredential
130 {
131 return [firstCredential.primaryServiceIdentifier isEqual:secondCredential.primaryServiceIdentifier] &&
132 [[NSSet setWithArray:firstCredential.supplementaryServiceIdentifiers] isEqualToSet:[NSSet setWithArray:secondCredential.supplementaryServiceIdentifiers]] &&
133 [firstCredential.localizedLabel isEqualToString:secondCredential.localizedLabel] &&
134 [firstCredential.localizedDescription isEqualToString:secondCredential.localizedDescription] &&
135 [firstCredential.customAttributes isEqualToDictionary:secondCredential.customAttributes];
136 }
137
138 #pragma clang diagnostic push
139 #pragma clang diagnostic ignored "-Warc-retain-cycles"
140 // we don't care about creating retain cycles inside our testing blocks (they get broken properly anyway)
141
142 - (void)testAddAndFetchCredential
143 {
144 SFPasswordCredential* credential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"TestPass" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
145 __block NSString* credentialIdentifier = nil;
146
147 XCTestExpectation* addExpectation = [self expectationWithDescription:@"add credential"];
148 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
149 credentialIdentifier = persistentIdentifier;
150 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
151 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
152 [addExpectation fulfill];
153 }];
154 [self waitForExpectations:@[addExpectation] timeout:5.0];
155
156 XCTestExpectation* fetchExpecation = [self expectationWithDescription:@"fetch credential"];
157 [_credentialStore fetchPasswordCredentialForPersistentIdentifier:credentialIdentifier withResultHandler:^(SFPasswordCredential* fetchedCredential, NSString* password, NSError* error) {
158 XCTAssertNotNil(fetchedCredential, @"failed to fetch credential just added to store");
159 XCTAssertNil(error, @"received unexpected error fetching credential from store: %@", error);
160 XCTAssertTrue([self passwordCredential:credential matchesCredential:fetchedCredential], @"the credential we fetched from the store does not match the one we added");
161 XCTAssertEqualObjects(password, @"TestPass", @"the password we fetched from the store does not match the one we added");
162 [fetchExpecation fulfill];
163 }];
164 [self waitForExpectations:@[fetchExpecation] timeout:5.0];
165 }
166
167 - (void)testLookupCredential
168 {
169 SFPasswordCredential* credential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"TestPass" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
170
171 XCTestExpectation* addExpectation = [self expectationWithDescription:@"add credential"];
172 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
173 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
174 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
175 [addExpectation fulfill];
176 }];
177 [self waitForExpectations:@[addExpectation] timeout:5.0];
178
179 SFServiceIdentifier* serviceIdentifier = [SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"];
180 if (!serviceIdentifier) {
181 XCTAssertTrue(false, @"Failed to create a service identifier; aborting test");
182 return;
183 }
184
185 XCTestExpectation* lookupExpecation = [self expectationWithDescription:@"lookup credential"];
186 [_credentialStore lookupCredentialsForServiceIdentifiers:@[serviceIdentifier] withResultHandler:^(NSArray<SFCredential*>* results, NSError* error) {
187 XCTAssertEqual((int)results.count, 1, @"error looking up credentials with service identifiers; expected 1 result but got %d", (int)results.count);
188 XCTAssertTrue([self passwordCredential:credential matchesCredential:(SFPasswordCredential*)results.firstObject], @"the credential we looked up does not match the one we added");
189 [lookupExpecation fulfill];
190 }];
191 [self waitForExpectations:@[lookupExpecation] timeout:5.0];
192 }
193
194 - (void)testAddDuplicateCredential
195 {
196 SFPasswordCredential* credential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"TestPass" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
197
198 XCTestExpectation* addExpectation = [self expectationWithDescription:@"add credential"];
199 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
200 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
201 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
202 [addExpectation fulfill];
203 }];
204 [self waitForExpectations:@[addExpectation] timeout:5.0];
205
206 XCTestExpectation* conflictingAddExpectation = [self expectationWithDescription:@"add conflicting item"];
207 SFCredential* conflictingCredential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"DifferentPassword" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
208 [_credentialStore addCredential:conflictingCredential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
209 XCTAssertNil(persistentIdentifier, @"adding a credential seems to have succeeded when we expected it to fail");
210 XCTAssertNotNil(error, @"failed to get error when adding a credential that should be rejected as a duplicate entry");
211 XCTAssertEqualObjects(error.domain, SFKeychainErrorDomain, @"duplicate error domain is not SFKeychainErrorDomain");
212 XCTAssertEqual(error.code, SFKeychainErrorDuplicateItem, @"duplicate error is not SFKeychainErrorDuplicateItem");
213 [conflictingAddExpectation fulfill];
214 }];
215 [self waitForExpectations:@[conflictingAddExpectation] timeout:5.0];
216 }
217
218 - (void)testRemoveCredential
219 {
220 SFPasswordCredential* credential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"TestPass" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
221
222 __block NSString* newItemPersistentIdentifier = nil;
223 XCTestExpectation* addExpectation = [self expectationWithDescription:@"add credential"];
224 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
225 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
226 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
227
228 newItemPersistentIdentifier = persistentIdentifier;
229 [addExpectation fulfill];
230 }];
231 [self waitForExpectations:@[addExpectation] timeout:5.0];
232
233 XCTestExpectation* removeExpectation = [self expectationWithDescription:@"remove credential"];
234 [_credentialStore removeCredentialWithPersistentIdentifier:newItemPersistentIdentifier withResultHandler:^(BOOL success, NSError* error) {
235 XCTAssertTrue(success, @"failed to remove credential from store");
236 XCTAssertNil(error, @"encountered error attempting to remove credential from store: %@", error);
237 [removeExpectation fulfill];
238 }];
239 [self waitForExpectations:@[removeExpectation] timeout:5.0];
240
241 XCTestExpectation* removeAgainExpectation = [self expectationWithDescription:@"remove credential gain"];
242 [_credentialStore removeCredentialWithPersistentIdentifier:newItemPersistentIdentifier withResultHandler:^(BOOL success, NSError* error) {
243 XCTAssertFalse(success, @"somehow succeeded at removing a credential that we'd already deleted");
244 XCTAssertNotNil(error, @"failed to get an error attempting to remove credential from store when there should not be a credential to delete");
245 [removeAgainExpectation fulfill];
246 }];
247
248 XCTestExpectation* fetchExpectation = [self expectationWithDescription:@"fetch credential"];
249 [_credentialStore fetchPasswordCredentialForPersistentIdentifier:newItemPersistentIdentifier withResultHandler:^(SFPasswordCredential* fetchedCredential, NSString* password, NSError* error) {
250 XCTAssertNil(fetchedCredential, @"found credential that we expected to be deleted");
251 XCTAssertNil(password, @"found password when credential was supposed to be deleted");
252 XCTAssertNotNil(error, "failed to get an error when fetching deleted credential");
253 [fetchExpectation fulfill];
254 }];
255 [self waitForExpectations:@[removeAgainExpectation, fetchExpectation] timeout:5.0];
256
257 // now try adding the thing again to make sure that works
258 XCTestExpectation* addAgainExpectation = [self expectationWithDescription:@"add credential again"];
259 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
260 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
261 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
262 XCTAssertNotEqual(persistentIdentifier, newItemPersistentIdentifier, @"the added credential has the same persistent identifier as the item we already deleted");
263
264 newItemPersistentIdentifier = persistentIdentifier;
265 [addAgainExpectation fulfill];
266 }];
267 [self waitForExpectations:@[addAgainExpectation] timeout:5.0];
268
269 XCTestExpectation* fetchAgainExpectation = [self expectationWithDescription:@"fetch credential again"];
270 [_credentialStore fetchPasswordCredentialForPersistentIdentifier:newItemPersistentIdentifier withResultHandler:^(SFPasswordCredential* fetchedCredential, NSString* password, NSError* error) {
271 XCTAssertNotNil(fetchedCredential, @"failed to fetch credential just added to store");
272 XCTAssertNil(error, @"received unexpected error fetching credential from store: %@", error);
273 XCTAssertTrue([self passwordCredential:credential matchesCredential:fetchedCredential], @"the credential we fetched from the store does not match the one we added");
274 XCTAssertEqualObjects(password, @"TestPass", @"the password we fetched from the store does not match the one we added");
275 [fetchAgainExpectation fulfill];
276 }];
277 [self waitForExpectations:@[fetchAgainExpectation] timeout:5.0];
278 }
279
280 - (void)testRemoveCredentialWithBadPersistentIdentifier
281 {
282 SFPasswordCredential* credential = [[SFPasswordCredential alloc] initWithUsername:@"TestUser" password:@"TestPass" primaryServiceIdentifier:[SFServiceIdentifier serviceIdentifierForDomain:@"testdomain.com"]];
283
284 __block NSString* newItemPersistentIdentifier = nil;
285 XCTestExpectation* addExpectation = [self expectationWithDescription:@"add credential"];
286 [_credentialStore addCredential:credential withAccessPolicy:[[SFAccessPolicy alloc] initWithAccessibility:SFAccessibilityMakeWithMode(SFAccessibleWhenUnlocked) sharingPolicy:SFSharingPolicyWithTrustedDevices] resultHandler:^(NSString* persistentIdentifier, NSError* error) {
287 XCTAssertNotNil(persistentIdentifier, @"failed to get persistent identifier for added credential");
288 XCTAssertNil(error, @"received unexpected error attempting to add credential to store: %@", error);
289
290 newItemPersistentIdentifier = persistentIdentifier;
291 [addExpectation fulfill];
292 }];
293 [self waitForExpectations:@[addExpectation] timeout:5.0];
294
295 NSString* wrongPersistentIdentifier = [[NSUUID UUID] UUIDString];
296 XCTestExpectation* removeWrongIdentifierEsxpectation = [self expectationWithDescription:@"remove wrong persistent identifier"];
297 [_credentialStore removeCredentialWithPersistentIdentifier:wrongPersistentIdentifier withResultHandler:^(BOOL success, NSError* error) {
298 XCTAssertFalse(success, @"reported success deleting a credential that was never there");
299 XCTAssertNotNil(error, @"failed to get error when attempting to delete an item with an erroneous persistent identifier");
300 [removeWrongIdentifierEsxpectation fulfill];
301 }];
302
303 NSString* notEvenAUUIDString = @"badstring";
304 XCTestExpectation* removeNonUUIDIdentifierExpectation = [self expectationWithDescription:@"remove non-uuid string identifier"];
305 [_credentialStore removeCredentialWithPersistentIdentifier:notEvenAUUIDString withResultHandler:^(BOOL success, NSError* error) {
306 XCTAssertFalse(success, @"reported success deleting a credential with a malformed persistent identifier");
307 XCTAssertNotNil(error, @"failed to get error when attempting to delete an item with a malformed persistent identifier");
308 XCTAssertEqualObjects(error.domain, SFKeychainErrorDomain);
309 XCTAssertEqual(error.code, SFKeychainErrorInvalidPersistentIdentifier);
310 [removeNonUUIDIdentifierExpectation fulfill];
311 }];
312 [self waitForExpectations:@[removeWrongIdentifierEsxpectation, removeNonUUIDIdentifierExpectation] timeout:5.0];
313 }
314
315 #pragma clang diagnostic pop
316
317 @end
318
319 #endif