#if OCTAGON @objcMembers class OctagonRecoveryKeyTests: OctagonTestsBase { override func setUp() { super.setUp() } func testSetRecoveryKey() throws { self.startCKAccountStatusMock() self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices") let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } self.wait(for: [createKeyExpectation], timeout: 10) } func testSetRecoveryKeyPeerReaction() throws { self.startCKAccountStatusMock() self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.verifyDatabaseMocks() let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } self.wait(for: [createKeyExpectation], timeout: 10) let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.verifyDatabaseMocks() let bottle = self.fakeCuttlefishServer.state.bottles[0] let initiatorContextID = "new guy" let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID, authKitAdapter: self.mockAuthKit2) initiatorContext.startOctagonStateMachine() self.sendContainerChange(context: initiatorContext) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } self.wait(for: [joinWithBottleExpectation], timeout: 10) self.verifyDatabaseMocks() self.sendContainerChangeWaitForFetch(context: initiatorContext) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.verifyDatabaseMocks() let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") stableInfoCheckDumpCallback.fulfill() } self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") stableInfoAcceptorCheckDumpCallback.fulfill() } self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) self.verifyDatabaseMocks() } func testSetRecoveryKey3PeerReaction() throws { self.startCKAccountStatusMock() self.manager.setSOSEnabledForPlatformFlag(false) self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) self.verifyDatabaseMocks() let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createKeyExpectation.fulfill() } self.wait(for: [createKeyExpectation], timeout: 10) let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] let initiatorContextID = "new guy" let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID) initiatorContext.startOctagonStateMachine() self.sendContainerChange(context: initiatorContext) self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs") initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in XCTAssertNil(error, "error should be nil") joinWithBottleExpectation.fulfill() } self.wait(for: [joinWithBottleExpectation], timeout: 10) self.verifyDatabaseMocks() self.sendContainerChangeWaitForFetch(context: initiatorContext) // The first peer will upload TLKs for the new peer self.assertAllCKKSViewsUpload(tlkShares: 1) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC) self.verifyDatabaseMocks() let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") stableInfoCheckDumpCallback.fulfill() } self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") stableInfoAcceptorCheckDumpCallback.fulfill() } self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) let thirdPeerContextID = "3rd guy" let thirdPeerContext = self.makeInitiatorContext(contextID: thirdPeerContextID, authKitAdapter: self.mockAuthKit3) thirdPeerContext.startOctagonStateMachine() self.sendContainerChange(context: thirdPeerContext) let thirdPeerJoinWithBottleExpectation = self.expectation(description: "thirdPeerJoinWithBottleExpectation callback occurs") thirdPeerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in XCTAssertNil(error, "error should be nil") thirdPeerJoinWithBottleExpectation.fulfill() } self.wait(for: [thirdPeerJoinWithBottleExpectation], timeout: 10) self.verifyDatabaseMocks() self.sendContainerChangeWaitForFetch(context: thirdPeerContext) let thirdPeerStableInfoCheckDumpCallback = self.expectation(description: "thirdPeerStableInfoCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: thirdPeerContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 3, "should be 3df peer ids") thirdPeerStableInfoCheckDumpCallback.fulfill() } self.wait(for: [thirdPeerStableInfoCheckDumpCallback], timeout: 10) } func createEstablishContext(contextID: String) -> OTCuttlefishContext { return self.manager.context(forContainerName: OTCKContainerName, contextID: contextID, sosAdapter: self.mockSOSAdapter, authKitAdapter: self.mockAuthKit2, lockStateTracker: self.lockStateTracker, accountStateTracker: self.accountStateTracker, deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-RK-iphone", serialNumber: "456", osVersion: "iOS (fake version)")) } func testJoinWithRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let bottlerotcliqueContext = OTConfigurationContext() bottlerotcliqueContext.context = establishContextID bottlerotcliqueContext.dsid = "1234" bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID! bottlerotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createRecoveryExpectation.fulfill() } self.wait(for: [createRecoveryExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) recoveryContext.startOctagonStateMachine() self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs") recoveryContext.join(withRecoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: recoveryContext) let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") stableInfoCheckDumpCallback.fulfill() } self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") stableInfoAcceptorCheckDumpCallback.fulfill() } self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) try self.putSelfTLKShareInCloudKit(context: recoveryContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: recoveryContext) } func testJoinWithRecoveryKeyWithCKKSConflict() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let bottlerotcliqueContext = OTConfigurationContext() bottlerotcliqueContext.context = establishContextID bottlerotcliqueContext.dsid = "1234" bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID! bottlerotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") createRecoveryExpectation.fulfill() } self.wait(for: [createRecoveryExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) self.silentFetchesAllowed = false self.expectCKFetchAndRun(beforeFinished: { self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID) self.silentFetchesAllowed = true }) let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) recoveryContext.startOctagonStateMachine() self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs") recoveryContext.join(withRecoveryKey: recoveryKey) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) self.assertConsidersSelfTrusted(context: recoveryContext) self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC) } func testOTCliqueSettingRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in XCTAssertNil(error, "error should be nil") XCTAssertNotNil(rk, "rk should not be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) } func testOTCliqueSet2ndRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in XCTAssertNil(error, "error should be nil") XCTAssertNotNil(rk, "rk should not be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) let recoveryKey2 = SecRKCreateRecoveryKeyString(nil) let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey2!) { rk, error in XCTAssertNil(error, "error should be nil") XCTAssertNotNil(rk, "rk should not be nil") setRecoveryKeyExpectationAgain.fulfill() } self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10) } func testRKReplacement() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let initiatorContextID = "initiator-context-id" self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.verifyDatabaseMocks() let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let bottle = self.fakeCuttlefishServer.state.bottles[0] let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID) let initiatorConfigurationContext = OTConfigurationContext() initiatorConfigurationContext.context = initiatorContextID initiatorConfigurationContext.dsid = "1234" initiatorConfigurationContext.altDSID = self.mockAuthKit.altDSID! initiatorConfigurationContext.otControl = self.otControl initiatorContext.startOctagonStateMachine() self.sendContainerChange(context: initiatorContext) let restoreExpectation = self.expectation(description: "restore returns") self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in XCTAssertNil(error, "error should be nil") restoreExpectation.fulfill() } self.wait(for: [restoreExpectation], timeout: 10) self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) var initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") initiatorDumpCallback.fulfill() } self.wait(for: [initiatorDumpCallback], timeout: 10) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in XCTAssertNil(error, "error should be nil") XCTAssertNotNil(rk, "rk should not be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) let recoveryKey2 = SecRKCreateRecoveryKeyString(nil) let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(initiatorConfigurationContext, recoveryKey: recoveryKey2!) { rk, error in XCTAssertNil(error, "error should be nil") XCTAssertNotNil(rk, "rk should not be nil") setRecoveryKeyExpectationAgain.fulfill() } self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10) self.sendContainerChangeWaitForFetch(context: initiatorContext) self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext) var initiatorRecoverySigningKey: Data? var initiatorRecoveryEncryptionKey: Data? var firstDeviceRecoverySigningKey: Data? var firstDeviceRecoveryEncryptionKey: Data? //now let's ensure recovery keys are set for both the first device and second device initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") initiatorRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data initiatorRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") initiatorDumpCallback.fulfill() } self.wait(for: [initiatorDumpCallback], timeout: 10) let firstDeviceDumpCallback = self.expectation(description: "firstDeviceDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") firstDeviceRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data firstDeviceRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") firstDeviceDumpCallback.fulfill() } self.wait(for: [firstDeviceDumpCallback], timeout: 10) XCTAssertEqual(firstDeviceRecoverySigningKey, initiatorRecoverySigningKey, "recovery signing keys should be equal") XCTAssertEqual(firstDeviceRecoveryEncryptionKey, initiatorRecoveryEncryptionKey, "recovery encryption keys should be equal") } func testOTCliqueJoiningUsingRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = establishContextID recoverykeyotcliqueContext.dsid = "1234" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in XCTAssertNil(error, "error should be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) newGuyContext.startOctagonStateMachine() self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: newGuyContext) let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") stableInfoAcceptorCheckDumpCallback.fulfill() } self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10) self.assertEnters(context: newGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: newGuyContext) try self.putSelfTLKShareInCloudKit(context: newGuyContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: newGuyContext) self.sendContainerChangeWaitForFetch(context: establishContext) let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 2, "should be 2 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") stableInfoCheckDumpCallback.fulfill() } self.wait(for: [stableInfoCheckDumpCallback], timeout: 10) } func testOTCliqueJoinUsingANotEnrolledRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let recoveryGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") TestsObjectiveC.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) self.sendContainerChange(context: recoveryGuyContext) let newGuyCheckDumpCallback = self.expectation(description: "newGuyCheckDumpCallback callback occurs") self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) { dump, _ in XCTAssertNotNil(dump, "dump should not be nil") let egoSelf = dump!["self"] as? Dictionary XCTAssertNotNil(egoSelf, "egoSelf should not be nil") let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil") let stableInfo = egoSelf!["stableInfo"] as? Dictionary XCTAssertNotNil(stableInfo, "stableInfo should not be nil") XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil") XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil") let included = dynamicInfo!["included"] as? Array XCTAssertNotNil(included, "included should not be nil") XCTAssertEqual(included!.count, 1, "should be 1 peer ids") let vouchers = dump!["vouchers"] XCTAssertNotNil(vouchers, "vouchers should not be nil") newGuyCheckDumpCallback.fulfill() } self.wait(for: [newGuyCheckDumpCallback], timeout: 10) self.assertEnters(context: recoveryGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertSelfTLKSharesInCloudKit(context: recoveryGuyContext) } func testSetRecoveryKeyAsLimitedPeer() throws { self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() self.cuttlefishContext.startOctagonStateMachine() self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices") let clique: OTClique do { clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!) XCTAssertNotNil(entropy, "entropy should not be nil") self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: self.cuttlefishContext) let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in XCTAssertNotNil(error, "error should not be nil") XCTAssertEqual((error! as NSError).code, Int(OTErrorLimitedPeer.rawValue), "error code should be limited peer") createKeyExpectation.fulfill() } self.wait(for: [createKeyExpectation], timeout: 10) } func testVouchWithRecoveryKeySetByUntrustedPeer() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = establishContextID recoverykeyotcliqueContext.dsid = "1234" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in XCTAssertNil(error, "error should be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) //now this peer will leave octagon XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique") // securityd should now consider itself untrusted self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: establishContext) let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) newGuyContext.startOctagonStateMachine() self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) } func testVouchWithWrongRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = establishContextID recoverykeyotcliqueContext.dsid = "1234" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) var recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in XCTAssertNil(error, "error should be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) newGuyContext.startOctagonStateMachine() self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") //creating new random recovery key recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in XCTAssertNotNil(error, "error should NOT be nil") XCTAssertEqual((error! as NSError).code, 32, "error code should be 32/untrusted recovery keys") XCTAssertEqual((error! as NSError).domain, "com.apple.security.trustedpeers.container", "error code domain should be com.apple.security.trustedpeers.container") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) } func testRecoveryWithDistrustedPeers() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = establishContextID recoverykeyotcliqueContext.dsid = "1234" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = SecRKCreateRecoveryKeyString(nil) XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs") TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in XCTAssertNil(error, "error should be nil") setRecoveryKeyExpectation.fulfill() } self.wait(for: [setRecoveryKeyExpectation], timeout: 10) self.sendContainerChangeWaitForFetch(context: establishContext) //now this peer will leave octagon XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique") // securityd should now consider itself untrusted self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfUntrusted(context: establishContext) let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) newGuyContext.startOctagonStateMachine() self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in XCTAssertNil(error, "error should be nil") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) } func testMalformedRecoveryKey() throws { OctagonRecoveryKeySetIsEnabled(true) self.manager.setSOSEnabledForPlatformFlag(false) self.startCKAccountStatusMock() let establishContextID = "establish-context-id" let establishContext = self.createEstablishContext(contextID: establishContextID) establishContext.startOctagonStateMachine() self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC) let clique: OTClique let recoverykeyotcliqueContext = OTConfigurationContext() recoverykeyotcliqueContext.context = establishContextID recoverykeyotcliqueContext.dsid = "1234" recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID! recoverykeyotcliqueContext.otControl = self.otControl do { clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated) XCTAssertNotNil(clique, "Clique should not be nil") XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call") } catch { XCTFail("Shouldn't have errored making new friends: \(error)") throw error } self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC) self.assertConsidersSelfTrusted(context: establishContext) // Fake that this peer also created some TLKShares for itself self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID) try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID) self.assertSelfTLKSharesInCloudKit(context: establishContext) let recoveryKey = "malformedRecoveryKey" XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil") self.manager.setSOSEnabledForPlatformFlag(true) let createKeyExpectation = self.expectation(description: "createKeyExpectation returns") self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in XCTAssertNotNil(error, "error should NOT be nil") XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key") XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon") createKeyExpectation.fulfill() } self.wait(for: [createKeyExpectation], timeout: 10) let newCliqueContext = OTConfigurationContext() newCliqueContext.context = OTDefaultContext newCliqueContext.dsid = self.otcliqueContext.dsid newCliqueContext.altDSID = self.mockAuthKit.altDSID! newCliqueContext.otControl = self.otControl let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext) newGuyContext.startOctagonStateMachine() self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext) self.manager.setSOSEnabledForPlatformFlag(true) let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs") OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey) { error in XCTAssertNotNil(error, "error should NOT be nil") XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key") XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon") joinWithRecoveryKeyExpectation.fulfill() } self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10) } } #endif