3 @objcMembers class OctagonRecoveryKeyTests: OctagonTestsBase {
4 override func setUp() {
8 func testSetRecoveryKey() throws {
9 self.startCKAccountStatusMock()
10 self.manager.setSOSEnabledForPlatformFlag(false)
12 self.cuttlefishContext.startOctagonStateMachine()
13 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
15 XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices")
19 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
20 XCTAssertNotNil(clique, "Clique should not be nil")
22 XCTFail("Shouldn't have errored making new friends: \(error)")
26 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
27 XCTAssertNotNil(entropy, "entropy should not be nil")
29 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
30 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
32 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
33 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
34 self.manager.setSOSEnabledForPlatformFlag(true)
36 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
37 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
38 XCTAssertNil(error, "error should be nil")
39 createKeyExpectation.fulfill()
41 self.wait(for: [createKeyExpectation], timeout: 10)
44 func testSetRecoveryKeyPeerReaction() throws {
45 self.startCKAccountStatusMock()
46 self.manager.setSOSEnabledForPlatformFlag(false)
48 self.cuttlefishContext.startOctagonStateMachine()
49 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
53 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
54 XCTAssertNotNil(clique, "Clique should not be nil")
56 XCTFail("Shouldn't have errored making new friends: \(error)")
60 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
61 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
62 self.verifyDatabaseMocks()
64 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
65 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
66 self.manager.setSOSEnabledForPlatformFlag(true)
68 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
69 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
70 XCTAssertNil(error, "error should be nil")
71 createKeyExpectation.fulfill()
73 self.wait(for: [createKeyExpectation], timeout: 10)
75 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
76 XCTAssertNotNil(entropy, "entropy should not be nil")
78 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
79 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
80 self.verifyDatabaseMocks()
82 let bottle = self.fakeCuttlefishServer.state.bottles[0]
84 let initiatorContextID = "new guy"
86 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID, authKitAdapter: self.mockAuthKit2)
88 initiatorContext.startOctagonStateMachine()
89 self.sendContainerChange(context: initiatorContext)
91 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
92 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
93 XCTAssertNil(error, "error should be nil")
94 joinWithBottleExpectation.fulfill()
96 self.wait(for: [joinWithBottleExpectation], timeout: 10)
97 self.verifyDatabaseMocks()
99 self.sendContainerChangeWaitForFetch(context: initiatorContext)
100 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
102 self.verifyDatabaseMocks()
104 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
105 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
107 XCTAssertNotNil(dump, "dump should not be nil")
108 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
109 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
110 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
111 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
113 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
114 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
115 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
116 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
118 let included = dynamicInfo!["included"] as? Array<String>
119 XCTAssertNotNil(included, "included should not be nil")
120 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
122 stableInfoCheckDumpCallback.fulfill()
124 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
126 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
127 self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") {
129 XCTAssertNotNil(dump, "dump should not be nil")
130 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
131 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
132 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
133 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
135 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
136 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
137 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
138 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
140 let included = dynamicInfo!["included"] as? Array<String>
141 XCTAssertNotNil(included, "included should not be nil")
142 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
144 stableInfoAcceptorCheckDumpCallback.fulfill()
146 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
147 self.verifyDatabaseMocks()
150 func testSetRecoveryKey3PeerReaction() throws {
151 self.startCKAccountStatusMock()
152 self.manager.setSOSEnabledForPlatformFlag(false)
154 self.cuttlefishContext.startOctagonStateMachine()
155 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
159 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
160 XCTAssertNotNil(clique, "Clique should not be nil")
162 XCTFail("Shouldn't have errored making new friends: \(error)")
166 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
167 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
168 self.verifyDatabaseMocks()
170 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
171 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
172 self.manager.setSOSEnabledForPlatformFlag(true)
174 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
175 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
176 XCTAssertNil(error, "error should be nil")
177 createKeyExpectation.fulfill()
179 self.wait(for: [createKeyExpectation], timeout: 10)
181 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
182 XCTAssertNotNil(entropy, "entropy should not be nil")
184 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
185 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
187 let bottle = self.fakeCuttlefishServer.state.bottles[0]
189 let initiatorContextID = "new guy"
190 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
192 initiatorContext.startOctagonStateMachine()
194 self.sendContainerChange(context: initiatorContext)
196 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
198 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
199 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
200 XCTAssertNil(error, "error should be nil")
201 joinWithBottleExpectation.fulfill()
203 self.wait(for: [joinWithBottleExpectation], timeout: 10)
205 self.verifyDatabaseMocks()
207 self.sendContainerChangeWaitForFetch(context: initiatorContext)
209 // The first peer will upload TLKs for the new peer
210 self.assertAllCKKSViewsUpload(tlkShares: 1)
211 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
212 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
213 self.verifyDatabaseMocks()
215 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
216 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
218 XCTAssertNotNil(dump, "dump should not be nil")
219 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
220 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
221 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
222 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
224 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
225 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
226 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
227 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
229 let included = dynamicInfo!["included"] as? Array<String>
230 XCTAssertNotNil(included, "included should not be nil")
231 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
233 stableInfoCheckDumpCallback.fulfill()
235 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
237 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
238 self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") {
240 XCTAssertNotNil(dump, "dump should not be nil")
241 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
242 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
243 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
244 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
246 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
247 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
248 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
249 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
251 let included = dynamicInfo!["included"] as? Array<String>
252 XCTAssertNotNil(included, "included should not be nil")
253 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
255 stableInfoAcceptorCheckDumpCallback.fulfill()
257 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
259 let thirdPeerContextID = "3rd guy"
260 let thirdPeerContext = self.makeInitiatorContext(contextID: thirdPeerContextID, authKitAdapter: self.mockAuthKit3)
262 thirdPeerContext.startOctagonStateMachine()
264 self.sendContainerChange(context: thirdPeerContext)
265 let thirdPeerJoinWithBottleExpectation = self.expectation(description: "thirdPeerJoinWithBottleExpectation callback occurs")
266 thirdPeerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
267 XCTAssertNil(error, "error should be nil")
268 thirdPeerJoinWithBottleExpectation.fulfill()
270 self.wait(for: [thirdPeerJoinWithBottleExpectation], timeout: 10)
272 self.verifyDatabaseMocks()
274 self.sendContainerChangeWaitForFetch(context: thirdPeerContext)
275 let thirdPeerStableInfoCheckDumpCallback = self.expectation(description: "thirdPeerStableInfoCheckDumpCallback callback occurs")
276 self.tphClient.dump(withContainer: OTCKContainerName, context: thirdPeerContextID) {
278 XCTAssertNotNil(dump, "dump should not be nil")
279 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
280 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
281 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
282 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
284 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
285 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
286 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
287 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
289 let included = dynamicInfo!["included"] as? Array<String>
290 XCTAssertNotNil(included, "included should not be nil")
291 XCTAssertEqual(included!.count, 3, "should be 3df peer ids")
293 thirdPeerStableInfoCheckDumpCallback.fulfill()
295 self.wait(for: [thirdPeerStableInfoCheckDumpCallback], timeout: 10)
298 func createEstablishContext(contextID: String) -> OTCuttlefishContext {
300 return self.manager.context(forContainerName: OTCKContainerName,
301 contextID: contextID,
302 sosAdapter: self.mockSOSAdapter,
303 authKitAdapter: self.mockAuthKit2,
304 lockStateTracker: self.lockStateTracker,
305 accountStateTracker: self.accountStateTracker,
306 deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-RK-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
309 func testJoinWithRecoveryKey() throws {
310 OctagonRecoveryKeySetIsEnabled(true)
311 self.manager.setSOSEnabledForPlatformFlag(false)
312 self.startCKAccountStatusMock()
314 let establishContextID = "establish-context-id"
315 let establishContext = self.createEstablishContext(contextID: establishContextID)
317 establishContext.startOctagonStateMachine()
318 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
321 let bottlerotcliqueContext = OTConfigurationContext()
322 bottlerotcliqueContext.context = establishContextID
323 bottlerotcliqueContext.dsid = "1234"
324 bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID!
325 bottlerotcliqueContext.otControl = self.otControl
327 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
328 XCTAssertNotNil(clique, "Clique should not be nil")
329 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
331 XCTFail("Shouldn't have errored making new friends: \(error)")
335 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
336 self.assertConsidersSelfTrusted(context: establishContext)
338 // Fake that this peer also created some TLKShares for itself
339 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
340 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
342 self.assertSelfTLKSharesInCloudKit(context: establishContext)
344 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
345 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
347 self.manager.setSOSEnabledForPlatformFlag(true)
349 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
350 self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in
351 XCTAssertNil(error, "error should be nil")
352 createRecoveryExpectation.fulfill()
354 self.wait(for: [createRecoveryExpectation], timeout: 10)
356 self.sendContainerChangeWaitForFetch(context: establishContext)
358 let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
360 recoveryContext.startOctagonStateMachine()
361 self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
363 self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext)
365 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
366 recoveryContext.join(withRecoveryKey: recoveryKey) { error in
367 XCTAssertNil(error, "error should be nil")
368 joinWithRecoveryKeyExpectation.fulfill()
370 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
372 self.sendContainerChangeWaitForFetch(context: recoveryContext)
374 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
375 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
377 XCTAssertNotNil(dump, "dump should not be nil")
378 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
379 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
380 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
381 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
383 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
384 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
385 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
386 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
388 let included = dynamicInfo!["included"] as? Array<String>
389 XCTAssertNotNil(included, "included should not be nil")
390 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
391 let vouchers = dump!["vouchers"]
392 XCTAssertNotNil(vouchers, "vouchers should not be nil")
393 stableInfoCheckDumpCallback.fulfill()
395 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
397 self.sendContainerChangeWaitForFetch(context: establishContext)
399 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
400 self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) {
402 XCTAssertNotNil(dump, "dump should not be nil")
403 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
404 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
405 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
406 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
408 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
409 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
410 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
411 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
413 let included = dynamicInfo!["included"] as? Array<String>
414 XCTAssertNotNil(included, "included should not be nil")
415 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
416 let vouchers = dump!["vouchers"]
417 XCTAssertNotNil(vouchers, "vouchers should not be nil")
418 stableInfoAcceptorCheckDumpCallback.fulfill()
420 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
421 try self.putSelfTLKShareInCloudKit(context: recoveryContext, zoneID: self.manateeZoneID)
422 self.assertSelfTLKSharesInCloudKit(context: recoveryContext)
425 func testJoinWithRecoveryKeyWithCKKSConflict() throws {
426 OctagonRecoveryKeySetIsEnabled(true)
427 self.manager.setSOSEnabledForPlatformFlag(false)
428 self.startCKAccountStatusMock()
430 let establishContextID = "establish-context-id"
431 let establishContext = self.createEstablishContext(contextID: establishContextID)
433 establishContext.startOctagonStateMachine()
434 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
437 let bottlerotcliqueContext = OTConfigurationContext()
438 bottlerotcliqueContext.context = establishContextID
439 bottlerotcliqueContext.dsid = "1234"
440 bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID!
441 bottlerotcliqueContext.otControl = self.otControl
443 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
444 XCTAssertNotNil(clique, "Clique should not be nil")
445 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
447 XCTFail("Shouldn't have errored making new friends: \(error)")
451 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
452 self.assertConsidersSelfTrusted(context: establishContext)
454 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
455 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
457 self.manager.setSOSEnabledForPlatformFlag(true)
459 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
460 self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in
461 XCTAssertNil(error, "error should be nil")
462 createRecoveryExpectation.fulfill()
464 self.wait(for: [createRecoveryExpectation], timeout: 10)
466 self.sendContainerChangeWaitForFetch(context: establishContext)
468 self.silentFetchesAllowed = false
469 self.expectCKFetchAndRun(beforeFinished: {
470 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
471 self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID)
472 self.silentFetchesAllowed = true
474 let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
476 recoveryContext.startOctagonStateMachine()
477 self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
479 self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext)
481 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
482 recoveryContext.join(withRecoveryKey: recoveryKey) { error in
483 XCTAssertNil(error, "error should be nil")
484 joinWithRecoveryKeyExpectation.fulfill()
486 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
488 self.assertConsidersSelfTrusted(context: recoveryContext)
489 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
492 func testOTCliqueSettingRecoveryKey() throws {
493 OctagonRecoveryKeySetIsEnabled(true)
494 self.manager.setSOSEnabledForPlatformFlag(false)
495 self.startCKAccountStatusMock()
496 let establishContextID = "establish-context-id"
497 let establishContext = self.createEstablishContext(contextID: establishContextID)
499 establishContext.startOctagonStateMachine()
500 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
504 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
505 XCTAssertNotNil(clique, "Clique should not be nil")
507 XCTFail("Shouldn't have errored making new friends: \(error)")
511 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
512 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
514 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
515 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
516 self.manager.setSOSEnabledForPlatformFlag(true)
518 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
519 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
520 XCTAssertNil(error, "error should be nil")
521 XCTAssertNotNil(rk, "rk should not be nil")
522 setRecoveryKeyExpectation.fulfill()
524 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
527 func testOTCliqueSet2ndRecoveryKey() throws {
528 OctagonRecoveryKeySetIsEnabled(true)
529 self.manager.setSOSEnabledForPlatformFlag(false)
530 self.startCKAccountStatusMock()
531 let establishContextID = "establish-context-id"
532 let establishContext = self.createEstablishContext(contextID: establishContextID)
534 establishContext.startOctagonStateMachine()
535 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
539 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
540 XCTAssertNotNil(clique, "Clique should not be nil")
542 XCTFail("Shouldn't have errored making new friends: \(error)")
546 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
547 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
549 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
550 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
551 self.manager.setSOSEnabledForPlatformFlag(true)
553 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
554 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
555 XCTAssertNil(error, "error should be nil")
556 XCTAssertNotNil(rk, "rk should not be nil")
557 setRecoveryKeyExpectation.fulfill()
559 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
561 let recoveryKey2 = SecRKCreateRecoveryKeyString(nil)
563 let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs")
564 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey2!) { rk, error in
565 XCTAssertNil(error, "error should be nil")
566 XCTAssertNotNil(rk, "rk should not be nil")
567 setRecoveryKeyExpectationAgain.fulfill()
569 self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10)
571 func testRKReplacement() throws {
572 OctagonRecoveryKeySetIsEnabled(true)
573 self.manager.setSOSEnabledForPlatformFlag(false)
574 self.startCKAccountStatusMock()
576 let initiatorContextID = "initiator-context-id"
577 self.cuttlefishContext.startOctagonStateMachine()
578 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
582 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
583 XCTAssertNotNil(clique, "Clique should not be nil")
585 XCTFail("Shouldn't have errored making new friends: \(error)")
589 self.verifyDatabaseMocks()
591 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
592 XCTAssertNotNil(entropy, "entropy should not be nil")
594 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
595 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
597 let bottle = self.fakeCuttlefishServer.state.bottles[0]
599 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
600 let initiatorConfigurationContext = OTConfigurationContext()
601 initiatorConfigurationContext.context = initiatorContextID
602 initiatorConfigurationContext.dsid = "1234"
603 initiatorConfigurationContext.altDSID = self.mockAuthKit.altDSID!
604 initiatorConfigurationContext.otControl = self.otControl
606 initiatorContext.startOctagonStateMachine()
607 self.sendContainerChange(context: initiatorContext)
608 let restoreExpectation = self.expectation(description: "restore returns")
610 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in
611 XCTAssertNil(error, "error should be nil")
612 restoreExpectation.fulfill()
614 self.wait(for: [restoreExpectation], timeout: 10)
616 self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
618 var initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
619 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
621 XCTAssertNotNil(dump, "dump should not be nil")
622 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
623 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
624 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
625 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
626 let included = dynamicInfo!["included"] as? Array<String>
627 XCTAssertNotNil(included, "included should not be nil")
628 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
630 initiatorDumpCallback.fulfill()
632 self.wait(for: [initiatorDumpCallback], timeout: 10)
633 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
634 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
635 self.manager.setSOSEnabledForPlatformFlag(true)
637 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
638 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
639 XCTAssertNil(error, "error should be nil")
640 XCTAssertNotNil(rk, "rk should not be nil")
641 setRecoveryKeyExpectation.fulfill()
643 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
645 let recoveryKey2 = SecRKCreateRecoveryKeyString(nil)
646 let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs")
647 TestsObjectiveC.setNewRecoveryKeyWithData(initiatorConfigurationContext, recoveryKey: recoveryKey2!) { rk, error in
648 XCTAssertNil(error, "error should be nil")
649 XCTAssertNotNil(rk, "rk should not be nil")
650 setRecoveryKeyExpectationAgain.fulfill()
652 self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10)
654 self.sendContainerChangeWaitForFetch(context: initiatorContext)
655 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
657 var initiatorRecoverySigningKey: Data?
658 var initiatorRecoveryEncryptionKey: Data?
660 var firstDeviceRecoverySigningKey: Data?
661 var firstDeviceRecoveryEncryptionKey: Data?
663 //now let's ensure recovery keys are set for both the first device and second device
664 initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
665 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
667 XCTAssertNotNil(dump, "dump should not be nil")
668 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
669 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
670 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
671 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
673 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
674 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
675 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
676 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
678 initiatorRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
679 initiatorRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
681 let included = dynamicInfo!["included"] as? Array<String>
682 XCTAssertNotNil(included, "included should not be nil")
683 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
684 let vouchers = dump!["vouchers"]
685 XCTAssertNotNil(vouchers, "vouchers should not be nil")
686 initiatorDumpCallback.fulfill()
688 self.wait(for: [initiatorDumpCallback], timeout: 10)
690 let firstDeviceDumpCallback = self.expectation(description: "firstDeviceDumpCallback callback occurs")
691 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
693 XCTAssertNotNil(dump, "dump should not be nil")
694 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
695 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
696 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
697 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
699 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
700 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
701 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
702 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
704 firstDeviceRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
705 firstDeviceRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
707 let included = dynamicInfo!["included"] as? Array<String>
708 XCTAssertNotNil(included, "included should not be nil")
709 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
710 let vouchers = dump!["vouchers"]
711 XCTAssertNotNil(vouchers, "vouchers should not be nil")
712 firstDeviceDumpCallback.fulfill()
714 self.wait(for: [firstDeviceDumpCallback], timeout: 10)
716 XCTAssertEqual(firstDeviceRecoverySigningKey, initiatorRecoverySigningKey, "recovery signing keys should be equal")
717 XCTAssertEqual(firstDeviceRecoveryEncryptionKey, initiatorRecoveryEncryptionKey, "recovery encryption keys should be equal")
720 func testOTCliqueJoiningUsingRecoveryKey() throws {
721 OctagonRecoveryKeySetIsEnabled(true)
722 self.manager.setSOSEnabledForPlatformFlag(false)
723 self.startCKAccountStatusMock()
725 let establishContextID = "establish-context-id"
726 let establishContext = self.createEstablishContext(contextID: establishContextID)
728 establishContext.startOctagonStateMachine()
729 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
732 let recoverykeyotcliqueContext = OTConfigurationContext()
733 recoverykeyotcliqueContext.context = establishContextID
734 recoverykeyotcliqueContext.dsid = "1234"
735 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
736 recoverykeyotcliqueContext.otControl = self.otControl
738 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
739 XCTAssertNotNil(clique, "Clique should not be nil")
740 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
742 XCTFail("Shouldn't have errored making new friends: \(error)")
746 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
747 self.assertConsidersSelfTrusted(context: establishContext)
749 // Fake that this peer also created some TLKShares for itself
750 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
751 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
753 self.assertSelfTLKSharesInCloudKit(context: establishContext)
755 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
756 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
757 self.manager.setSOSEnabledForPlatformFlag(true)
759 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
760 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
761 XCTAssertNil(error, "error should be nil")
762 setRecoveryKeyExpectation.fulfill()
764 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
766 self.sendContainerChangeWaitForFetch(context: establishContext)
768 let newCliqueContext = OTConfigurationContext()
769 newCliqueContext.context = OTDefaultContext
770 newCliqueContext.dsid = self.otcliqueContext.dsid
771 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
772 newCliqueContext.otControl = self.otControl
774 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
775 newGuyContext.startOctagonStateMachine()
777 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
779 self.manager.setSOSEnabledForPlatformFlag(true)
780 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
781 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
782 XCTAssertNil(error, "error should be nil")
783 joinWithRecoveryKeyExpectation.fulfill()
785 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
787 self.sendContainerChangeWaitForFetch(context: newGuyContext)
789 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
790 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
792 XCTAssertNotNil(dump, "dump should not be nil")
793 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
794 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
795 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
796 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
798 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
799 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
800 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
801 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
803 let included = dynamicInfo!["included"] as? Array<String>
804 XCTAssertNotNil(included, "included should not be nil")
805 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
806 let vouchers = dump!["vouchers"]
807 XCTAssertNotNil(vouchers, "vouchers should not be nil")
808 stableInfoAcceptorCheckDumpCallback.fulfill()
810 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
811 self.assertEnters(context: newGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
812 self.assertConsidersSelfTrusted(context: newGuyContext)
813 try self.putSelfTLKShareInCloudKit(context: newGuyContext, zoneID: self.manateeZoneID)
814 self.assertSelfTLKSharesInCloudKit(context: newGuyContext)
816 self.sendContainerChangeWaitForFetch(context: establishContext)
818 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
819 self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) {
821 XCTAssertNotNil(dump, "dump should not be nil")
822 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
823 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
824 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
825 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
827 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
828 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
829 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
830 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
832 let included = dynamicInfo!["included"] as? Array<String>
833 XCTAssertNotNil(included, "included should not be nil")
834 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
835 let vouchers = dump!["vouchers"]
836 XCTAssertNotNil(vouchers, "vouchers should not be nil")
837 stableInfoCheckDumpCallback.fulfill()
839 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
842 func testOTCliqueJoinUsingANotEnrolledRecoveryKey() throws {
843 OctagonRecoveryKeySetIsEnabled(true)
844 self.manager.setSOSEnabledForPlatformFlag(false)
845 self.startCKAccountStatusMock()
847 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
848 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
850 let newCliqueContext = OTConfigurationContext()
851 newCliqueContext.context = OTDefaultContext
852 newCliqueContext.dsid = self.otcliqueContext.dsid
853 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
854 newCliqueContext.otControl = self.otControl
856 let recoveryGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
857 self.manager.setSOSEnabledForPlatformFlag(true)
859 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
860 TestsObjectiveC.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
861 XCTAssertNil(error, "error should be nil")
862 joinWithRecoveryKeyExpectation.fulfill()
864 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
866 self.sendContainerChange(context: recoveryGuyContext)
868 let newGuyCheckDumpCallback = self.expectation(description: "newGuyCheckDumpCallback callback occurs")
869 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
871 XCTAssertNotNil(dump, "dump should not be nil")
872 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
873 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
874 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
875 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
877 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
878 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
879 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
880 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
882 let included = dynamicInfo!["included"] as? Array<String>
883 XCTAssertNotNil(included, "included should not be nil")
884 XCTAssertEqual(included!.count, 1, "should be 1 peer ids")
885 let vouchers = dump!["vouchers"]
886 XCTAssertNotNil(vouchers, "vouchers should not be nil")
887 newGuyCheckDumpCallback.fulfill()
889 self.wait(for: [newGuyCheckDumpCallback], timeout: 10)
890 self.assertEnters(context: recoveryGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
892 self.assertSelfTLKSharesInCloudKit(context: recoveryGuyContext)
895 func testSetRecoveryKeyAsLimitedPeer() throws {
896 self.manager.setSOSEnabledForPlatformFlag(false)
898 self.startCKAccountStatusMock()
900 self.cuttlefishContext.startOctagonStateMachine()
901 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
903 XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices")
907 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
908 XCTAssertNotNil(clique, "Clique should not be nil")
910 XCTFail("Shouldn't have errored making new friends: \(error)")
914 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
915 XCTAssertNotNil(entropy, "entropy should not be nil")
917 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
918 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
920 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
921 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
923 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
924 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
925 XCTAssertNotNil(error, "error should not be nil")
926 XCTAssertEqual((error! as NSError).code, OctagonError.OTErrorLimitedPeer.rawValue, "error code should be limited peer")
927 createKeyExpectation.fulfill()
929 self.wait(for: [createKeyExpectation], timeout: 10)
932 func testVouchWithRecoveryKeySetByUntrustedPeer() throws {
933 OctagonRecoveryKeySetIsEnabled(true)
934 self.manager.setSOSEnabledForPlatformFlag(false)
935 self.startCKAccountStatusMock()
937 let establishContextID = "establish-context-id"
938 let establishContext = self.createEstablishContext(contextID: establishContextID)
940 establishContext.startOctagonStateMachine()
941 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
944 let recoverykeyotcliqueContext = OTConfigurationContext()
945 recoverykeyotcliqueContext.context = establishContextID
946 recoverykeyotcliqueContext.dsid = "1234"
947 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
948 recoverykeyotcliqueContext.otControl = self.otControl
950 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
951 XCTAssertNotNil(clique, "Clique should not be nil")
952 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
954 XCTFail("Shouldn't have errored making new friends: \(error)")
958 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
959 self.assertConsidersSelfTrusted(context: establishContext)
961 // Fake that this peer also created some TLKShares for itself
962 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
963 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
965 self.assertSelfTLKSharesInCloudKit(context: establishContext)
967 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
968 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
969 self.manager.setSOSEnabledForPlatformFlag(true)
971 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
972 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
973 XCTAssertNil(error, "error should be nil")
974 setRecoveryKeyExpectation.fulfill()
976 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
978 self.sendContainerChangeWaitForFetch(context: establishContext)
980 //now this peer will leave octagon
981 XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique")
983 // securityd should now consider itself untrusted
984 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
985 self.assertConsidersSelfUntrusted(context: establishContext)
987 let newCliqueContext = OTConfigurationContext()
988 newCliqueContext.context = OTDefaultContext
989 newCliqueContext.dsid = self.otcliqueContext.dsid
990 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
991 newCliqueContext.otControl = self.otControl
993 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
994 newGuyContext.startOctagonStateMachine()
996 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
998 self.manager.setSOSEnabledForPlatformFlag(true)
999 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1000 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1001 XCTAssertNil(error, "error should be nil")
1002 joinWithRecoveryKeyExpectation.fulfill()
1004 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1007 func testVouchWithWrongRecoveryKey() throws {
1008 OctagonRecoveryKeySetIsEnabled(true)
1009 self.manager.setSOSEnabledForPlatformFlag(false)
1010 self.startCKAccountStatusMock()
1012 let establishContextID = "establish-context-id"
1013 let establishContext = self.createEstablishContext(contextID: establishContextID)
1015 establishContext.startOctagonStateMachine()
1016 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1018 let clique: OTClique
1019 let recoverykeyotcliqueContext = OTConfigurationContext()
1020 recoverykeyotcliqueContext.context = establishContextID
1021 recoverykeyotcliqueContext.dsid = "1234"
1022 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1023 recoverykeyotcliqueContext.otControl = self.otControl
1025 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1026 XCTAssertNotNil(clique, "Clique should not be nil")
1027 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1029 XCTFail("Shouldn't have errored making new friends: \(error)")
1033 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1034 self.assertConsidersSelfTrusted(context: establishContext)
1036 // Fake that this peer also created some TLKShares for itself
1037 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1038 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1040 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1042 var recoveryKey = SecRKCreateRecoveryKeyString(nil)
1043 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1044 self.manager.setSOSEnabledForPlatformFlag(true)
1046 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
1047 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
1048 XCTAssertNil(error, "error should be nil")
1049 setRecoveryKeyExpectation.fulfill()
1051 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
1053 self.sendContainerChangeWaitForFetch(context: establishContext)
1055 let newCliqueContext = OTConfigurationContext()
1056 newCliqueContext.context = OTDefaultContext
1057 newCliqueContext.dsid = self.otcliqueContext.dsid
1058 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1059 newCliqueContext.otControl = self.otControl
1061 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1062 newGuyContext.startOctagonStateMachine()
1064 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1066 self.manager.setSOSEnabledForPlatformFlag(true)
1067 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1069 //creating new random recovery key
1070 recoveryKey = SecRKCreateRecoveryKeyString(nil)
1071 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1073 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1074 XCTAssertNotNil(error, "error should NOT be nil")
1075 XCTAssertEqual((error! as NSError).code, 32, "error code should be 32/untrusted recovery keys")
1076 XCTAssertEqual((error! as NSError).domain, "com.apple.security.trustedpeers.container", "error code domain should be com.apple.security.trustedpeers.container")
1077 joinWithRecoveryKeyExpectation.fulfill()
1079 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1082 func testRecoveryWithDistrustedPeers() throws {
1083 OctagonRecoveryKeySetIsEnabled(true)
1084 self.manager.setSOSEnabledForPlatformFlag(false)
1085 self.startCKAccountStatusMock()
1087 let establishContextID = "establish-context-id"
1088 let establishContext = self.createEstablishContext(contextID: establishContextID)
1090 establishContext.startOctagonStateMachine()
1091 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1093 let clique: OTClique
1094 let recoverykeyotcliqueContext = OTConfigurationContext()
1095 recoverykeyotcliqueContext.context = establishContextID
1096 recoverykeyotcliqueContext.dsid = "1234"
1097 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1098 recoverykeyotcliqueContext.otControl = self.otControl
1100 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1101 XCTAssertNotNil(clique, "Clique should not be nil")
1102 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1104 XCTFail("Shouldn't have errored making new friends: \(error)")
1108 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1109 self.assertConsidersSelfTrusted(context: establishContext)
1111 // Fake that this peer also created some TLKShares for itself
1112 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1113 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1115 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1117 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1118 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1119 self.manager.setSOSEnabledForPlatformFlag(true)
1121 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
1122 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
1123 XCTAssertNil(error, "error should be nil")
1124 setRecoveryKeyExpectation.fulfill()
1126 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
1128 self.sendContainerChangeWaitForFetch(context: establishContext)
1130 //now this peer will leave octagon
1131 XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique")
1133 // securityd should now consider itself untrusted
1134 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1135 self.assertConsidersSelfUntrusted(context: establishContext)
1137 let newCliqueContext = OTConfigurationContext()
1138 newCliqueContext.context = OTDefaultContext
1139 newCliqueContext.dsid = self.otcliqueContext.dsid
1140 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1141 newCliqueContext.otControl = self.otControl
1143 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1144 newGuyContext.startOctagonStateMachine()
1146 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1148 self.manager.setSOSEnabledForPlatformFlag(true)
1149 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1151 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1152 XCTAssertNil(error, "error should be nil")
1153 joinWithRecoveryKeyExpectation.fulfill()
1155 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1158 func testMalformedRecoveryKey() throws {
1159 OctagonRecoveryKeySetIsEnabled(true)
1160 self.manager.setSOSEnabledForPlatformFlag(false)
1161 self.startCKAccountStatusMock()
1163 let establishContextID = "establish-context-id"
1164 let establishContext = self.createEstablishContext(contextID: establishContextID)
1166 establishContext.startOctagonStateMachine()
1167 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1169 let clique: OTClique
1170 let recoverykeyotcliqueContext = OTConfigurationContext()
1171 recoverykeyotcliqueContext.context = establishContextID
1172 recoverykeyotcliqueContext.dsid = "1234"
1173 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1174 recoverykeyotcliqueContext.otControl = self.otControl
1176 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1177 XCTAssertNotNil(clique, "Clique should not be nil")
1178 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1180 XCTFail("Shouldn't have errored making new friends: \(error)")
1184 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1185 self.assertConsidersSelfTrusted(context: establishContext)
1187 // Fake that this peer also created some TLKShares for itself
1188 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1189 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1191 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1193 let recoveryKey = "malformedRecoveryKey"
1194 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1195 self.manager.setSOSEnabledForPlatformFlag(true)
1197 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
1198 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
1199 XCTAssertNotNil(error, "error should NOT be nil")
1200 XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key")
1201 XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon")
1202 createKeyExpectation.fulfill()
1204 self.wait(for: [createKeyExpectation], timeout: 10)
1206 let newCliqueContext = OTConfigurationContext()
1207 newCliqueContext.context = OTDefaultContext
1208 newCliqueContext.dsid = self.otcliqueContext.dsid
1209 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1210 newCliqueContext.otControl = self.otControl
1212 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1213 newGuyContext.startOctagonStateMachine()
1215 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1217 self.manager.setSOSEnabledForPlatformFlag(true)
1218 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1220 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey) { error in
1221 XCTAssertNotNil(error, "error should NOT be nil")
1222 XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key")
1223 XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon")
1224 joinWithRecoveryKeyExpectation.fulfill()
1226 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)