OctagonTests+RecoveryKey.swift [plain text]
#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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let included = dynamicInfo!["included"] as? Array<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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<String, AnyObject>
XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
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<String>
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