]> git.saurik.com Git - apple/security.git/blob - keychain/ot/tests/octagon/OctagonTests+RecoveryKey.swift
ad69de5b3b09a010995c1e411e6a3d5fd65c1cee
[apple/security.git] / keychain / ot / tests / octagon / OctagonTests+RecoveryKey.swift
1 #if OCTAGON
2
3 @objcMembers class OctagonRecoveryKeyTests: OctagonTestsBase {
4 override func setUp() {
5 super.setUp()
6 }
7
8 func testSetRecoveryKey() throws {
9 self.startCKAccountStatusMock()
10 self.manager.setSOSEnabledForPlatformFlag(false)
11
12 self.cuttlefishContext.startOctagonStateMachine()
13 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
14
15 XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices")
16
17 let clique: OTClique
18 do {
19 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
20 XCTAssertNotNil(clique, "Clique should not be nil")
21 } catch {
22 XCTFail("Shouldn't have errored making new friends: \(error)")
23 throw error
24 }
25
26 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
27 XCTAssertNotNil(entropy, "entropy should not be nil")
28
29 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
30 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
31
32 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
33 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
34 self.manager.setSOSEnabledForPlatformFlag(true)
35
36 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
37 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
38 XCTAssertNil(error, "error should be nil")
39 createKeyExpectation.fulfill()
40 }
41 self.wait(for: [createKeyExpectation], timeout: 10)
42 }
43
44 func testSetRecoveryKeyPeerReaction() throws {
45 self.startCKAccountStatusMock()
46 self.manager.setSOSEnabledForPlatformFlag(false)
47
48 self.cuttlefishContext.startOctagonStateMachine()
49 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
50
51 let clique: OTClique
52 do {
53 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
54 XCTAssertNotNil(clique, "Clique should not be nil")
55 } catch {
56 XCTFail("Shouldn't have errored making new friends: \(error)")
57 throw error
58 }
59
60 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
61 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
62 self.verifyDatabaseMocks()
63
64 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
65 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
66 self.manager.setSOSEnabledForPlatformFlag(true)
67
68 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
69 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
70 XCTAssertNil(error, "error should be nil")
71 createKeyExpectation.fulfill()
72 }
73 self.wait(for: [createKeyExpectation], timeout: 10)
74
75 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
76 XCTAssertNotNil(entropy, "entropy should not be nil")
77
78 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
79 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
80 self.verifyDatabaseMocks()
81
82 let bottle = self.fakeCuttlefishServer.state.bottles[0]
83
84 let initiatorContextID = "new guy"
85
86 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID, authKitAdapter: self.mockAuthKit2)
87
88 initiatorContext.startOctagonStateMachine()
89 self.sendContainerChange(context: initiatorContext)
90
91 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
92 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
93 XCTAssertNil(error, "error should be nil")
94 joinWithBottleExpectation.fulfill()
95 }
96 self.wait(for: [joinWithBottleExpectation], timeout: 10)
97 self.verifyDatabaseMocks()
98
99 self.sendContainerChangeWaitForFetch(context: initiatorContext)
100 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
101
102 self.verifyDatabaseMocks()
103
104 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
105 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
106 dump, _ in
107 XCTAssertNotNil(dump, "dump should not be nil")
108 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
109 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
110 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
111 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
112
113 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
114 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
115 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
116 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
117
118 let included = dynamicInfo!["included"] as? Array<String>
119 XCTAssertNotNil(included, "included should not be nil")
120 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
121
122 stableInfoCheckDumpCallback.fulfill()
123 }
124 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
125
126 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
127 self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") {
128 dump, _ in
129 XCTAssertNotNil(dump, "dump should not be nil")
130 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
131 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
132 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
133 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
134
135 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
136 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
137 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
138 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
139
140 let included = dynamicInfo!["included"] as? Array<String>
141 XCTAssertNotNil(included, "included should not be nil")
142 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
143
144 stableInfoAcceptorCheckDumpCallback.fulfill()
145 }
146 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
147 self.verifyDatabaseMocks()
148 }
149
150 func testSetRecoveryKey3PeerReaction() throws {
151 self.startCKAccountStatusMock()
152 self.manager.setSOSEnabledForPlatformFlag(false)
153
154 self.cuttlefishContext.startOctagonStateMachine()
155 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
156
157 let clique: OTClique
158 do {
159 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
160 XCTAssertNotNil(clique, "Clique should not be nil")
161 } catch {
162 XCTFail("Shouldn't have errored making new friends: \(error)")
163 throw error
164 }
165
166 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
167 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
168 self.verifyDatabaseMocks()
169
170 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
171 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
172 self.manager.setSOSEnabledForPlatformFlag(true)
173
174 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
175 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
176 XCTAssertNil(error, "error should be nil")
177 createKeyExpectation.fulfill()
178 }
179 self.wait(for: [createKeyExpectation], timeout: 10)
180
181 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
182 XCTAssertNotNil(entropy, "entropy should not be nil")
183
184 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
185 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
186
187 let bottle = self.fakeCuttlefishServer.state.bottles[0]
188
189 let initiatorContextID = "new guy"
190 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
191
192 initiatorContext.startOctagonStateMachine()
193
194 self.sendContainerChange(context: initiatorContext)
195
196 self.assertEnters(context: initiatorContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
197
198 let joinWithBottleExpectation = self.expectation(description: "joinWithBottle callback occurs")
199 initiatorContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
200 XCTAssertNil(error, "error should be nil")
201 joinWithBottleExpectation.fulfill()
202 }
203 self.wait(for: [joinWithBottleExpectation], timeout: 10)
204
205 self.verifyDatabaseMocks()
206
207 self.sendContainerChangeWaitForFetch(context: initiatorContext)
208
209 // The first peer will upload TLKs for the new peer
210 self.assertAllCKKSViewsUpload(tlkShares: 1)
211 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
212 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
213 self.verifyDatabaseMocks()
214
215 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
216 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
217 dump, _ in
218 XCTAssertNotNil(dump, "dump should not be nil")
219 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
220 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
221 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
222 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
223
224 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
225 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
226 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
227 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
228
229 let included = dynamicInfo!["included"] as? Array<String>
230 XCTAssertNotNil(included, "included should not be nil")
231 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
232
233 stableInfoCheckDumpCallback.fulfill()
234 }
235 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
236
237 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
238 self.tphClient.dump(withContainer: OTCKContainerName, context: self.otcliqueContext.context ?? "defaultContext") {
239 dump, _ in
240 XCTAssertNotNil(dump, "dump should not be nil")
241 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
242 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
243 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
244 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
245
246 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
247 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
248 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
249 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
250
251 let included = dynamicInfo!["included"] as? Array<String>
252 XCTAssertNotNil(included, "included should not be nil")
253 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
254
255 stableInfoAcceptorCheckDumpCallback.fulfill()
256 }
257 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
258
259 let thirdPeerContextID = "3rd guy"
260 let thirdPeerContext = self.makeInitiatorContext(contextID: thirdPeerContextID, authKitAdapter: self.mockAuthKit3)
261
262 thirdPeerContext.startOctagonStateMachine()
263
264 self.sendContainerChange(context: thirdPeerContext)
265 let thirdPeerJoinWithBottleExpectation = self.expectation(description: "thirdPeerJoinWithBottleExpectation callback occurs")
266 thirdPeerContext.join(withBottle: bottle.bottleID, entropy: entropy!, bottleSalt: self.otcliqueContext.altDSID) { error in
267 XCTAssertNil(error, "error should be nil")
268 thirdPeerJoinWithBottleExpectation.fulfill()
269 }
270 self.wait(for: [thirdPeerJoinWithBottleExpectation], timeout: 10)
271
272 self.verifyDatabaseMocks()
273
274 self.sendContainerChangeWaitForFetch(context: thirdPeerContext)
275 let thirdPeerStableInfoCheckDumpCallback = self.expectation(description: "thirdPeerStableInfoCheckDumpCallback callback occurs")
276 self.tphClient.dump(withContainer: OTCKContainerName, context: thirdPeerContextID) {
277 dump, _ in
278 XCTAssertNotNil(dump, "dump should not be nil")
279 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
280 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
281 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
282 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
283
284 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
285 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
286 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
287 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
288
289 let included = dynamicInfo!["included"] as? Array<String>
290 XCTAssertNotNil(included, "included should not be nil")
291 XCTAssertEqual(included!.count, 3, "should be 3df peer ids")
292
293 thirdPeerStableInfoCheckDumpCallback.fulfill()
294 }
295 self.wait(for: [thirdPeerStableInfoCheckDumpCallback], timeout: 10)
296 }
297
298 func createEstablishContext(contextID: String) -> OTCuttlefishContext {
299
300 return self.manager.context(forContainerName: OTCKContainerName,
301 contextID: contextID,
302 sosAdapter: self.mockSOSAdapter,
303 authKitAdapter: self.mockAuthKit2,
304 lockStateTracker: self.lockStateTracker,
305 accountStateTracker: self.accountStateTracker,
306 deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-RK-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
307 }
308
309 func testJoinWithRecoveryKey() throws {
310 OctagonRecoveryKeySetIsEnabled(true)
311 self.manager.setSOSEnabledForPlatformFlag(false)
312 self.startCKAccountStatusMock()
313
314 let establishContextID = "establish-context-id"
315 let establishContext = self.createEstablishContext(contextID: establishContextID)
316
317 establishContext.startOctagonStateMachine()
318 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
319
320 let clique: OTClique
321 let bottlerotcliqueContext = OTConfigurationContext()
322 bottlerotcliqueContext.context = establishContextID
323 bottlerotcliqueContext.dsid = "1234"
324 bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID!
325 bottlerotcliqueContext.otControl = self.otControl
326 do {
327 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
328 XCTAssertNotNil(clique, "Clique should not be nil")
329 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
330 } catch {
331 XCTFail("Shouldn't have errored making new friends: \(error)")
332 throw error
333 }
334
335 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
336 self.assertConsidersSelfTrusted(context: establishContext)
337
338 // Fake that this peer also created some TLKShares for itself
339 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
340 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
341
342 self.assertSelfTLKSharesInCloudKit(context: establishContext)
343
344 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
345 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
346
347 self.manager.setSOSEnabledForPlatformFlag(true)
348
349 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
350 self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in
351 XCTAssertNil(error, "error should be nil")
352 createRecoveryExpectation.fulfill()
353 }
354 self.wait(for: [createRecoveryExpectation], timeout: 10)
355
356 self.sendContainerChangeWaitForFetch(context: establishContext)
357
358 let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
359
360 recoveryContext.startOctagonStateMachine()
361 self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
362
363 self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext)
364
365 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
366 recoveryContext.join(withRecoveryKey: recoveryKey) { error in
367 XCTAssertNil(error, "error should be nil")
368 joinWithRecoveryKeyExpectation.fulfill()
369 }
370 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
371
372 self.sendContainerChangeWaitForFetch(context: recoveryContext)
373
374 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
375 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
376 dump, _ in
377 XCTAssertNotNil(dump, "dump should not be nil")
378 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
379 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
380 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
381 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
382
383 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
384 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
385 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
386 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
387
388 let included = dynamicInfo!["included"] as? Array<String>
389 XCTAssertNotNil(included, "included should not be nil")
390 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
391 let vouchers = dump!["vouchers"]
392 XCTAssertNotNil(vouchers, "vouchers should not be nil")
393 stableInfoCheckDumpCallback.fulfill()
394 }
395 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
396
397 self.sendContainerChangeWaitForFetch(context: establishContext)
398
399 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
400 self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) {
401 dump, _ in
402 XCTAssertNotNil(dump, "dump should not be nil")
403 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
404 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
405 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
406 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
407
408 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
409 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
410 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
411 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
412
413 let included = dynamicInfo!["included"] as? Array<String>
414 XCTAssertNotNil(included, "included should not be nil")
415 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
416 let vouchers = dump!["vouchers"]
417 XCTAssertNotNil(vouchers, "vouchers should not be nil")
418 stableInfoAcceptorCheckDumpCallback.fulfill()
419 }
420 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
421 try self.putSelfTLKShareInCloudKit(context: recoveryContext, zoneID: self.manateeZoneID)
422 self.assertSelfTLKSharesInCloudKit(context: recoveryContext)
423 }
424
425 func testJoinWithRecoveryKeyWithCKKSConflict() throws {
426 OctagonRecoveryKeySetIsEnabled(true)
427 self.manager.setSOSEnabledForPlatformFlag(false)
428 self.startCKAccountStatusMock()
429
430 let establishContextID = "establish-context-id"
431 let establishContext = self.createEstablishContext(contextID: establishContextID)
432
433 establishContext.startOctagonStateMachine()
434 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
435
436 let clique: OTClique
437 let bottlerotcliqueContext = OTConfigurationContext()
438 bottlerotcliqueContext.context = establishContextID
439 bottlerotcliqueContext.dsid = "1234"
440 bottlerotcliqueContext.altDSID = self.mockAuthKit2.altDSID!
441 bottlerotcliqueContext.otControl = self.otControl
442 do {
443 clique = try OTClique.newFriends(withContextData: bottlerotcliqueContext, resetReason: .testGenerated)
444 XCTAssertNotNil(clique, "Clique should not be nil")
445 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
446 } catch {
447 XCTFail("Shouldn't have errored making new friends: \(error)")
448 throw error
449 }
450
451 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
452 self.assertConsidersSelfTrusted(context: establishContext)
453
454 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
455 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
456
457 self.manager.setSOSEnabledForPlatformFlag(true)
458
459 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
460 self.manager.createRecoveryKey(OTCKContainerName, contextID: establishContextID, recoveryKey: recoveryKey) { error in
461 XCTAssertNil(error, "error should be nil")
462 createRecoveryExpectation.fulfill()
463 }
464 self.wait(for: [createRecoveryExpectation], timeout: 10)
465
466 self.sendContainerChangeWaitForFetch(context: establishContext)
467
468 self.silentFetchesAllowed = false
469 self.expectCKFetchAndRun(beforeFinished: {
470 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
471 self.putFakeDeviceStatus(inCloudKit: self.manateeZoneID)
472 self.silentFetchesAllowed = true
473 })
474 let recoveryContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
475
476 recoveryContext.startOctagonStateMachine()
477 self.assertEnters(context: recoveryContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
478
479 self.sendContainerChangeWaitForUntrustedFetch(context: recoveryContext)
480
481 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
482 recoveryContext.join(withRecoveryKey: recoveryKey) { error in
483 XCTAssertNil(error, "error should be nil")
484 joinWithRecoveryKeyExpectation.fulfill()
485 }
486 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
487
488 self.assertConsidersSelfTrusted(context: recoveryContext)
489 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
490 }
491
492 func testOTCliqueSettingRecoveryKey() throws {
493 OctagonRecoveryKeySetIsEnabled(true)
494 self.manager.setSOSEnabledForPlatformFlag(false)
495 self.startCKAccountStatusMock()
496 let establishContextID = "establish-context-id"
497 let establishContext = self.createEstablishContext(contextID: establishContextID)
498
499 establishContext.startOctagonStateMachine()
500 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
501
502 let clique: OTClique
503 do {
504 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
505 XCTAssertNotNil(clique, "Clique should not be nil")
506 } catch {
507 XCTFail("Shouldn't have errored making new friends: \(error)")
508 throw error
509 }
510
511 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
512 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
513
514 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
515 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
516 self.manager.setSOSEnabledForPlatformFlag(true)
517
518 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
519 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
520 XCTAssertNil(error, "error should be nil")
521 XCTAssertNotNil(rk, "rk should not be nil")
522 setRecoveryKeyExpectation.fulfill()
523 }
524 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
525 }
526
527 func testOTCliqueSet2ndRecoveryKey() throws {
528 OctagonRecoveryKeySetIsEnabled(true)
529 self.manager.setSOSEnabledForPlatformFlag(false)
530 self.startCKAccountStatusMock()
531 let establishContextID = "establish-context-id"
532 let establishContext = self.createEstablishContext(contextID: establishContextID)
533
534 establishContext.startOctagonStateMachine()
535 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
536
537 let clique: OTClique
538 do {
539 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
540 XCTAssertNotNil(clique, "Clique should not be nil")
541 } catch {
542 XCTFail("Shouldn't have errored making new friends: \(error)")
543 throw error
544 }
545
546 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
547 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
548
549 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
550 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
551 self.manager.setSOSEnabledForPlatformFlag(true)
552
553 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
554 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
555 XCTAssertNil(error, "error should be nil")
556 XCTAssertNotNil(rk, "rk should not be nil")
557 setRecoveryKeyExpectation.fulfill()
558 }
559 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
560
561 let recoveryKey2 = SecRKCreateRecoveryKeyString(nil)
562
563 let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs")
564 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey2!) { rk, error in
565 XCTAssertNil(error, "error should be nil")
566 XCTAssertNotNil(rk, "rk should not be nil")
567 setRecoveryKeyExpectationAgain.fulfill()
568 }
569 self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10)
570 }
571 func testRKReplacement() throws {
572 OctagonRecoveryKeySetIsEnabled(true)
573 self.manager.setSOSEnabledForPlatformFlag(false)
574 self.startCKAccountStatusMock()
575
576 let initiatorContextID = "initiator-context-id"
577 self.cuttlefishContext.startOctagonStateMachine()
578 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
579
580 let clique: OTClique
581 do {
582 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
583 XCTAssertNotNil(clique, "Clique should not be nil")
584 } catch {
585 XCTFail("Shouldn't have errored making new friends: \(error)")
586 throw error
587 }
588
589 self.verifyDatabaseMocks()
590
591 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
592 XCTAssertNotNil(entropy, "entropy should not be nil")
593
594 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
595 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
596
597 let bottle = self.fakeCuttlefishServer.state.bottles[0]
598
599 let initiatorContext = self.makeInitiatorContext(contextID: initiatorContextID)
600 let initiatorConfigurationContext = OTConfigurationContext()
601 initiatorConfigurationContext.context = initiatorContextID
602 initiatorConfigurationContext.dsid = "1234"
603 initiatorConfigurationContext.altDSID = self.mockAuthKit.altDSID!
604 initiatorConfigurationContext.otControl = self.otControl
605
606 initiatorContext.startOctagonStateMachine()
607 self.sendContainerChange(context: initiatorContext)
608 let restoreExpectation = self.expectation(description: "restore returns")
609
610 self.manager!.restore(OTCKContainerName, contextID: initiatorContextID, bottleSalt: self.otcliqueContext.altDSID, entropy: entropy!, bottleID: bottle.bottleID) { error in
611 XCTAssertNil(error, "error should be nil")
612 restoreExpectation.fulfill()
613 }
614 self.wait(for: [restoreExpectation], timeout: 10)
615
616 self.assertEnters(context: initiatorContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
617
618 var initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
619 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
620 dump, _ in
621 XCTAssertNotNil(dump, "dump should not be nil")
622 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
623 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
624 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
625 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
626 let included = dynamicInfo!["included"] as? Array<String>
627 XCTAssertNotNil(included, "included should not be nil")
628 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
629
630 initiatorDumpCallback.fulfill()
631 }
632 self.wait(for: [initiatorDumpCallback], timeout: 10)
633 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
634 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
635 self.manager.setSOSEnabledForPlatformFlag(true)
636
637 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
638 TestsObjectiveC.setNewRecoveryKeyWithData(self.otcliqueContext, recoveryKey: recoveryKey!) { rk, error in
639 XCTAssertNil(error, "error should be nil")
640 XCTAssertNotNil(rk, "rk should not be nil")
641 setRecoveryKeyExpectation.fulfill()
642 }
643 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
644
645 let recoveryKey2 = SecRKCreateRecoveryKeyString(nil)
646 let setRecoveryKeyExpectationAgain = self.expectation(description: "setRecoveryKeyExpectationAgain callback occurs")
647 TestsObjectiveC.setNewRecoveryKeyWithData(initiatorConfigurationContext, recoveryKey: recoveryKey2!) { rk, error in
648 XCTAssertNil(error, "error should be nil")
649 XCTAssertNotNil(rk, "rk should not be nil")
650 setRecoveryKeyExpectationAgain.fulfill()
651 }
652 self.wait(for: [setRecoveryKeyExpectationAgain], timeout: 10)
653
654 self.sendContainerChangeWaitForFetch(context: initiatorContext)
655 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
656
657 var initiatorRecoverySigningKey: Data?
658 var initiatorRecoveryEncryptionKey: Data?
659
660 var firstDeviceRecoverySigningKey: Data?
661 var firstDeviceRecoveryEncryptionKey: Data?
662
663 //now let's ensure recovery keys are set for both the first device and second device
664 initiatorDumpCallback = self.expectation(description: "initiatorDumpCallback callback occurs")
665 self.tphClient.dump(withContainer: OTCKContainerName, context: initiatorContextID) {
666 dump, _ in
667 XCTAssertNotNil(dump, "dump should not be nil")
668 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
669 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
670 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
671 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
672
673 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
674 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
675 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
676 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
677
678 initiatorRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
679 initiatorRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
680
681 let included = dynamicInfo!["included"] as? Array<String>
682 XCTAssertNotNil(included, "included should not be nil")
683 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
684 let vouchers = dump!["vouchers"]
685 XCTAssertNotNil(vouchers, "vouchers should not be nil")
686 initiatorDumpCallback.fulfill()
687 }
688 self.wait(for: [initiatorDumpCallback], timeout: 10)
689
690 let firstDeviceDumpCallback = self.expectation(description: "firstDeviceDumpCallback callback occurs")
691 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
692 dump, _ in
693 XCTAssertNotNil(dump, "dump should not be nil")
694 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
695 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
696 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
697 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
698
699 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
700 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
701 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
702 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
703
704 firstDeviceRecoverySigningKey = stableInfo!["recovery_signing_public_key"] as? Data
705 firstDeviceRecoveryEncryptionKey = stableInfo!["recovery_encryption_public_key"] as? Data
706
707 let included = dynamicInfo!["included"] as? Array<String>
708 XCTAssertNotNil(included, "included should not be nil")
709 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
710 let vouchers = dump!["vouchers"]
711 XCTAssertNotNil(vouchers, "vouchers should not be nil")
712 firstDeviceDumpCallback.fulfill()
713 }
714 self.wait(for: [firstDeviceDumpCallback], timeout: 10)
715
716 XCTAssertEqual(firstDeviceRecoverySigningKey, initiatorRecoverySigningKey, "recovery signing keys should be equal")
717 XCTAssertEqual(firstDeviceRecoveryEncryptionKey, initiatorRecoveryEncryptionKey, "recovery encryption keys should be equal")
718 }
719
720 func testOTCliqueJoiningUsingRecoveryKey() throws {
721 OctagonRecoveryKeySetIsEnabled(true)
722 self.manager.setSOSEnabledForPlatformFlag(false)
723 self.startCKAccountStatusMock()
724
725 let establishContextID = "establish-context-id"
726 let establishContext = self.createEstablishContext(contextID: establishContextID)
727
728 establishContext.startOctagonStateMachine()
729 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
730
731 let clique: OTClique
732 let recoverykeyotcliqueContext = OTConfigurationContext()
733 recoverykeyotcliqueContext.context = establishContextID
734 recoverykeyotcliqueContext.dsid = "1234"
735 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
736 recoverykeyotcliqueContext.otControl = self.otControl
737 do {
738 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
739 XCTAssertNotNil(clique, "Clique should not be nil")
740 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
741 } catch {
742 XCTFail("Shouldn't have errored making new friends: \(error)")
743 throw error
744 }
745
746 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
747 self.assertConsidersSelfTrusted(context: establishContext)
748
749 // Fake that this peer also created some TLKShares for itself
750 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
751 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
752
753 self.assertSelfTLKSharesInCloudKit(context: establishContext)
754
755 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
756 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
757 self.manager.setSOSEnabledForPlatformFlag(true)
758
759 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
760 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
761 XCTAssertNil(error, "error should be nil")
762 setRecoveryKeyExpectation.fulfill()
763 }
764 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
765
766 self.sendContainerChangeWaitForFetch(context: establishContext)
767
768 let newCliqueContext = OTConfigurationContext()
769 newCliqueContext.context = OTDefaultContext
770 newCliqueContext.dsid = self.otcliqueContext.dsid
771 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
772 newCliqueContext.otControl = self.otControl
773
774 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
775 newGuyContext.startOctagonStateMachine()
776
777 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
778
779 self.manager.setSOSEnabledForPlatformFlag(true)
780 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
781 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
782 XCTAssertNil(error, "error should be nil")
783 joinWithRecoveryKeyExpectation.fulfill()
784 }
785 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
786
787 self.sendContainerChangeWaitForFetch(context: newGuyContext)
788
789 let stableInfoAcceptorCheckDumpCallback = self.expectation(description: "stableInfoAcceptorCheckDumpCallback callback occurs")
790 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
791 dump, _ in
792 XCTAssertNotNil(dump, "dump should not be nil")
793 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
794 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
795 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
796 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
797
798 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
799 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
800 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
801 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
802
803 let included = dynamicInfo!["included"] as? Array<String>
804 XCTAssertNotNil(included, "included should not be nil")
805 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
806 let vouchers = dump!["vouchers"]
807 XCTAssertNotNil(vouchers, "vouchers should not be nil")
808 stableInfoAcceptorCheckDumpCallback.fulfill()
809 }
810 self.wait(for: [stableInfoAcceptorCheckDumpCallback], timeout: 10)
811 self.assertEnters(context: newGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
812 self.assertConsidersSelfTrusted(context: newGuyContext)
813 try self.putSelfTLKShareInCloudKit(context: newGuyContext, zoneID: self.manateeZoneID)
814 self.assertSelfTLKSharesInCloudKit(context: newGuyContext)
815
816 self.sendContainerChangeWaitForFetch(context: establishContext)
817
818 let stableInfoCheckDumpCallback = self.expectation(description: "stableInfoCheckDumpCallback callback occurs")
819 self.tphClient.dump(withContainer: OTCKContainerName, context: establishContextID) {
820 dump, _ in
821 XCTAssertNotNil(dump, "dump should not be nil")
822 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
823 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
824 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
825 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
826
827 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
828 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
829 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
830 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
831
832 let included = dynamicInfo!["included"] as? Array<String>
833 XCTAssertNotNil(included, "included should not be nil")
834 XCTAssertEqual(included!.count, 2, "should be 2 peer ids")
835 let vouchers = dump!["vouchers"]
836 XCTAssertNotNil(vouchers, "vouchers should not be nil")
837 stableInfoCheckDumpCallback.fulfill()
838 }
839 self.wait(for: [stableInfoCheckDumpCallback], timeout: 10)
840 }
841
842 func testOTCliqueJoinUsingANotEnrolledRecoveryKey() throws {
843 OctagonRecoveryKeySetIsEnabled(true)
844 self.manager.setSOSEnabledForPlatformFlag(false)
845 self.startCKAccountStatusMock()
846
847 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
848 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
849
850 let newCliqueContext = OTConfigurationContext()
851 newCliqueContext.context = OTDefaultContext
852 newCliqueContext.dsid = self.otcliqueContext.dsid
853 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
854 newCliqueContext.otControl = self.otControl
855
856 let recoveryGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
857 self.manager.setSOSEnabledForPlatformFlag(true)
858
859 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
860 TestsObjectiveC.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
861 XCTAssertNil(error, "error should be nil")
862 joinWithRecoveryKeyExpectation.fulfill()
863 }
864 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
865
866 self.sendContainerChange(context: recoveryGuyContext)
867
868 let newGuyCheckDumpCallback = self.expectation(description: "newGuyCheckDumpCallback callback occurs")
869 self.tphClient.dump(withContainer: OTCKContainerName, context: OTDefaultContext) {
870 dump, _ in
871 XCTAssertNotNil(dump, "dump should not be nil")
872 let egoSelf = dump!["self"] as? Dictionary<String, AnyObject>
873 XCTAssertNotNil(egoSelf, "egoSelf should not be nil")
874 let dynamicInfo = egoSelf!["dynamicInfo"] as? Dictionary<String, AnyObject>
875 XCTAssertNotNil(dynamicInfo, "dynamicInfo should not be nil")
876
877 let stableInfo = egoSelf!["stableInfo"] as? Dictionary<String, AnyObject>
878 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
879 XCTAssertNotNil(stableInfo!["recovery_signing_public_key"], "recoverySigningPublicKey should not be nil")
880 XCTAssertNotNil(stableInfo!["recovery_encryption_public_key"], "recoveryEncryptionPublicKey should not be nil")
881
882 let included = dynamicInfo!["included"] as? Array<String>
883 XCTAssertNotNil(included, "included should not be nil")
884 XCTAssertEqual(included!.count, 1, "should be 1 peer ids")
885 let vouchers = dump!["vouchers"]
886 XCTAssertNotNil(vouchers, "vouchers should not be nil")
887 newGuyCheckDumpCallback.fulfill()
888 }
889 self.wait(for: [newGuyCheckDumpCallback], timeout: 10)
890 self.assertEnters(context: recoveryGuyContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
891
892 self.assertSelfTLKSharesInCloudKit(context: recoveryGuyContext)
893 }
894
895 func testSetRecoveryKeyAsLimitedPeer() throws {
896 self.manager.setSOSEnabledForPlatformFlag(false)
897
898 self.startCKAccountStatusMock()
899
900 self.cuttlefishContext.startOctagonStateMachine()
901 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
902
903 XCTAssertFalse(self.mockAuthKit.currentDeviceList().isEmpty, "should not have zero devices")
904
905 let clique: OTClique
906 do {
907 clique = try OTClique.newFriends(withContextData: self.otcliqueContext, resetReason: .testGenerated)
908 XCTAssertNotNil(clique, "Clique should not be nil")
909 } catch {
910 XCTFail("Shouldn't have errored making new friends: \(error)")
911 throw error
912 }
913
914 let entropy = try self.loadSecret(label: clique.cliqueMemberIdentifier!)
915 XCTAssertNotNil(entropy, "entropy should not be nil")
916
917 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
918 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
919
920 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
921 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
922
923 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
924 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
925 XCTAssertNotNil(error, "error should not be nil")
926 XCTAssertEqual((error! as NSError).code, OctagonError.OTErrorLimitedPeer.rawValue, "error code should be limited peer")
927 createKeyExpectation.fulfill()
928 }
929 self.wait(for: [createKeyExpectation], timeout: 10)
930 }
931
932 func testVouchWithRecoveryKeySetByUntrustedPeer() throws {
933 OctagonRecoveryKeySetIsEnabled(true)
934 self.manager.setSOSEnabledForPlatformFlag(false)
935 self.startCKAccountStatusMock()
936
937 let establishContextID = "establish-context-id"
938 let establishContext = self.createEstablishContext(contextID: establishContextID)
939
940 establishContext.startOctagonStateMachine()
941 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
942
943 let clique: OTClique
944 let recoverykeyotcliqueContext = OTConfigurationContext()
945 recoverykeyotcliqueContext.context = establishContextID
946 recoverykeyotcliqueContext.dsid = "1234"
947 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
948 recoverykeyotcliqueContext.otControl = self.otControl
949 do {
950 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
951 XCTAssertNotNil(clique, "Clique should not be nil")
952 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
953 } catch {
954 XCTFail("Shouldn't have errored making new friends: \(error)")
955 throw error
956 }
957
958 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
959 self.assertConsidersSelfTrusted(context: establishContext)
960
961 // Fake that this peer also created some TLKShares for itself
962 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
963 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
964
965 self.assertSelfTLKSharesInCloudKit(context: establishContext)
966
967 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
968 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
969 self.manager.setSOSEnabledForPlatformFlag(true)
970
971 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
972 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
973 XCTAssertNil(error, "error should be nil")
974 setRecoveryKeyExpectation.fulfill()
975 }
976 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
977
978 self.sendContainerChangeWaitForFetch(context: establishContext)
979
980 //now this peer will leave octagon
981 XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique")
982
983 // securityd should now consider itself untrusted
984 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
985 self.assertConsidersSelfUntrusted(context: establishContext)
986
987 let newCliqueContext = OTConfigurationContext()
988 newCliqueContext.context = OTDefaultContext
989 newCliqueContext.dsid = self.otcliqueContext.dsid
990 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
991 newCliqueContext.otControl = self.otControl
992
993 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
994 newGuyContext.startOctagonStateMachine()
995
996 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
997
998 self.manager.setSOSEnabledForPlatformFlag(true)
999 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1000 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1001 XCTAssertNil(error, "error should be nil")
1002 joinWithRecoveryKeyExpectation.fulfill()
1003 }
1004 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1005 }
1006
1007 func testVouchWithWrongRecoveryKey() throws {
1008 OctagonRecoveryKeySetIsEnabled(true)
1009 self.manager.setSOSEnabledForPlatformFlag(false)
1010 self.startCKAccountStatusMock()
1011
1012 let establishContextID = "establish-context-id"
1013 let establishContext = self.createEstablishContext(contextID: establishContextID)
1014
1015 establishContext.startOctagonStateMachine()
1016 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1017
1018 let clique: OTClique
1019 let recoverykeyotcliqueContext = OTConfigurationContext()
1020 recoverykeyotcliqueContext.context = establishContextID
1021 recoverykeyotcliqueContext.dsid = "1234"
1022 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1023 recoverykeyotcliqueContext.otControl = self.otControl
1024 do {
1025 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1026 XCTAssertNotNil(clique, "Clique should not be nil")
1027 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1028 } catch {
1029 XCTFail("Shouldn't have errored making new friends: \(error)")
1030 throw error
1031 }
1032
1033 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1034 self.assertConsidersSelfTrusted(context: establishContext)
1035
1036 // Fake that this peer also created some TLKShares for itself
1037 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1038 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1039
1040 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1041
1042 var recoveryKey = SecRKCreateRecoveryKeyString(nil)
1043 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1044 self.manager.setSOSEnabledForPlatformFlag(true)
1045
1046 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
1047 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
1048 XCTAssertNil(error, "error should be nil")
1049 setRecoveryKeyExpectation.fulfill()
1050 }
1051 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
1052
1053 self.sendContainerChangeWaitForFetch(context: establishContext)
1054
1055 let newCliqueContext = OTConfigurationContext()
1056 newCliqueContext.context = OTDefaultContext
1057 newCliqueContext.dsid = self.otcliqueContext.dsid
1058 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1059 newCliqueContext.otControl = self.otControl
1060
1061 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1062 newGuyContext.startOctagonStateMachine()
1063
1064 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1065
1066 self.manager.setSOSEnabledForPlatformFlag(true)
1067 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1068
1069 //creating new random recovery key
1070 recoveryKey = SecRKCreateRecoveryKeyString(nil)
1071 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1072
1073 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1074 XCTAssertNotNil(error, "error should NOT be nil")
1075 XCTAssertEqual((error! as NSError).code, 32, "error code should be 32/untrusted recovery keys")
1076 XCTAssertEqual((error! as NSError).domain, "com.apple.security.trustedpeers.container", "error code domain should be com.apple.security.trustedpeers.container")
1077 joinWithRecoveryKeyExpectation.fulfill()
1078 }
1079 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1080 }
1081
1082 func testRecoveryWithDistrustedPeers() throws {
1083 OctagonRecoveryKeySetIsEnabled(true)
1084 self.manager.setSOSEnabledForPlatformFlag(false)
1085 self.startCKAccountStatusMock()
1086
1087 let establishContextID = "establish-context-id"
1088 let establishContext = self.createEstablishContext(contextID: establishContextID)
1089
1090 establishContext.startOctagonStateMachine()
1091 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1092
1093 let clique: OTClique
1094 let recoverykeyotcliqueContext = OTConfigurationContext()
1095 recoverykeyotcliqueContext.context = establishContextID
1096 recoverykeyotcliqueContext.dsid = "1234"
1097 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1098 recoverykeyotcliqueContext.otControl = self.otControl
1099 do {
1100 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1101 XCTAssertNotNil(clique, "Clique should not be nil")
1102 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1103 } catch {
1104 XCTFail("Shouldn't have errored making new friends: \(error)")
1105 throw error
1106 }
1107
1108 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1109 self.assertConsidersSelfTrusted(context: establishContext)
1110
1111 // Fake that this peer also created some TLKShares for itself
1112 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1113 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1114
1115 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1116
1117 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1118 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1119 self.manager.setSOSEnabledForPlatformFlag(true)
1120
1121 let setRecoveryKeyExpectation = self.expectation(description: "setRecoveryKeyExpectation callback occurs")
1122 TestsObjectiveC.setNewRecoveryKeyWithData(recoverykeyotcliqueContext, recoveryKey: recoveryKey!) { _, error in
1123 XCTAssertNil(error, "error should be nil")
1124 setRecoveryKeyExpectation.fulfill()
1125 }
1126 self.wait(for: [setRecoveryKeyExpectation], timeout: 10)
1127
1128 self.sendContainerChangeWaitForFetch(context: establishContext)
1129
1130 //now this peer will leave octagon
1131 XCTAssertNoThrow(try clique.leave(), "Should be no error departing clique")
1132
1133 // securityd should now consider itself untrusted
1134 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1135 self.assertConsidersSelfUntrusted(context: establishContext)
1136
1137 let newCliqueContext = OTConfigurationContext()
1138 newCliqueContext.context = OTDefaultContext
1139 newCliqueContext.dsid = self.otcliqueContext.dsid
1140 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1141 newCliqueContext.otControl = self.otControl
1142
1143 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1144 newGuyContext.startOctagonStateMachine()
1145
1146 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1147
1148 self.manager.setSOSEnabledForPlatformFlag(true)
1149 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1150
1151 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey!) { error in
1152 XCTAssertNil(error, "error should be nil")
1153 joinWithRecoveryKeyExpectation.fulfill()
1154 }
1155 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1156 }
1157
1158 func testMalformedRecoveryKey() throws {
1159 OctagonRecoveryKeySetIsEnabled(true)
1160 self.manager.setSOSEnabledForPlatformFlag(false)
1161 self.startCKAccountStatusMock()
1162
1163 let establishContextID = "establish-context-id"
1164 let establishContext = self.createEstablishContext(contextID: establishContextID)
1165
1166 establishContext.startOctagonStateMachine()
1167 self.assertEnters(context: establishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
1168
1169 let clique: OTClique
1170 let recoverykeyotcliqueContext = OTConfigurationContext()
1171 recoverykeyotcliqueContext.context = establishContextID
1172 recoverykeyotcliqueContext.dsid = "1234"
1173 recoverykeyotcliqueContext.altDSID = self.mockAuthKit.altDSID!
1174 recoverykeyotcliqueContext.otControl = self.otControl
1175 do {
1176 clique = try OTClique.newFriends(withContextData: recoverykeyotcliqueContext, resetReason: .testGenerated)
1177 XCTAssertNotNil(clique, "Clique should not be nil")
1178 XCTAssertNotNil(clique.cliqueMemberIdentifier, "Should have a member identifier after a clique newFriends call")
1179 } catch {
1180 XCTFail("Shouldn't have errored making new friends: \(error)")
1181 throw error
1182 }
1183
1184 self.assertEnters(context: establishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
1185 self.assertConsidersSelfTrusted(context: establishContext)
1186
1187 // Fake that this peer also created some TLKShares for itself
1188 self.putFakeKeyHierarchy(inCloudKit: self.manateeZoneID)
1189 try self.putSelfTLKShareInCloudKit(context: establishContext, zoneID: self.manateeZoneID)
1190
1191 self.assertSelfTLKSharesInCloudKit(context: establishContext)
1192
1193 let recoveryKey = "malformedRecoveryKey"
1194 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1195 self.manager.setSOSEnabledForPlatformFlag(true)
1196
1197 let createKeyExpectation = self.expectation(description: "createKeyExpectation returns")
1198 self.manager.createRecoveryKey(OTCKContainerName, contextID: self.otcliqueContext.context ?? "defaultContext", recoveryKey: recoveryKey) { error in
1199 XCTAssertNotNil(error, "error should NOT be nil")
1200 XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key")
1201 XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon")
1202 createKeyExpectation.fulfill()
1203 }
1204 self.wait(for: [createKeyExpectation], timeout: 10)
1205
1206 let newCliqueContext = OTConfigurationContext()
1207 newCliqueContext.context = OTDefaultContext
1208 newCliqueContext.dsid = self.otcliqueContext.dsid
1209 newCliqueContext.altDSID = self.mockAuthKit.altDSID!
1210 newCliqueContext.otControl = self.otControl
1211
1212 let newGuyContext = self.manager.context(forContainerName: OTCKContainerName, contextID: OTDefaultContext)
1213 newGuyContext.startOctagonStateMachine()
1214
1215 self.sendContainerChangeWaitForUntrustedFetch(context: newGuyContext)
1216
1217 self.manager.setSOSEnabledForPlatformFlag(true)
1218 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKeyExpectation callback occurs")
1219
1220 OTClique.recoverOctagon(usingData: newCliqueContext, recoveryKey: recoveryKey) { error in
1221 XCTAssertNotNil(error, "error should NOT be nil")
1222 XCTAssertEqual((error! as NSError).code, 41, "error code should be 41/malformed recovery key")
1223 XCTAssertEqual((error! as NSError).domain, "com.apple.security.octagon", "error code domain should be com.apple.security.octagon")
1224 joinWithRecoveryKeyExpectation.fulfill()
1225 }
1226 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
1227 }
1228 }
1229 #endif