OctagonTests+CoreFollowUp.swift [plain text]
#if OCTAGON
class OctagonCoreFollowUpTests: OctagonTestsBase {
func testAttemptedJoinStateAttempted() throws {
self.startCKAccountStatusMock()
// Prepare an identity, then pretend like securityd thought it was in the right account
let containerName = OTCKContainerName
let contextName = OTDefaultContext
var selfPeerID: String?
let prepareExpectation = self.expectation(description: "prepare callback occurs")
tphClient.prepare(withContainer: containerName,
context: contextName,
epoch: 0,
machineID: "asdf",
bottleSalt: "123456789",
bottleID: UUID().uuidString,
modelID: "asdf",
deviceName: "asdf",
serialNumber: "1234",
osVersion: "asdf",
policyVersion: nil,
policySecrets: nil,
signingPrivKeyPersistentRef: nil,
encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
XCTAssertNil(error, "Should be no error preparing identity")
XCTAssertNotNil(peerID, "Should be a peer ID")
XCTAssertNotNil(permanentInfo, "Should have a permenent info")
XCTAssertNotNil(permanentInfoSig, "Should have a permanent info signature")
XCTAssertNotNil(stableInfo, "Should have a stable info")
XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
selfPeerID = peerID
prepareExpectation.fulfill()
}
self.wait(for: [prepareExpectation], timeout: 10)
let account = OTAccountMetadataClassC()!
account.peerID = selfPeerID
account.icloudAccountState = .ACCOUNT_AVAILABLE
account.trustState = .TRUSTED
account.attemptedJoin = .ATTEMPTED
XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata")
OctagonInitialize()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
// CKKS should be waiting for assistance
assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
#if !os(tvOS)
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU")
#else
// Apple TV should not post a CFU, as there's no peers to join
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
#endif
}
func testAttemptedJoinNotAttemptedStateSOSEnabled() throws {
self.startCKAccountStatusMock()
self.mockSOSAdapter.sosEnabled = true
self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
// Prepare an identity, then pretend like securityd thought it was in the right account
let containerName = OTCKContainerName
let contextName = OTDefaultContext
var selfPeerID: String?
let prepareExpectation = self.expectation(description: "prepare callback occurs")
tphClient.prepare(withContainer: containerName,
context: contextName,
epoch: 0,
machineID: "asdf",
bottleSalt: "123456789",
bottleID: UUID().uuidString,
modelID: "asdf",
deviceName: "asdf",
serialNumber: "1234",
osVersion: "asdf",
policyVersion: nil,
policySecrets: nil,
signingPrivKeyPersistentRef: nil,
encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
XCTAssertNil(error, "Should be no error preparing identity")
XCTAssertNotNil(peerID, "Should be a peer ID")
XCTAssertNotNil(permanentInfo, "Should have a permenent info")
XCTAssertNotNil(permanentInfoSig, "Should have a permanent info signature")
XCTAssertNotNil(stableInfo, "Should have a stable info")
XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
selfPeerID = peerID
prepareExpectation.fulfill()
}
self.wait(for: [prepareExpectation], timeout: 10)
let account = OTAccountMetadataClassC()!
account.peerID = selfPeerID
account.icloudAccountState = .ACCOUNT_AVAILABLE
account.trustState = .TRUSTED
account.attemptedJoin = .NOTATTEMPTED
XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata")
OctagonInitialize()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
// CKKS should be waiting for assistance
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
// Since SOS isn't around to help, Octagon should post a CFU
#if os(tvOS)
XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV (due to having no peers to join)")
#else
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS can't help")
#endif
}
func testAttemptedJoinNotAttemptedStateSOSError() throws {
self.startCKAccountStatusMock()
self.mockSOSAdapter.sosEnabled = true
self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCError)
// Prepare an identity, then pretend like securityd thought it was in the right account
let containerName = OTCKContainerName
let contextName = OTDefaultContext
var selfPeerID: String?
let prepareExpectation = self.expectation(description: "prepare callback occurs")
tphClient.prepare(withContainer: containerName,
context: contextName,
epoch: 0,
machineID: "asdf",
bottleSalt: "123456789",
bottleID: UUID().uuidString,
modelID: "asdf",
deviceName: "asdf",
serialNumber: "1234",
osVersion: "asdf",
policyVersion: nil,
policySecrets: nil,
signingPrivKeyPersistentRef: nil,
encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
XCTAssertNil(error, "Should be no error preparing identity")
XCTAssertNotNil(peerID, "Should be a peer ID")
XCTAssertNotNil(permanentInfo, "Should have a permenent info")
XCTAssertNotNil(permanentInfoSig, "Should have a permanent info signature")
XCTAssertNotNil(stableInfo, "Should have a stable info")
XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
selfPeerID = peerID
prepareExpectation.fulfill()
}
self.wait(for: [prepareExpectation], timeout: 10)
let account = OTAccountMetadataClassC()!
account.peerID = selfPeerID
account.icloudAccountState = .ACCOUNT_AVAILABLE
account.trustState = .TRUSTED
account.attemptedJoin = .NOTATTEMPTED
XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata")
OctagonInitialize()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
// CKKS should be waiting for assistance
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
// Since SOS is in 'error', octagon shouldn't post until SOS can say y/n
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "should NOT have posted an repair CFU")
}
func testAttemptedJoinNotAttemptedStateSOSDisabled() throws {
self.startCKAccountStatusMock()
// Octagon only examines the JoinState if SOS is enabled
self.mockSOSAdapter.sosEnabled = false
// No need to mock not joining; Octagon won't have attempted a join if we just start it
self.cuttlefishContext.startOctagonStateMachine()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
// CKKS should be waiting for assistance
assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
#if !os(tvOS)
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS is disabled")
#else
// Apple TV should not post a CFU, as there's no peers to join
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
#endif
}
func testAttemptedJoinStateUnknown() throws {
self.startCKAccountStatusMock()
// Prepare an identity, then pretend like securityd thought it was in the right account
let containerName = OTCKContainerName
let contextName = OTDefaultContext
var selfPeerID: String?
let prepareExpectation = self.expectation(description: "prepare callback occurs")
tphClient.prepare(withContainer: containerName,
context: contextName,
epoch: 0,
machineID: "asdf",
bottleSalt: "123456789",
bottleID: UUID().uuidString,
modelID: "asdf",
deviceName: "asdf",
serialNumber: "1234",
osVersion: "asdf",
policyVersion: nil,
policySecrets: nil,
signingPrivKeyPersistentRef: nil,
encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error in
XCTAssertNil(error, "Should be no error preparing identity")
XCTAssertNotNil(peerID, "Should be a peer ID")
XCTAssertNotNil(permanentInfo, "Should have a permenent info")
XCTAssertNotNil(permanentInfoSig, "Should have a permanent info signature")
XCTAssertNotNil(stableInfo, "Should have a stable info")
XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
selfPeerID = peerID
prepareExpectation.fulfill()
}
self.wait(for: [prepareExpectation], timeout: 10)
let account = OTAccountMetadataClassC()!
account.peerID = selfPeerID
account.icloudAccountState = .ACCOUNT_AVAILABLE
account.trustState = .TRUSTED
account.attemptedJoin = .UNKNOWN
XCTAssertNoThrow(try account.saveToKeychain(forContainer: containerName, contextID: contextName), "Should be no error saving fake account metadata")
OctagonInitialize()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
// CKKS should be waiting for assistance
assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
#if !os(tvOS)
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU")
#else
// Apple TV should not post a CFU, as there's no peers to join
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
#endif
}
#if os(tvOS)
func testPostCFUWhenApprovalCapablePeerJoins() throws {
self.startCKAccountStatusMock()
// Octagon only examines the JoinState if SOS is enabled
self.mockSOSAdapter.sosEnabled = false
self.cuttlefishContext.startOctagonStateMachine()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
// Apple TV should not post a CFU, as there's no peers to join
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
// Now, an iphone appears!
let iphone = self.manager.context(forContainerName: OTCKContainerName,
contextID: "asdf",
sosAdapter: self.mockSOSAdapter,
authKitAdapter: self.mockAuthKit2,
lockStateTracker: self.lockStateTracker,
accountStateTracker: self.accountStateTracker,
deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
iphone.startOctagonStateMachine()
let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablishExpectation returns")
iphone.rpcResetAndEstablish(.testGenerated) { resetError in
XCTAssertNil(resetError, "should be no error resetting and establishing")
resetAndEstablishExpectation.fulfill()
}
self.wait(for: [resetAndEstablishExpectation], timeout: 10)
self.sendContainerChangeWaitForUntrustedFetch(context: self.cuttlefishContext)
// The TV should now post a CFU, as there's an iphone that can repair it
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "appleTV should have posted a repair CFU")
}
func testDontPostCFUWhenApprovalIncapablePeerJoins() throws {
self.startCKAccountStatusMock()
// Octagon only examines the JoinState if SOS is enabled
self.mockSOSAdapter.sosEnabled = false
self.cuttlefishContext.startOctagonStateMachine()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
// Apple TV should not post a CFU, as there's no peers to join
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
// Now, a mac appears! macs cannot fix apple TVs.
let mac = self.manager.context(forContainerName: OTCKContainerName,
contextID: "asdf",
sosAdapter: self.mockSOSAdapter,
authKitAdapter: self.mockAuthKit2,
lockStateTracker: self.lockStateTracker,
accountStateTracker: self.accountStateTracker,
deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iMac7,1", deviceName: "test-mac", serialNumber: "456", osVersion: "macOS (fake version)"))
mac.startOctagonStateMachine()
let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablishExpectation returns")
mac.rpcResetAndEstablish(.testGenerated) { resetError in
XCTAssertNil(resetError, "should be no error resetting and establishing")
resetAndEstablishExpectation.fulfill()
}
self.wait(for: [resetAndEstablishExpectation], timeout: 10)
self.sendContainerChangeWaitForUntrustedFetch(context: self.cuttlefishContext)
// The TV should not post a CFU, as there's still no iPhone to repair it
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU; no devices present can repair it")
}
func testDontPostCFUWhenCapablePeersAreUntrusted() throws {
self.startCKAccountStatusMock()
// Octagon only examines the JoinState if SOS is enabled
self.mockSOSAdapter.sosEnabled = false
// An iPhone establishes some octagon state, then untrusts itself
// This is techinically an invalid situation, since Cuttlefish should have rejected the untrust, but it should trigger the condition we're interested in
let iphone = self.manager.context(forContainerName: OTCKContainerName,
contextID: "firstPhone",
sosAdapter: self.mockSOSAdapter,
authKitAdapter: self.mockAuthKit2,
lockStateTracker: self.lockStateTracker,
accountStateTracker: self.accountStateTracker,
deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
iphone.startOctagonStateMachine()
let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablishExpectation returns")
iphone.rpcResetAndEstablish(.testGenerated) { resetError in
XCTAssertNil(resetError, "should be no error resetting and establishing")
resetAndEstablishExpectation.fulfill()
}
self.wait(for: [resetAndEstablishExpectation], timeout: 10)
let iphonePeerID = try iphone.accountMetadataStore.loadOrCreateAccountMetadata().peerID!
let leaveExpectation = self.expectation(description: "rpcLeaveClique returns")
iphone.rpcLeaveClique { leaveError in
XCTAssertNil(leaveError, "Should be no error leaving")
leaveExpectation.fulfill()
}
self.wait(for: [leaveExpectation], timeout: 10)
XCTAssertTrue(self.fakeCuttlefishServer.assertCuttlefishState(FakeCuttlefishAssertion(peer: iphonePeerID, opinion: .excludes, target: iphonePeerID)),
"iphone should distrust itself")
self.cuttlefishContext.startOctagonStateMachine()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
// Ensure that the aTV has fetched properly
self.sendContainerChangeWaitForUntrustedFetch(context: self.cuttlefishContext)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
// Apple TV should not post a CFU, as the only iPhone around is untrusted
XCTAssertFalse(self.cuttlefishContext.postedRepairCFU, "appleTV should not have posted a repair CFU")
// Another iPhone resets the world
let iphone2 = self.manager.context(forContainerName: OTCKContainerName,
contextID: "firstPhone",
sosAdapter: self.mockSOSAdapter,
authKitAdapter: self.mockAuthKit3,
lockStateTracker: self.lockStateTracker,
accountStateTracker: self.accountStateTracker,
deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
iphone2.startOctagonStateMachine()
let resetAndEstablishExpectation2 = self.expectation(description: "resetAndEstablishExpectation returns")
iphone2.rpcResetAndEstablish(.testGenerated) { resetError in
XCTAssertNil(resetError, "should be no error resetting and establishing")
resetAndEstablishExpectation2.fulfill()
}
self.wait(for: [resetAndEstablishExpectation2], timeout: 10)
// The aTV is notified, and now posts a CFU
self.sendContainerChangeWaitForUntrustedFetch(context: self.cuttlefishContext)
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "appleTV should have posted a repair CFU")
}
#endif
func testPostCFUAfterSOSUpgradeFails() throws {
self.startCKAccountStatusMock()
self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCNotInCircle)
self.cuttlefishContext.startOctagonStateMachine()
self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
self.assertConsidersSelfUntrusted(context: self.cuttlefishContext)
self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLKCreation, within: 10 * NSEC_PER_SEC)
#if os(tvOS)
XCTAssertEqual(self.cuttlefishContext.postedRepairCFU, false, "Should not have posted a CFU on aTV (due to having no peers to join)")
#else
XCTAssertTrue(self.cuttlefishContext.postedRepairCFU, "should have posted an repair CFU, as SOS can't help")
#endif
}
}
#endif // OCTAGON