]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/tests/CKKSManifestTests.m
Security-58286.20.16.tar.gz
[apple/security.git] / keychain / ckks / tests / CKKSManifestTests.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import "CKKSTests.h"
27 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
28 #import "keychain/ckks/CKKSManifest.h"
29 #import "keychain/ckks/CKKSMirrorEntry.h"
30 #import "keychain/ckks/CKKSViewManager.h"
31 #import "keychain/ckks/CKKSCurrentItemPointer.h"
32 #import <Security/SecEntitlements.h>
33 #include <ipc/server_security_helpers.h>
34 #import <SecurityFoundation/SFKey.h>
35 #import <Foundation/Foundation.h>
36 #import <XCTest/XCTest.h>
37 #import <OCMock/OCMock.h>
38
39 @interface CKKSManifestTests : CloudKitKeychainSyncingTestsBase {
40 BOOL _manifestIsSetup;
41 CKKSEgoManifest* _egoManifest;
42 id _manifestMock;
43 id _fakeSigner1Mock;
44 }
45
46 @end
47
48 @implementation CKKSManifestTests
49
50 - (NSArray*)mirrorItemsForExistingItems
51 {
52 NSArray<CKRecord*>* records = [self.keychainZone.currentDatabase.allValues filteredArrayUsingPredicate: [NSPredicate predicateWithFormat:@"self.recordType like %@", SecCKRecordItemType]];
53 NSMutableArray* items = [[NSMutableArray alloc] init];
54 __weak __typeof(self) weakSelf = self;
55 __block NSError* error = nil;
56 [self.keychainView dispatchSync:^bool(void) {
57 for (CKRecord* record in records) {
58 CKKSMirrorEntry* mirrorEntry = [CKKSMirrorEntry tryFromDatabase:record.recordID.recordName zoneID:weakSelf.keychainZoneID error:&error];
59 XCTAssertNil(error, @"error encountered trying to generate CKKSMirrorEntry: %@", error);
60 XCTAssertNotNil(mirrorEntry, @"failed to generate mirror entry");
61 if (mirrorEntry) {
62 [items addObject:mirrorEntry.item];
63 }
64 }
65
66 return YES;
67 }];
68
69 return items;
70 }
71
72 - (void)setUp
73 {
74 // Lie to our superclass about how the world is
75 (void)[CKKSManifest shouldSyncManifests]; // initialize.
76 SecCKKSSetSyncManifests(false);
77 SecCKKSSetEnforceManifests(false);
78
79 [super setUp];
80
81 // Always sync manifests, and never enforce them
82 SecCKKSSetSyncManifests(true);
83 SecCKKSSetEnforceManifests(false);
84
85 XCTAssertTrue([CKKSManifest shouldSyncManifests], "Manifests syncing is enabled");
86 XCTAssertFalse([CKKSManifest shouldEnforceManifests], "Manifests enforcement is disabled");
87
88 NSError* error = nil;
89
90 [CKKSManifestInjectionPointHelper setIgnoreChanges:NO];
91
92 // Test starts with keys in CloudKit (so we can create items later)
93 [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
94 [self saveTLKMaterialToKeychain:self.keychainZoneID];
95
96 [self addGenericPassword:@"data" account:@"first"];
97 [self addGenericPassword:@"data" account:@"second"];
98 [self addGenericPassword:@"data" account:@"third"];
99 [self addGenericPassword:@"data" account:@"fourth"];
100 NSUInteger passwordCount = 4u;
101
102 [self checkGenericPassword:@"data" account:@"first"];
103 [self checkGenericPassword:@"data" account:@"second"];
104 [self checkGenericPassword:@"data" account:@"third"];
105 [self checkGenericPassword:@"data" account:@"fourth"];
106
107 [self expectCKModifyItemRecords:passwordCount currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
108
109 [self startCKKSSubsystem];
110 [self.keychainView waitForKeyHierarchyReadiness];
111
112 // Wait for uploads to happen
113 OCMVerifyAllWithDelay(self.mockDatabase, 8);
114 [self waitForCKModifications];
115 XCTAssertEqual(self.keychainZone.currentDatabase.count, SYSTEM_DB_RECORD_COUNT + passwordCount, "Have 6+passwordCount objects in cloudkit");
116
117 NSArray* items = [self mirrorItemsForExistingItems];
118 _egoManifest = [CKKSEgoManifest newManifestForZone:self.keychainZoneID.zoneName withItems:items peerManifestIDs:@[] currentItems:@{} error:&error];
119 XCTAssertNil(error, @"no error encountered generating ego manifest");
120 XCTAssertNotNil(_egoManifest, @"generated ego manifest");
121 }
122
123 - (void)tearDown {
124 _egoManifest = nil;
125 [super tearDown];
126 }
127
128 - (void)testSaveEgoManifest
129 {
130 if (![CKKSManifest shouldSyncManifests]) {
131 return;
132 }
133
134 NSError* error;
135 [_egoManifest saveToDatabase:&error];
136 XCTAssertNil(error, @"error encountered attempting to save ego manifest: %@", error);
137 }
138
139 - (void)testManifestValidatesItems
140 {
141 if (![CKKSManifest shouldSyncManifests]) {
142 return;
143 }
144
145 NSError* error = nil;
146 for (CKKSItem* mirrorItem in [self mirrorItemsForExistingItems]) {
147 XCTAssertTrue([_egoManifest validateItem:mirrorItem withError:&error], @"failed to validate item against manifest");
148 XCTAssertNil(error, @"error generated attempting to validate item against manifest: %@", error);
149 }
150 }
151
152 - (void)disable32852700testManifestDoesNotValidateUnknownItems
153 {
154 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
155 [self startCKKSSubsystem];
156
157 NSArray* knownItems = [self mirrorItemsForExistingItems];
158 [self addGenericPassword:@"data" account:@"unknown_account"];
159 [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
160 OCMVerifyAllWithDelay(self.mockDatabase, 4);
161 [self waitForCKModifications];
162
163 NSArray* newItems = [self mirrorItemsForExistingItems];
164 CKKSItem* unknownItem = nil;
165 for (CKKSItem* item in newItems) {
166 BOOL found = NO;
167 for (CKKSItem* knownItem in knownItems) {
168 if ([knownItem.CKRecordName isEqualToString:item.CKRecordName]) {
169 found = YES;
170 break;
171 }
172 }
173
174 if (!found) {
175 unknownItem = item;
176 break;
177 }
178 }
179
180 NSError* error = nil;
181 XCTAssertNotNil(_egoManifest, @"have an ego manifest");
182 XCTAssertFalse([_egoManifest validateItem:unknownItem withError:&error], @"erroneously validated an unknown item against the manifest");
183 XCTAssertNotNil(error, @"failed to generate error when trying to validate an unknown item against the manifest");
184 }
185
186 - (void)testManifestValidatesCurrentItems
187 {
188 NSError* error = nil;
189 CKKSItem* item = [[self mirrorItemsForExistingItems] firstObject];
190 [_egoManifest setCurrentItemUUID:item.uuid forIdentifier:@"testCurrentItemIdentifier"];
191 CKKSCurrentItemPointer* currentItemPointer = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"testCurrentItemIdentifier" currentItemUUID:item.uuid state:SecCKKSProcessedStateLocal zoneID:self.keychainZoneID encodedCKRecord:nil];
192 XCTAssertTrue([_egoManifest validateCurrentItem:currentItemPointer withError:&error], @"failed to validate current item against manifest");
193 XCTAssertNil(error, @"error encountered validating current item: %@", error);
194 }
195
196 - (void)testManifestDoesNotValidateUnknownCurrentItems
197 {
198 NSError* error = nil;
199 CKKSItem* item = [[self mirrorItemsForExistingItems] firstObject];
200 CKKSCurrentItemPointer* currentItemPointer = [[CKKSCurrentItemPointer alloc] initForIdentifier:@"testCurrentItemIdentifier" currentItemUUID:item.uuid state:SecCKKSProcessedStateLocal zoneID:self.keychainZoneID encodedCKRecord:nil];
201 XCTAssertFalse([_egoManifest validateCurrentItem:currentItemPointer withError:&error], @"erroneously validated an uunknown current item against the manifest");
202 XCTAssertNotNil(error, @"failed to generate error when trying to validate an unknown item against the manifest %@", error);
203 }
204
205 - (void)testEgoManifestValidates
206 {
207 if (![CKKSManifest shouldSyncManifests]) {
208 return;
209 }
210
211 NSError* error = nil;
212 XCTAssertTrue([_egoManifest validateWithError:&error], @"failed to validate our own ego manifest");
213 XCTAssertNil(error, @"error generated attempting to validate ego manifest: %@", error);
214 }
215
216 - (void)testEgoManifestCreatedOnUpload
217 {
218 if (![CKKSManifest shouldSyncManifests]) {
219 return;
220 }
221
222 CKKSEgoManifest* manifest = [CKKSEgoManifest tryCurrentEgoManifestForZone:self.keychainZoneID.zoneName];
223 XCTAssertNotNil(manifest, @"created some items, but we don't have a current manifest");
224 }
225
226 - (void)disable32852700testGenerationCountIncreaseOnChanges
227 {
228 if (![CKKSManifest shouldSyncManifests]) {
229 return;
230 }
231
232 NSInteger initialManifestGenerationCount = [[CKKSEgoManifest tryCurrentEgoManifestForZone:self.keychainZoneID.zoneName] generationCount];
233
234 [self addGenericPassword:@"data" account:@"GenerationCountIncrease"];
235 [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
236 OCMVerifyAllWithDelay(self.mockDatabase, 4);
237 [self waitForCKModifications];
238
239 NSInteger postUpdateGenerationCount = [[CKKSEgoManifest tryCurrentEgoManifestForZone:self.keychainZoneID.zoneName] generationCount];
240 XCTAssertGreaterThan(postUpdateGenerationCount, initialManifestGenerationCount, @"the manifest after the update does not have a larger generation count as expected");
241 }
242
243 - (NSDictionary*)receiveOneItemInManifest:(BOOL)inManifest
244 {
245 if (!inManifest) {
246 [CKKSManifestInjectionPointHelper setIgnoreChanges:YES];
247 }
248
249 NSError* error = nil;
250
251 NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
252 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
253 (id)kSecAttrAccount : @"account-delete-me",
254 (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
255 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
256 };
257
258 CFTypeRef item = NULL;
259 XCTAssertEqual(errSecItemNotFound, SecItemCopyMatching((__bridge CFDictionaryRef) query, &item), "item should not yet exist");
260
261 CKRecord* ckr = [self createFakeRecord:self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
262
263 SFECKeyPair* keyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
264 CKKSEgoManifest* manifest = [CKKSEgoManifest newFakeManifestForZone:self.keychainZoneID.zoneName withItemRecords:@[ckr] currentItems:@{} signerID:@"FakeSigner-1" keyPair:keyPair error:&error];
265 [manifest saveToDatabase:&error];
266 XCTAssertNil(error, @"error encountered generating fake manifest: %@", error);
267 XCTAssertNotNil(manifest, @"failed to generate a fake manifest");
268
269 [self.keychainZone addToZone:ckr];
270 for (CKRecord* record in [manifest allCKRecordsWithZoneID:self.keychainZoneID]) {
271 [self.keychainZone addToZone:record];
272 }
273
274 // Trigger a notification (with hilariously fake data)
275 [self.keychainView notifyZoneChange:nil];
276
277 [self.keychainView waitForFetchAndIncomingQueueProcessing];
278
279 return query;
280 }
281
282 - (void)testReceiveManifest
283 {
284 if (![CKKSManifest shouldSyncManifests]) {
285 return;
286 }
287
288 NSError* error = nil;
289
290 NSDictionary* itemQuery = [self receiveOneItemInManifest:YES];
291 CFTypeRef item = NULL;
292 XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)itemQuery, &item), "item should exist now");
293
294 CKKSManifest* fetchedManifest = [CKKSManifest manifestForZone:self.keychainZoneID.zoneName peerID:@"FakeSigner-1" error:&error];
295 XCTAssertNil(error, @"error encountered fetching expected manifest from database: %@", error);
296 XCTAssertNotNil(fetchedManifest, @"failed to fetch manifest expected in database");
297
298 CKKSMirrorEntry* fetchedEntry = [CKKSMirrorEntry tryFromDatabase:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85" zoneID:self.keychainZoneID error:&error];
299 XCTAssertNil(error, @"error encountered fetching expected item from database: %@", error);
300 XCTAssertNotNil(fetchedEntry, @"failed to fetch item expected in database");
301
302 XCTAssertTrue([fetchedManifest validateWithError:&error], @"failed to validate fetched manifest with error: %@", error);
303 XCTAssertTrue([fetchedManifest validateItem:fetchedEntry.item withError:&error], @"failed to validate item (%@) against manifest with error: %@", fetchedEntry, error);
304 }
305
306 - (void)testUnauthorizedItemRejected
307 {
308 if (![CKKSManifest shouldSyncManifests] || ![CKKSManifest shouldEnforceManifests]) {
309 return;
310 }
311
312 NSDictionary* itemQuery = [self receiveOneItemInManifest:NO];
313 CFTypeRef item = NULL;
314 XCTAssertNotEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef)itemQuery, &item), "item should have been rejected");
315 }
316
317 - (void)testSaveManifestWithNilValues
318 {
319 SFECKeyPair* keyPair = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
320 CKKSManifest* manifest = [CKKSEgoManifest newFakeManifestForZone:@"SomeZone" withItemRecords:@[] currentItems:@{} signerID:@"BadBoy" keyPair:keyPair error:nil];
321 [manifest nilAllIvars];
322 XCTAssertNil(manifest.zoneID);
323 XCTAssertNil(manifest.signerID);
324 XCTAssertNoThrow([manifest saveToDatabase:nil]);
325 }
326
327 @end
328
329 #endif // OCTAGON