2 * Copyright (c) 2017 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@
26 #import <Foundation/Foundation.h>
28 #import <XCTest/XCTest.h>
29 #import <OCMock/OCMock.h>
31 #import "OTTestsBase.h"
32 #import "keychain/ot/OTConstants.h"
34 static NSString* const testContextID = @"Foo";
35 static NSString* const testDSID = @"123456789";
37 static NSString* OTCKRecordBottledPeerType = @"OTBottledPeer";
39 @interface OTRampingUnitTests : OTTestsBase
43 @implementation OTRampingUnitTests
47 self.continueAfterFailure = NO;
54 -(void) testPreflightWithFeatureOnOn
56 [self setUpRampRecordsInCloudKitWithFeatureOn];
58 [self startCKKSSubsystem];
60 [self.otControl preflightBottledPeer:testContextID
62 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
63 XCTAssertNotNil(entropy, "entropy should not be nil");
64 XCTAssertNotNil(bottleID, "bottle id should not be nil");
65 XCTAssertNotNil(signingPublicKey, "signing pub key should not be nil");
66 XCTAssertNil(error, "error should be nil");
71 -(void) testBottleUpdateWithFeatureOnOn
73 __block NSData* localEntropy = nil;
74 __block NSString* localBottleID = nil;
76 [self setUpRampRecordsInCloudKitWithFeatureOn];
77 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
78 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
80 [self startCKKSSubsystem];
83 [self.otControl preflightBottledPeer:testContextID
85 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
86 localEntropy = entropy;
87 localBottleID = bottleID;
88 XCTAssertNotNil(entropy, "entropy should not be nil");
89 XCTAssertNotNil(bottleID, "bottle id should not be nil");
90 XCTAssertNotNil(signingPublicKey, "signing pub key should not be nil");
91 XCTAssertNil(error, "error should be nil");
94 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:[[NSNumber alloc] initWithInt:1], OTCKRecordBottledPeerType, nil];
96 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
99 [self.otControl launchBottledPeer:testContextID bottleID:localBottleID reply:^(NSError * _Nullable error) {
100 XCTAssertNil(error, "error should be nil");
103 [self waitForCKModifications];
104 OCMVerifyAllWithDelay(self.mockDatabase, 8);
105 [self releaseCloudKitFetchHold];
107 [self expectCKFetch];
109 SFECKeyPair* newSigningKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
111 SFECKeyPair* newEncryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
113 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
116 [self.otControl handleIdentityChangeForSigningKey:newSigningKey
117 ForEncryptionKey:newEncryptionKey
118 ForPeerID:self.sosPeerID
119 reply:^(BOOL result, NSError* _Nullable error){
120 XCTAssertNil(error, "error should be nil");
124 -(void) testLaunchWithRampOn
126 [self setUpRampRecordsInCloudKitWithFeatureOn];
127 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
128 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
129 [self startCKKSSubsystem];
131 __block NSData* localEntropy = nil;
132 __block NSString* localBottleID = nil;
134 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
136 [self.otControl preflightBottledPeer:testContextID
138 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
139 [self.spiBlockExpectation fulfill];
140 localEntropy = entropy;
141 localBottleID = bottleID;
142 XCTAssertNotNil(entropy, "entropy should not be nil");
143 XCTAssertNotNil(bottleID, "bottle id should not be nil");
144 XCTAssertNotNil(signingPublicKey, "signing pub key should not be nil");
145 XCTAssertNil(error, "error should be nil");
147 [self waitForExpectationsWithTimeout:1.0 handler:nil];
149 self.spiBlockExpectation = [self expectationWithDescription:@"launch bottled peer fired"];
151 NSMutableDictionary* recordDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:[[NSNumber alloc] initWithInt:1], OTCKRecordBottledPeerType, nil];
153 [self expectAddedCKModifyRecords:recordDictionary holdFetch:NO];
155 [self.otControl launchBottledPeer:testContextID bottleID:localBottleID reply:^(NSError * _Nullable error) {
156 [self.spiBlockExpectation fulfill];
157 XCTAssertNil(error, "error should be nil");
160 [self waitForExpectationsWithTimeout:1.0 handler:nil];
163 -(void) testRestoreWithRampOn
165 [self setUpRampRecordsInCloudKitWithFeatureOn];
166 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
167 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
168 [self startCKKSSubsystem];
170 __block NSData* localEntropy = nil;
171 __block NSString* localBottleID = nil;
173 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
175 [self.otControl preflightBottledPeer:OTDefaultContext
177 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
178 [self.spiBlockExpectation fulfill];
179 localEntropy = entropy;
180 localBottleID = bottleID;
181 XCTAssertNotNil(entropy, "entropy should not be nil");
182 XCTAssertNotNil(bottleID, "bottle id should not be nil");
183 XCTAssertNotNil(signingPublicKey, "signing pub key should not be nil");
184 XCTAssertNil(error, "error should be nil");
186 [self waitForExpectationsWithTimeout:1.0 handler:nil];
188 __block NSData* localSigningKeyData = nil;
189 __block NSData* localEncryptionKeyData = nil;
191 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
193 [self.otControl restore:testContextID
196 escrowRecordID:self.sosPeerID
197 reply:^(NSData* signingKeyData, NSData* encryptionKeyData, NSError* _Nullable error) {
198 [self.spiBlockExpectation fulfill];
199 localSigningKeyData = signingKeyData;
200 localEncryptionKeyData = encryptionKeyData;
201 XCTAssertNotNil(signingKeyData, "Signing key data should not be nil");
202 XCTAssertNotNil(encryptionKeyData, "encryption key data should not be nil");
203 XCTAssertNil(error, "error should not be nil");
205 [self waitForExpectationsWithTimeout:1.0 handler:nil];
206 NSError* localError = nil;
208 OTIdentity *ourSelf = [self currentIdentity:&localError];
209 XCTAssertTrue([localSigningKeyData isEqualToData:[ourSelf.peerSigningKey.publicKey keyData]], @"signing keys should be equal!");
210 XCTAssertTrue([localEncryptionKeyData isEqualToData:[ourSelf.peerEncryptionKey.publicKey keyData]], @"signing keys should be equal!");
213 -(void) testScrubWithRampOn
215 [self setUpRampRecordsInCloudKitWithFeatureOn];
216 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
217 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
218 [self startCKKSSubsystem];
220 __block NSString* localBottleID = nil;
222 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
224 [self.otControl preflightBottledPeer:testContextID
226 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
227 [self.spiBlockExpectation fulfill];
228 localBottleID = bottleID;
229 XCTAssertNotNil(entropy, "entropy should not be nil");
230 XCTAssertNotNil(bottleID, "bottle id should not be nil");
231 XCTAssertNotNil(signingPublicKey, "signing pub key should not be nil");
232 XCTAssertNil(error, "error should be nil");
235 [self waitForExpectationsWithTimeout:1.0 handler:nil];
237 self.spiBlockExpectation = [self expectationWithDescription:@"scrub scheduler fired"];
239 [self.otControl scrubBottledPeer:testContextID bottleID:localBottleID reply:^(NSError * _Nullable error) {
240 [self.spiBlockExpectation fulfill];
241 XCTAssertNil(error, "error should be nil");
243 [self waitForExpectationsWithTimeout:1.0 handler:nil];
245 NSError* localError = nil;
246 NSArray* bottles = [self.localStore readAllLocalBottledPeerRecords:&localError];
247 XCTAssertNotNil(localError, "error should not be nil");
248 XCTAssertTrue([bottles count] == 0, "should be 0 bottles");
251 -(void) testPreflightWithRampOff
253 [self setUpRampRecordsInCloudKitWithFeatureOff];
254 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
255 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
256 [self startCKKSSubsystem];
258 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
260 [self.otControl preflightBottledPeer:OTDefaultContext
262 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
263 [self.spiBlockExpectation fulfill];
264 XCTAssertNil(entropy, "shouldn't return any entropy");
265 XCTAssertNil(bottleID, "shouldn't return a bottle ID");
266 XCTAssertNil(signingPublicKey, "shouldn't return a signingPublicKey");
267 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
269 [self waitForCKModifications];
270 OCMVerifyAllWithDelay(self.mockDatabase, 8);
271 [self waitForExpectationsWithTimeout:1.0 handler:nil];
274 -(void) testBottleUpdateWithFeatureOff
276 [self setUpRampRecordsInCloudKitWithFeatureOff];
277 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
278 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
279 [self startCKKSSubsystem];
281 SFECKeyPair* newSigningKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
283 SFECKeyPair* newEncryptionKey = [[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]];
286 [self.otControl handleIdentityChangeForSigningKey:newSigningKey
287 ForEncryptionKey:newEncryptionKey
288 ForPeerID:self.sosPeerID
289 reply:^(BOOL result, NSError* _Nullable error){
290 XCTAssertNotNil(error, "error should be nil");
291 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
296 -(void) testPreflightWithRecordNotThere
298 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
299 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
300 [self startCKKSSubsystem];
302 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
304 [self.otControl preflightBottledPeer:OTDefaultContext
306 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
307 [self.spiBlockExpectation fulfill];
308 XCTAssertNil(entropy, "shouldn't return any entropy");
309 XCTAssertNil(bottleID, "shouldn't return a bottle ID");
310 XCTAssertNil(signingPublicKey, "shouldn't return a signingPublicKey");
311 XCTAssertNotNil(error, "should not be nil");
313 [self waitForCKModifications];
314 OCMVerifyAllWithDelay(self.mockDatabase, 8);
315 [self waitForExpectationsWithTimeout:1.0 handler:nil];
318 -(void) testLaunchWithRampOff
320 [self setUpRampRecordsInCloudKitWithFeatureOff];
321 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
322 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
323 [self startCKKSSubsystem];
325 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer fired"];
327 [self.otControl preflightBottledPeer:OTDefaultContext
329 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
330 [self.spiBlockExpectation fulfill];
331 XCTAssertNil(entropy, "shouldn't return any entropy");
332 XCTAssertNil(bottleID, "shouldn't return a bottle ID");
333 XCTAssertNil(signingPublicKey, "shouldn't return a signingPublicKey");
334 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
336 [self waitForCKModifications];
337 OCMVerifyAllWithDelay(self.mockDatabase, 8);
338 [self waitForExpectationsWithTimeout:1.0 handler:nil];
341 self.spiBlockExpectation = [self expectationWithDescription:@"launch SPI fired"];
343 NSString* localBottleID = @"random bottle id";
344 [self.otControl launchBottledPeer:testContextID bottleID:localBottleID reply:^(NSError * _Nullable error) {
345 [self.spiBlockExpectation fulfill];
346 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
349 [self waitForCKModifications];
350 OCMVerifyAllWithDelay(self.mockDatabase, 8);
351 [self waitForExpectationsWithTimeout:1.0 handler:nil];
353 -(void) testRestoreWithRampOff
355 [self setUpRampRecordsInCloudKitWithFeatureOff];
356 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
357 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
358 [self startCKKSSubsystem];
360 self.spiBlockExpectation = [self expectationWithDescription:@"restore SPI fired"];
362 [self.otControl restore:testContextID
365 escrowRecordID:self.sosPeerID
366 reply:^(NSData* signingKeyData, NSData* encryptionKeyData, NSError* _Nullable error) {
367 [self.spiBlockExpectation fulfill];
368 XCTAssertNil(signingKeyData, "Signing key data should be nil");
369 XCTAssertNil(encryptionKeyData, "encryption key data should be nil");
370 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
372 [self waitForCKModifications];
373 OCMVerifyAllWithDelay(self.mockDatabase, 8);
374 [self waitForExpectationsWithTimeout:1.0 handler:nil];
377 -(void) testScrubWithRampOff
379 [self setUpRampRecordsInCloudKitWithFeatureOff];
380 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
381 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
382 [self startCKKSSubsystem];
384 self.spiBlockExpectation = [self expectationWithDescription:@"preflight bottled peer SPI fired"];
386 [self.otControl preflightBottledPeer:testContextID
388 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
389 [self.spiBlockExpectation fulfill];
390 XCTAssertNil(entropy, "entropy should be nil");
391 XCTAssertNil(bottleID, "bottle id should be nil");
392 XCTAssertNil(signingPublicKey, "signing pub key should be nil");
393 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
396 [self waitForExpectationsWithTimeout:1.0 handler:nil];
398 __block NSString* localBottleID = @"random bottle id";
399 self.spiBlockExpectation = [self expectationWithDescription:@"scrub bottled peer SPI fired"];
401 [self.otControl scrubBottledPeer:testContextID bottleID:localBottleID reply:^(NSError * _Nullable error) {
402 [self.spiBlockExpectation fulfill];
403 XCTAssertEqual(error.code, OTErrorFeatureNotEnabled, "should return a OTErrorFeatureNotEnabled error");
406 [self waitForCKModifications];
407 OCMVerifyAllWithDelay(self.mockDatabase, 8);
408 [self waitForExpectationsWithTimeout:1.0 handler:nil];
410 [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
411 [self startCKKSSubsystem];
413 XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:4*NSEC_PER_SEC], @"Key state should have arrived at ready");
416 -(void) testRampFetchTimeout
418 [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
419 [self putSelfTLKSharesInCloudKit:self.keychainZoneID];
420 [self startCKKSSubsystem];
422 __block NSError* localError = nil;
424 [self holdCloudKitFetches];
426 [self.otControl preflightBottledPeer:OTDefaultContext
428 reply:^(NSData * _Nullable entropy, NSString * _Nullable bottleID, NSData * _Nullable signingPublicKey, NSError * _Nullable error) {
430 XCTAssertNil(entropy, "shouldn't return any entropy");
431 XCTAssertNil(bottleID, "shouldn't return a bottle ID");
432 XCTAssertNil(signingPublicKey, "shouldn't return a signingPublicKey");
433 XCTAssertEqual(error.code, OTErrorCKTimeOut, "should return a OTErrorCKTimeout error");
437 -(void)testCFUWithRampOn
439 NSError* localError = nil;
441 [self setUpRampRecordsInCloudKitWithFeatureOn];
443 [self startCKKSSubsystem];
445 XCTAssertTrue([self.cfu checkRampStateWithError:&localError], @"should be true");
446 XCTAssertNil(localError, "Should not have gotten an error checking ramp state (and getting true)");
449 -(void)testCFUWithRampOff
451 NSError* localError = nil;
452 [self setUpRampRecordsInCloudKitWithFeatureOff];
454 [self startCKKSSubsystem];
456 XCTAssertFalse([self.cfu checkRampStateWithError:&localError], @"should be false");
457 XCTAssertNil(localError, "Should not have gotten an error checking ramp state (and getting false)");
460 -(void)testCFUWithNonExistentRampRecord
462 NSError* localError = nil;
464 [self startCKKSSubsystem];
466 XCTAssertFalse([self.cfu checkRampStateWithError:&localError], @"should be false");
467 XCTAssertNotNil(localError, "Should have gotten an error checking ramp state (and getting false)");
468 XCTAssertEqual(localError.code, OTErrorRecordNotFound, "Error should be 'record not found'");