]> git.saurik.com Git - apple/security.git/blob - keychain/ot/tests/octagon/OctagonTests+Reset.swift
3bc43ec728c0c144dbc3f3e68b7dac8f12f8cd00
[apple/security.git] / keychain / ot / tests / octagon / OctagonTests+Reset.swift
1 #if OCTAGON
2
3 class OctagonResetTests: OctagonTestsBase {
4 func testAccountAvailableAndHandleExternalCall() throws {
5 self.startCKAccountStatusMock()
6
7 self.cuttlefishContext.startOctagonStateMachine()
8 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
9 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
10
11 _ = try self.cuttlefishContext.accountAvailable("13453464")
12
13 self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in
14 XCTAssertNil(resetError, "should be no error resetting and establishing")
15 }
16
17 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
18 self.verifyDatabaseMocks()
19 }
20
21 func testExernalCallAndAccountAvailable() throws {
22 self.startCKAccountStatusMock()
23
24 self.cuttlefishContext.startOctagonStateMachine()
25 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
26 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
27
28 self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in
29 XCTAssertNil(resetError, "should be no error resetting and establishing")
30 }
31
32 _ = try self.cuttlefishContext.accountAvailable("13453464")
33
34 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
35 self.verifyDatabaseMocks()
36 }
37
38 func testCallingAccountAvailableDuringResetAndEstablish() throws {
39 self.startCKAccountStatusMock()
40
41 self.cuttlefishContext.startOctagonStateMachine()
42 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
43 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
44
45 self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in
46 XCTAssertNil(resetError, "should be no error resetting and establishing")
47 }
48
49 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateResetAndEstablish, within: 1 * NSEC_PER_SEC)
50
51 _ = try self.cuttlefishContext.accountAvailable("13453464")
52
53 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
54 self.verifyDatabaseMocks()
55 }
56
57 func testResetAndEstablishWithEscrow() throws {
58 let contextName = OTDefaultContext
59 let containerName = OTCKContainerName
60
61 self.startCKAccountStatusMock()
62
63 // Before resetAndEstablish, there shouldn't be any stored account state
64 XCTAssertThrowsError(try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName), "Before doing anything, loading a non-existent account state should fail")
65
66 let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs")
67 let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification,
68 object: nil,
69 handler: nil)
70 self.manager.resetAndEstablish(containerName,
71 context: contextName,
72 altDSID: "new altDSID",
73 resetReason: .testGenerated) { resetError in
74 XCTAssertNil(resetError, "Should be no error calling resetAndEstablish")
75 resetAndEstablishExpectation.fulfill()
76 }
77
78 self.wait(for: [resetAndEstablishExpectation], timeout: 10)
79 self.wait(for: [escrowRequestNotification], timeout: 5)
80 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
81
82 let selfPeerID = try self.cuttlefishContext.accountMetadataStore.loadOrCreateAccountMetadata().peerID
83
84 // After resetAndEstablish, you should be able to see the persisted account state
85 do {
86 let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName)
87 XCTAssertEqual(selfPeerID, accountState.peerID, "Saved account state should have the same peer ID that prepare returned")
88 } catch {
89 XCTFail("error loading account state: \(error)")
90 }
91 }
92
93 func testResetAndEstablishStopsCKKS() throws {
94 let contextName = OTDefaultContext
95 let containerName = OTCKContainerName
96
97 self.startCKAccountStatusMock()
98
99 self.cuttlefishContext.startOctagonStateMachine()
100 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
101 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
102
103 do {
104 let clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
105 XCTAssertNotNil(clique, "Clique should not be nil")
106 } catch {
107 XCTFail("Shouldn't have errored making new friends: \(error)")
108 }
109
110 // Now, we should be in 'ready'
111 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
112 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
113 self.assertConsidersSelfTrustedCachedAccountStatus(context: self.cuttlefishContext)
114
115 // and all subCKKSes should enter ready...
116 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
117 self.verifyDatabaseMocks()
118
119 // CKKS should pass through "waitfortrust" during a reset
120 let waitfortrusts = self.ckksViews.compactMap { view in
121 (view as! CKKSKeychainView).keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTrust] as? CKKSCondition
122 }
123 XCTAssert(!waitfortrusts.isEmpty, "Should have at least one waitfortrust condition")
124
125 let resetAndEstablishExpectation = self.expectation(description: "resetAndEstablish callback occurs")
126 let escrowRequestNotification = expectation(forNotification: OTMockEscrowRequestNotification,
127 object: nil,
128 handler: nil)
129 self.manager.resetAndEstablish(containerName,
130 context: contextName,
131 altDSID: "new altDSID",
132 resetReason: .testGenerated) { resetError in
133 XCTAssertNil(resetError, "Should be no error calling resetAndEstablish")
134 resetAndEstablishExpectation.fulfill()
135 }
136
137 self.wait(for: [resetAndEstablishExpectation], timeout: 10)
138 self.wait(for: [escrowRequestNotification], timeout: 5)
139 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
140
141 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
142
143 // CKKS should have all gone into waitfortrust during that time
144 for condition in waitfortrusts {
145 XCTAssertEqual(0, condition.wait(10 * NSEC_PER_MSEC), "CKKS should have entered waitfortrust")
146 }
147 }
148
149 func testOctagonResetAlsoResetsCKKSViewsMissingTLKs() {
150 self.putFakeKeyHierarchiesInCloudKit()
151
152 let zoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
153 XCTAssertNotNil(zoneKeys, "Should have some zone keys")
154 XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set")
155
156 self.startCKAccountStatusMock()
157 self.cuttlefishContext.startOctagonStateMachine()
158 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
159 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
160 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTrust, within: 10 * NSEC_PER_SEC)
161
162 self.silentZoneDeletesAllowed = true
163
164 do {
165 _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
166 } catch {
167 XCTFail("failed to make new friends: \(error)")
168 }
169
170 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
171
172 let laterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
173 XCTAssertNotNil(laterZoneKeys, "Should have some zone keys")
174 XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset")
175 XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys")
176 }
177
178 func testOctagonResetIgnoresOldRemoteDevicesWithKeysAndResetsCKKS() {
179 // CKKS has no keys, and there's another device claiming to have them already, but it's old
180 self.putFakeKeyHierarchiesInCloudKit()
181 self.putFakeDeviceStatusesInCloudKit()
182
183 #if !os(tvOS)
184 (self.zones![self.manateeZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in
185 let r = record as! CKRecord
186 if r.recordType == SecCKRecordDeviceStateType {
187 r.creationDate = NSDate.distantPast
188 r.modificationDate = NSDate.distantPast
189 }
190 }
191 #endif
192 (self.zones![self.limitedPeersAllowedZoneID!]! as! FakeCKZone).currentDatabase.allValues.forEach { record in
193 let r = record as! CKRecord
194 if r.recordType == SecCKRecordDeviceStateType {
195 r.creationDate = NSDate.distantPast
196 r.modificationDate = NSDate.distantPast
197 }
198 }
199
200 #if !os(tvOS)
201 let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
202 XCTAssertNotNil(zoneKeys, "Should have some zone keys for Manatee")
203 XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set for Manatee")
204 #endif
205
206 let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
207 XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers")
208 XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers")
209
210 self.startCKAccountStatusMock()
211 self.cuttlefishContext.startOctagonStateMachine()
212 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
213 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
214
215 self.silentZoneDeletesAllowed = true
216
217 do {
218 _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
219 } catch {
220 XCTFail("failed to make new friends: \(error)")
221 }
222
223 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
224
225 #if !os(tvOS)
226 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
227 XCTAssertNotNil(laterZoneKeys, "Should have some zone keys for Manatee")
228 XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for Manatee")
229 XCTAssertNotEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for Manatee")
230 #else
231 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
232 XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV")
233 #endif
234
235 let laterLpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
236 XCTAssertNotNil(laterLpZoneKeys, "Should have some zone keys for LimitedPeers")
237 XCTAssertNotNil(laterLpZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeers")
238 XCTAssertNotEqual(lpZoneKeys?.tlk?.uuid, laterLpZoneKeys?.tlk?.uuid, "CKKS zone should now have different keys for LimitedPeers")
239 }
240
241 func testOctagonResetWithRemoteDevicesWithKeysDoesNotResetCKKS() {
242 // CKKS has no keys, and there's another device claiming to have them already, so CKKS won't immediately reset it
243 self.putFakeKeyHierarchiesInCloudKit()
244 self.putFakeDeviceStatusesInCloudKit()
245
246 #if !os(tvOS)
247 let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
248 XCTAssertNotNil(zoneKeys, "Should have some zone keys")
249 XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set")
250 #endif
251
252 let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
253 XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers")
254 XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers")
255
256 self.startCKAccountStatusMock()
257 self.cuttlefishContext.startOctagonStateMachine()
258 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
259 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
260
261 self.silentZoneDeletesAllowed = true
262
263 do {
264 _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
265 } catch {
266 XCTFail("failed to make new friends: \(error)")
267 }
268
269 assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
270
271 #if !os(tvOS)
272 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
273 XCTAssertNotNil(laterZoneKeys, "Should have some zone keys")
274 XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset")
275 XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys")
276 #else
277 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
278 XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV")
279 #endif
280
281 let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
282 XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed")
283 XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed")
284 XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed")
285 }
286
287 func testOctagonResetWithTLKsDoesNotResetCKKS() {
288 // CKKS has the keys keys
289 self.putFakeKeyHierarchiesInCloudKit()
290 self.saveTLKMaterialToKeychain()
291
292 #if !os(tvOS)
293 let zoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
294 XCTAssertNotNil(zoneKeys, "Should have some zone keys")
295 XCTAssertNotNil(zoneKeys?.tlk, "Should have a tlk in the original key set")
296 #endif
297
298 let lpZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
299 XCTAssertNotNil(lpZoneKeys, "Should have some zone keys for LimitedPeers")
300 XCTAssertNotNil(lpZoneKeys?.tlk, "Should have a tlk in the original key set for LimitedPeers")
301
302 self.startCKAccountStatusMock()
303 self.cuttlefishContext.startOctagonStateMachine()
304 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
305 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
306
307 do {
308 _ = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
309 } catch {
310 XCTFail("failed to make new friends: \(error)")
311 }
312 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
313
314 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
315
316 #if !os(tvOS)
317 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
318 XCTAssertNotNil(laterZoneKeys, "Should have some zone keys")
319 XCTAssertNotNil(laterZoneKeys?.tlk, "Should have a tlk in the newly created keyset")
320 XCTAssertEqual(zoneKeys?.tlk?.uuid, laterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys")
321 #else
322 let laterZoneKeys = self.keys![self.manateeZoneID!] as? ZoneKeys
323 XCTAssertNil(laterZoneKeys, "Should have no Manatee zone keys for aTV")
324 #endif
325
326 let lpLaterZoneKeys = self.keys![self.limitedPeersAllowedZoneID!] as? ZoneKeys
327 XCTAssertNotNil(lpLaterZoneKeys, "Should have some zone keys for LimitedPeersAllowed")
328 XCTAssertNotNil(lpLaterZoneKeys?.tlk, "Should have a tlk in the newly created keyset for LimitedPeersAllowed")
329 XCTAssertEqual(lpZoneKeys?.tlk?.uuid, lpLaterZoneKeys?.tlk?.uuid, "CKKS zone should now have the same keys for LimitedPeersAllowed")
330 }
331
332 func testOctagonResetAndEstablishFail() throws {
333 // Make sure if establish fail we end up in untrusted instead of error
334 self.startCKAccountStatusMock()
335
336 self.cuttlefishContext.startOctagonStateMachine()
337 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
338 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
339
340 _ = try self.cuttlefishContext.accountAvailable("13453464")
341
342 let establishExpectation = self.expectation(description: "establishExpectation")
343 let resetExpectation = self.expectation(description: "resetExpectation")
344
345 self.fakeCuttlefishServer.establishListener = { [unowned self] request in
346 self.fakeCuttlefishServer.establishListener = nil
347 establishExpectation.fulfill()
348
349 return FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .establishFailed)
350 }
351
352 self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in
353 resetExpectation.fulfill()
354 XCTAssertNotNil(resetError, "should error resetting and establishing")
355 }
356 self.wait(for: [establishExpectation, resetExpectation], timeout: 10)
357
358 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
359 self.verifyDatabaseMocks()
360 }
361
362 func testResetReasonUnknown() throws {
363 self.startCKAccountStatusMock()
364
365 self.cuttlefishContext.startOctagonStateMachine()
366 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
367 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
368
369 _ = try self.cuttlefishContext.accountAvailable("13453464")
370
371 let resetExpectation = self.expectation(description: "resetExpectation")
372
373 self.fakeCuttlefishServer.resetListener = { request in
374 self.fakeCuttlefishServer.resetListener = nil
375 resetExpectation.fulfill()
376 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.unknown.rawValue, "reset reason should be unknown")
377 return nil
378 }
379
380 let establishAndResetExpectation = self.expectation(description: "resetExpectation")
381 self.cuttlefishContext.rpcResetAndEstablish(.unknown) { resetError in
382 establishAndResetExpectation.fulfill()
383 XCTAssertNil(resetError, "should not error resetting and establishing")
384 }
385 self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10)
386
387 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
388 self.verifyDatabaseMocks()
389 }
390
391 func testResetReasonUserInitiatedReset() throws {
392 // Make sure if establish fail we end up in untrusted instead of error
393 self.startCKAccountStatusMock()
394
395 self.cuttlefishContext.startOctagonStateMachine()
396 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
397 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
398
399 _ = try self.cuttlefishContext.accountAvailable("13453464")
400
401 let resetExpectation = self.expectation(description: "resetExpectation")
402
403 self.fakeCuttlefishServer.resetListener = { request in
404 self.fakeCuttlefishServer.resetListener = nil
405 resetExpectation.fulfill()
406 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.userInitiatedReset.rawValue, "reset reason should be user initiated reset")
407 return nil
408 }
409
410 let establishAndResetExpectation = self.expectation(description: "resetExpectation")
411 let clique: OTClique
412 let recoverykeyotcliqueContext = OTConfigurationContext()
413 recoverykeyotcliqueContext.context = OTDefaultContext
414 recoverykeyotcliqueContext.dsid = "13453464"
415 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
416 recoverykeyotcliqueContext.otControl = self.otControl
417 do {
418 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .userInitiatedReset)
419 XCTAssertNotNil(clique, "Clique should not be nil")
420 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
421 establishAndResetExpectation.fulfill()
422 } catch {
423 XCTFail("Shouldn't have errored making new friends: \(error)")
424 throw error
425 }
426 self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10)
427
428 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
429 self.verifyDatabaseMocks()
430 }
431
432 func testResetReasonRecoveryKey() throws {
433 // Make sure if establish fail we end up in untrusted instead of error
434 self.startCKAccountStatusMock()
435
436 self.cuttlefishContext.startOctagonStateMachine()
437 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
438 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
439
440 _ = try self.cuttlefishContext.accountAvailable("13453464")
441
442 let resetExpectation = self.expectation(description: "resetExpectation")
443
444 self.fakeCuttlefishServer.resetListener = { request in
445 self.fakeCuttlefishServer.resetListener = nil
446 resetExpectation.fulfill()
447 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.recoveryKey.rawValue, "reset reason should be recovery key")
448 return nil
449 }
450
451 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
452 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
453
454 let newCliqueContext = OTConfigurationContext()
455 newCliqueContext.context = OTDefaultContext
456 newCliqueContext.dsid = self.otcliqueContext.dsid
457 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
458 newCliqueContext.otControl = self.otControl
459
460 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
461 TestsObjectiveC.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey) { error in
462 XCTAssertNil(error, "error should be nil")
463 joinWithRecoveryKeyExpectation.fulfill()
464 }
465 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
466 self.wait(for: [resetExpectation], timeout: 10)
467
468 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
469 self.verifyDatabaseMocks()
470 }
471
472 func testResetReasonNoValidBottle() throws {
473 self.startCKAccountStatusMock()
474
475 self.cuttlefishContext.startOctagonStateMachine()
476 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
477 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
478
479 let initiatorContext = self.manager.context(forContainerName: OTCKContainerName,
480 contextID: "restoreContext",
481 sosAdapter: OTSOSMissingAdapter(),
482 authKitAdapter: self.mockAuthKit2,
483 lockStateTracker: self.lockStateTracker,
484 accountStateTracker: self.accountStateTracker,
485 deviceInformationAdapter: self.makeInitiatorDeviceInfoAdapter())
486
487 initiatorContext.startOctagonStateMachine()
488 let newOTCliqueContext = OTConfigurationContext()
489 newOTCliqueContext.context = OTDefaultContext
490 newOTCliqueContext.dsid = self.otcliqueContext.dsid
491 newOTCliqueContext.altDSID = self.otcliqueContext.altDSID
492 newOTCliqueContext.otControl = self.otcliqueContext.otControl
493 newOTCliqueContext.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil)
494
495 let resetExpectation = self.expectation(description: "resetExpectation callback occurs")
496 self.fakeCuttlefishServer.resetListener = { request in
497 self.fakeCuttlefishServer.resetListener = nil
498 resetExpectation.fulfill()
499 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.noBottleDuringEscrowRecovery.rawValue, "reset reason should be no bottle during escrow recovery")
500 return nil
501 }
502
503 let newClique: OTClique
504 do {
505 newClique = try OTClique.performEscrowRecovery(withContextData: newOTCliqueContext, escrowArguments: [:])
506 XCTAssertNotNil(newClique, "newClique should not be nil")
507 } catch {
508 XCTFail("Shouldn't have errored recovering: \(error)")
509 throw error
510 }
511 self.wait(for: [resetExpectation], timeout: 10)
512
513 }
514
515 func testResetReasonHealthCheck() throws {
516
517 let containerName = OTCKContainerName
518 let contextName = OTDefaultContext
519
520 self.cuttlefishContext.startOctagonStateMachine()
521 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
522 self.startCKAccountStatusMock()
523
524 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
525
526 let clique: OTClique
527 do {
528 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
529 XCTAssertNotNil(clique, "Clique should not be nil")
530 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
531 } catch {
532 XCTFail("Shouldn't have errored making new friends: \(error)")
533 throw error
534 }
535
536 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
537 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
538
539 do {
540 let accountState = try OTAccountMetadataClassC.loadFromKeychain(forContainer: containerName, contextID: contextName)
541 XCTAssertEqual(2, accountState.trustState.rawValue, "saved account should be trusted")
542 } catch {
543 XCTFail("error loading account state: \(error)")
544 }
545
546 // Reset any CFUs we've done so far
547 self.otFollowUpController.postedFollowUp = false
548
549 let resetExpectation = self.expectation(description: "resetExpectation callback occurs")
550 self.fakeCuttlefishServer.resetListener = { request in
551 self.fakeCuttlefishServer.resetListener = nil
552 resetExpectation.fulfill()
553 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.healthCheck.rawValue, "reset reason should be health check")
554 return nil
555 }
556 self.fakeCuttlefishServer.returnResetOctagonResponse = true
557 self.aksLockState = false
558 self.lockStateTracker.recheck()
559
560 let healthCheckCallback = self.expectation(description: "healthCheckCallback callback occurs")
561 self.manager.healthCheck(containerName, context: contextName, skipRateLimitingCheck: false) { error in
562 XCTAssertNil(error, "error should be nil")
563 healthCheckCallback.fulfill()
564 }
565 self.wait(for: [healthCheckCallback, resetExpectation], timeout: 10)
566
567 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
568 self.verifyDatabaseMocks()
569
570 let dumpCallback = self.expectation(description: "dumpCallback callback occurs")
571 self.tphClient.dump(withContainer: containerName, context: contextName) { dump, _ in
572 XCTAssertNotNil(dump, "dump should not be nil")
573 let egoSelf = dump!["self"] as? [String: AnyObject]
574 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
575 dumpCallback.fulfill()
576 }
577 self.wait(for: [dumpCallback], timeout: 10)
578
579 self.verifyDatabaseMocks()
580 self.assertEnters(context: cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
581 }
582
583 func testLegacyJoinCircleDoesNotReset() throws {
584 self.cuttlefishContext.startOctagonStateMachine()
585 self.startCKAccountStatusMock()
586 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
587
588 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
589
590 let establishAndResetExpectation = self.expectation(description: "resetExpectation")
591 let clique: OTClique
592 let recoverykeyotcliqueContext = OTConfigurationContext()
593 recoverykeyotcliqueContext.context = OTDefaultContext
594 recoverykeyotcliqueContext.dsid = "13453464"
595 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
596 recoverykeyotcliqueContext.otControl = self.otControl
597 do {
598 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
599 XCTAssertNotNil(clique, "Clique should not be nil")
600 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
601 establishAndResetExpectation.fulfill()
602 } catch {
603 XCTFail("Shouldn't have errored making new friends: \(error)")
604 throw error
605 }
606 self.wait(for: [establishAndResetExpectation], timeout: 10)
607
608 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
609 self.verifyDatabaseMocks()
610
611 self.fakeCuttlefishServer.resetListener = { request in
612 XCTFail("requestToJoinCircle should not reset Octagon")
613 return nil
614 }
615
616 do {
617 _ = try clique.requestToJoinCircle()
618 } catch {
619 XCTFail("Shouldn't have errored requesting to join circle: \(error)")
620 throw error
621 }
622
623 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
624 }
625
626 func testResetReasonTestGenerated() throws {
627 self.startCKAccountStatusMock()
628
629 self.cuttlefishContext.startOctagonStateMachine()
630 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
631 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
632
633 _ = try self.cuttlefishContext.accountAvailable("13453464")
634
635 let resetExpectation = self.expectation(description: "resetExpectation")
636
637 self.fakeCuttlefishServer.resetListener = { request in
638 self.fakeCuttlefishServer.resetListener = nil
639 resetExpectation.fulfill()
640 XCTAssertTrue(request.resetReason.rawValue == CuttlefishResetReason.testGenerated.rawValue, "reset reason should be test generated")
641 return nil
642 }
643
644 let establishAndResetExpectation = self.expectation(description: "resetExpectation")
645 self.cuttlefishContext.rpcResetAndEstablish(.testGenerated) { resetError in
646 establishAndResetExpectation.fulfill()
647 XCTAssertNil(resetError, "should not error resetting and establishing")
648 }
649 self.wait(for: [establishAndResetExpectation, resetExpectation], timeout: 10)
650
651 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
652 self.verifyDatabaseMocks()
653 }
654
655 func testCliqueResetAllSPI() throws {
656 self.cuttlefishContext.startOctagonStateMachine()
657 self.startCKAccountStatusMock()
658 OctagonSetSOSFeatureEnabled(false)
659
660 XCTAssertNoThrow(try self.cuttlefishContext.setCDPEnabled())
661
662 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
663
664 let establishAndResetExpectation = self.expectation(description: "resetExpectation")
665 let clique: OTClique
666 let otcliqueContext = OTConfigurationContext()
667 var firstCliqueIdentifier: String?
668
669 otcliqueContext.context = OTDefaultContext
670 otcliqueContext.dsid = "13453464"
671 otcliqueContext.altDSID = self.mockAuthKit.altDSID!
672 otcliqueContext.authenticationAppleID = "appleID"
673 otcliqueContext.passwordEquivalentToken = "petpetpetpetpet"
674 otcliqueContext.otControl = self.otControl
675 otcliqueContext.ckksControl = self.ckksControl
676 otcliqueContext.sbd = OTMockSecureBackup(bottleID: nil, entropy: nil)
677
678 do {
679 clique = try OTClique.newFriends(withContextData: otcliqueContext, resetReason: .testGenerated)
680 XCTAssertNotNil(clique, "Clique should not be nil")
681 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
682 firstCliqueIdentifier = clique.cliqueMemberIdentifier
683 establishAndResetExpectation.fulfill()
684 } catch {
685 XCTFail("Shouldn't have errored making new friends everything: \(error)")
686 throw error
687 }
688 self.wait(for: [establishAndResetExpectation], timeout: 10)
689
690 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
691 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
692 self.verifyDatabaseMocks()
693
694 self.silentZoneDeletesAllowed = true
695
696 let newClique: OTClique
697 do {
698 newClique = try OTClique.resetProtectedData(otcliqueContext)
699 XCTAssertNotEqual(newClique.cliqueMemberIdentifier, firstCliqueIdentifier, "clique identifiers should be different")
700 } catch {
701 XCTFail("Shouldn't have errored resetting everything: \(error)")
702 throw error
703 }
704 XCTAssertNotNil(newClique, "newClique should not be nil")
705
706 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
707 assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
708 self.verifyDatabaseMocks()
709 }
710 }
711
712 #endif // OCTAGON