]> git.saurik.com Git - apple/security.git/blob - keychain/ot/tests/octagon/OctagonTests+ForwardCompatibility.swift
67c3928f2c1726d59d1821b961c5fb5be19f1329
[apple/security.git] / keychain / ot / tests / octagon / OctagonTests+ForwardCompatibility.swift
1 #if OCTAGON
2
3 import Foundation
4
5 class OctagonForwardCompatibilityTests: OctagonTestsBase {
6 func testApprovePeerWithNewPolicy() throws {
7 self.startCKAccountStatusMock()
8 let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()
9
10 // Now, we'll approve a new peer with a new policy! First, make that new policy.
11 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
12 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
13 let currentPolicy = currentPolicyOptional!
14
15 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
16 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
17
18 let peer2ContextID = "asdf"
19
20 // Assist the other client here: it'll likely already have the policy built-in
21 let fetchExpectation = self.expectation(description: "fetch callback occurs")
22 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
23 context: peer2ContextID,
24 versions: Set([newPolicy.version])) { _, error in
25 XCTAssertNil(error, "Should have no error")
26 fetchExpectation.fulfill()
27 }
28 self.wait(for: [fetchExpectation], timeout: 10)
29
30 var peer2ID: String = "not initialized"
31
32 let prepareExpectation = self.expectation(description: "prepare callback occurs")
33 let vouchExpectation = self.expectation(description: "vouch callback occurs")
34 let joinExpectation = self.expectation(description: "join callback occurs")
35 let serverJoinExpectation = self.expectation(description: "joinWithVoucher is called")
36
37 self.tphClient.prepare(withContainer: OTCKContainerName,
38 context: peer2ContextID,
39 epoch: 1,
40 machineID: self.mockAuthKit2.currentMachineID,
41 bottleSalt: self.mockAuthKit2.altDSID!,
42 bottleID: "why-is-this-nonnil",
43 modelID: self.mockDeviceInfo.modelID(),
44 deviceName: "new-policy-peer",
45 serialNumber: "1234",
46 osVersion: "something",
47 policyVersion: newPolicy.version,
48 policySecrets: nil,
49 signingPrivKeyPersistentRef: nil,
50 encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
51 XCTAssertNil(error, "Should be no error preparing the second peer")
52 XCTAssertNotNil(peerID, "Should have a peerID")
53 peer2ID = peerID!
54
55 XCTAssertNotNil(stableInfo, "Should have a stable info")
56 XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
57
58 let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
59 XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")
60
61 XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
62 XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")
63
64 self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
65 context: self.cuttlefishContext.contextID,
66 peerID: peerID!,
67 permanentInfo: permanentInfo!,
68 permanentInfoSig: permanentInfoSig!,
69 stableInfo: stableInfo!,
70 stableInfoSig: stableInfoSig!,
71 ckksKeys: []) { voucher, voucherSig, error in
72 XCTAssertNil(error, "Should be no error vouching")
73 XCTAssertNotNil(voucher, "Should have a voucher")
74 XCTAssertNotNil(voucherSig, "Should have a voucher signature")
75
76 self.fakeCuttlefishServer.joinListener = { joinRequest in
77 XCTAssertEqual(peer2ID, joinRequest.peer.peerID, "joinWithVoucher request should be for peer 2")
78 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
79 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
80
81 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
82 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")
83
84 serverJoinExpectation.fulfill()
85 return nil
86 }
87
88 self.tphClient.join(withContainer: OTCKContainerName,
89 context: peer2ContextID,
90 voucherData: voucher!,
91 voucherSig: voucherSig!,
92 ckksKeys: [],
93 tlkShares: [],
94 preapprovedKeys: []) { peerID, _, _, _, error in
95 XCTAssertNil(error, "Should be no error joining")
96 XCTAssertNotNil(peerID, "Should have a peerID")
97 joinExpectation.fulfill()
98 }
99 vouchExpectation.fulfill()
100 }
101 prepareExpectation.fulfill()
102 }
103 self.wait(for: [prepareExpectation, vouchExpectation, joinExpectation, serverJoinExpectation], timeout: 10)
104
105 // Then, after the remote peer joins, the original peer should realize it trusts the new peer and update its own stableinfo to use the new policy
106 let updateTrustExpectation = self.expectation(description: "updateTrust")
107 self.fakeCuttlefishServer.updateListener = { request in
108 XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
109 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
110 XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")
111
112 let newStableInfo = request.stableInfoAndSig.stableInfo()
113
114 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
115 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Prevailing policy version in peer should match new policy version")
116
117 updateTrustExpectation.fulfill()
118 return nil
119 }
120
121 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
122 self.wait(for: [updateTrustExpectation], timeout: 10)
123 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
124 }
125
126 func testRejectVouchingForPeerWithUnknownNewPolicy() throws {
127 self.startCKAccountStatusMock()
128 _ = self.assertResetAndBecomeTrustedInDefaultContext()
129
130 // Now, a new peer joins with a policy we can't fetch
131 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
132 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
133 let currentPolicy = currentPolicyOptional!
134
135 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
136
137 let peer2ContextID = "asdf"
138
139 // Assist the other client here: it'll likely already have this built-in
140 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
141
142 let fetchExpectation = self.expectation(description: "fetch callback occurs")
143 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
144 context: peer2ContextID,
145 versions: Set([newPolicy.version])) { _, error in
146 XCTAssertNil(error, "Should have no error")
147 fetchExpectation.fulfill()
148 }
149 self.wait(for: [fetchExpectation], timeout: 10)
150
151 // Remove the policy, now that peer2 has it
152 self.fakeCuttlefishServer.policyOverlay.removeAll()
153
154 let prepareExpectation = self.expectation(description: "prepare callback occurs")
155 let vouchExpectation = self.expectation(description: "vouch callback occurs")
156
157 self.tphClient.prepare(withContainer: OTCKContainerName,
158 context: peer2ContextID,
159 epoch: 1,
160 machineID: self.mockAuthKit2.currentMachineID,
161 bottleSalt: self.mockAuthKit2.altDSID!,
162 bottleID: "why-is-this-nonnil",
163 modelID: self.mockDeviceInfo.modelID(),
164 deviceName: "new-policy-peer",
165 serialNumber: "1234",
166 osVersion: "something",
167 policyVersion: newPolicy.version,
168 policySecrets: nil,
169 signingPrivKeyPersistentRef: nil,
170 encPrivKeyPersistentRef: nil) { peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error in
171 XCTAssertNil(error, "Should be no error preparing the second peer")
172 XCTAssertNotNil(peerID, "Should have a peerID")
173
174 XCTAssertNotNil(stableInfo, "Should have a stable info")
175 XCTAssertNotNil(stableInfoSig, "Should have a stable info signature")
176
177 let newStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
178 XCTAssertNotNil(newStableInfo, "should be able to make a stableInfo info from protobuf")
179
180 XCTAssertEqual(newStableInfo?.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
181 XCTAssertEqual(newStableInfo?.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version")
182
183 self.tphClient.vouch(withContainer: self.cuttlefishContext.containerName,
184 context: self.cuttlefishContext.contextID,
185 peerID: peerID!,
186 permanentInfo: permanentInfo!,
187 permanentInfoSig: permanentInfoSig!,
188 stableInfo: stableInfo!,
189 stableInfoSig: stableInfoSig!,
190 ckksKeys: []) { voucher, voucherSig, error in
191 XCTAssertNotNil(error, "should be an error vouching for a peer with an unknown policy")
192 XCTAssertNil(voucher, "Should have no voucher")
193 XCTAssertNil(voucherSig, "Should have no voucher signature")
194
195 vouchExpectation.fulfill()
196 }
197 prepareExpectation.fulfill()
198 }
199 self.wait(for: [prepareExpectation, vouchExpectation], timeout: 10)
200 }
201
202 func testIgnoreAlreadyJoinedPeerWithUnknownNewPolicy() throws {
203 self.startCKAccountStatusMock()
204 let peer1ID = self.assertResetAndBecomeTrustedInDefaultContext()
205
206 // Now, a new peer joins with a policy we can't fetch
207 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
208 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
209 let currentPolicy = currentPolicyOptional!
210
211 let newPolicy = currentPolicy.clone(withVersionNumber: currentPolicy.version.versionNumber + 1)!
212
213 let peer2ContextID = "asdf"
214
215 // Assist the other client here: it'll likely already have this built-in
216 self.fakeCuttlefishServer.policyOverlay.append(newPolicy)
217
218 let fetchExpectation = self.expectation(description: "fetch callback occurs")
219 self.tphClient.fetchPolicyDocuments(withContainer: OTCKContainerName,
220 context: peer2ContextID,
221 versions: Set([newPolicy.version])) { _, error in
222 XCTAssertNil(error, "Should have no error")
223 fetchExpectation.fulfill()
224 }
225 self.wait(for: [fetchExpectation], timeout: 10)
226
227 // Remove the policy, now that peer2 has it
228 self.fakeCuttlefishServer.policyOverlay.removeAll()
229
230 let joiningContext = self.makeInitiatorContext(contextID: peer2ContextID, authKitAdapter: self.mockAuthKit2)
231 joiningContext.policyOverride = newPolicy.version
232
233 let serverJoinExpectation = self.expectation(description: "peer2 joins successfully")
234 self.fakeCuttlefishServer.joinListener = { joinRequest in
235 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
236 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
237
238 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Frozen policy version in new identity should match frozen policy version")
239 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicy.version, "Flexible policy version in new identity should match new policy version (as provided by new peer)")
240
241 serverJoinExpectation.fulfill()
242 return nil
243 }
244
245 _ = self.assertJoinViaEscrowRecovery(joiningContext: joiningContext, sponsor: self.cuttlefishContext)
246 self.wait(for: [serverJoinExpectation], timeout: 10)
247
248 // Then, after the remote peer joins, the original peer should ignore it entirely: peer1 has no idea what this new policy is about
249 // That means it won't update its trust in response to the join
250 self.fakeCuttlefishServer.updateListener = { request in
251 XCTFail("Expected no updateTrust after peer1 joins")
252 XCTAssertEqual(peer1ID, request.peerID, "updateTrust request should be for peer 1")
253 /*
254 * But, if it did update its trust, here's what we would expect:
255 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
256 XCTAssertFalse(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should not trust peer2")
257 XCTAssertFalse(newDynamicInfo.excludedPeerIDs.contains(peer2ID), "Peer1 should not distrust peer2")
258
259 let newStableInfo = request.stableInfoAndSig.stableInfo()
260
261 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
262 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, prevailingPolicyVersion, "Prevailing policy version in peer should match current prevailing policy version")
263
264 updateTrustExpectation.fulfill()
265 */
266 return nil
267 }
268
269 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
270 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
271 }
272
273 func createOctagonAndCKKSUsingFuturePolicy() throws -> (TPPolicyDocument, CKRecordZone.ID) {
274 // We want to set up a world with a peer, in Octagon, with TLKs for zones that don't even exist in our current policy.
275 // First, make a new policy.
276 let currentPolicyOptional = builtInPolicyDocuments().filter { $0.version.versionNumber == prevailingPolicyVersion.versionNumber }.first
277 XCTAssertNotNil(currentPolicyOptional, "Should have one current policy")
278 let currentPolicyDocument = currentPolicyOptional!
279
280 let futureViewName = "FutureView"
281 let futureViewMapping = TPPBPolicyKeyViewMapping()!
282
283 let futureViewZoneID = CKRecordZone.ID(zoneName: futureViewName)
284 self.ckksZones.add(futureViewZoneID)
285 self.injectedManager!.setSyncingViewsAllowList(Set((self.intendedCKKSZones.union([futureViewZoneID])).map { $0.zoneName }))
286
287 self.zones![futureViewZoneID] = FakeCKZone(zone: futureViewZoneID)
288
289 XCTAssertFalse(currentPolicyDocument.categoriesByView.keys.contains(futureViewName), "Current policy should not include future view")
290
291 let newPolicyDocument = try TPPolicyDocument(internalVersion: currentPolicyDocument.version.versionNumber + 1,
292 modelToCategory: currentPolicyDocument.modelToCategory,
293 categoriesByView: currentPolicyDocument.categoriesByView.merging([futureViewName: Set(["watch", "full", "tv"])]) { _, new in new },
294 introducersByCategory: currentPolicyDocument.introducersByCategory,
295 redactions: [:],
296 keyViewMapping: [futureViewMapping] + currentPolicyDocument.keyViewMapping,
297 hashAlgo: .SHA256)
298
299 self.fakeCuttlefishServer.policyOverlay.append(newPolicyDocument)
300
301 return (newPolicyDocument, futureViewZoneID)
302 }
303
304 func testRestoreBottledPeerUsingFuturePolicy() throws {
305 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
306
307 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
308 futurePeerContext.policyOverride = newPolicyDocument.version
309
310 self.startCKAccountStatusMock()
311 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
312
313 self.putFakeKeyHierarchiesInCloudKit()
314 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
315 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
316
317 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via escrow recovery.
318 // It should be able to recover the FutureView TLK
319 self.assertAllCKKSViewsUpload(tlkShares: 1)
320
321 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
322 self.fakeCuttlefishServer.joinListener = { joinRequest in
323 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
324 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
325
326 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
327 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
328
329 serverJoinExpectation.fulfill()
330 return nil
331 }
332
333 let peerID = self.assertJoinViaEscrowRecovery(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)
334 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
335 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
336 self.verifyDatabaseMocks()
337
338 self.wait(for: [serverJoinExpectation], timeout: 10)
339
340 // And the joined peer should have recovered the TLK, and uploaded itself a share
341 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
342 }
343
344 func testPairingJoinUsingFuturePolicy() throws {
345 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
346
347 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
348 futurePeerContext.policyOverride = newPolicyDocument.version
349
350 self.startCKAccountStatusMock()
351 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
352
353 self.putFakeKeyHierarchiesInCloudKit()
354 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
355 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
356
357 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via pairing
358 // It should be able to recover the FutureView TLK
359 self.assertAllCKKSViewsUpload(tlkShares: 1)
360
361 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
362 self.fakeCuttlefishServer.joinListener = { joinRequest in
363 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
364 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
365
366 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
367 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
368
369 serverJoinExpectation.fulfill()
370 return nil
371 }
372
373 let peerID = self.assertJoinViaProximitySetup(joiningContext: self.cuttlefishContext, sponsor: futurePeerContext)
374
375 // And then fake like the other peer uploaded TLKShares after the join succeeded (it would normally happen during, but that's okay)
376 try self.putAllTLKSharesInCloudKit(to: self.cuttlefishContext, from: futurePeerContext)
377 self.sendAllCKKSViewsZoneChanged()
378
379 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
380 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
381 self.verifyDatabaseMocks()
382
383 self.wait(for: [serverJoinExpectation], timeout: 10)
384
385 // And the joined peer should have recovered the TLK, and uploaded itself a share
386 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: peerID, senderPeerID: peerID, zoneID: futureViewZoneID))
387 }
388
389 func testRecoveryKeyJoinUsingFuturePolicy() throws {
390 let (newPolicyDocument, futureViewZoneID) = try self.createOctagonAndCKKSUsingFuturePolicy()
391
392 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
393 futurePeerContext.policyOverride = newPolicyDocument.version
394
395 self.startCKAccountStatusMock()
396 let futurePeerID = self.assertResetAndBecomeTrusted(context: futurePeerContext)
397
398 self.putFakeKeyHierarchiesInCloudKit()
399 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
400 XCTAssertTrue(try self.tlkShareInCloudKit(receiverPeerID: futurePeerID, senderPeerID: futurePeerID, zoneID: futureViewZoneID))
401
402 // Create the recovery key
403 let recoveryKey = SecPasswordGenerate(SecPasswordType(kSecPasswordTypeiCloudRecoveryKey), nil, nil)! as String
404 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
405
406 self.manager.setSOSEnabledForPlatformFlag(true)
407
408 let createRecoveryExpectation = self.expectation(description: "createRecoveryExpectation returns")
409 self.manager.createRecoveryKey(OTCKContainerName, contextID: futurePeerContext.contextID, recoveryKey: recoveryKey) { error in
410 XCTAssertNil(error, "error should be nil")
411 createRecoveryExpectation.fulfill()
412 }
413 self.wait(for: [createRecoveryExpectation], timeout: 10)
414
415 // Setting the RK will make TLKShares to the RK, so help that out too
416 try self.putRecoveryKeyTLKSharesInCloudKit(recoveryKey: recoveryKey, salt: self.mockAuthKit.altDSID!)
417
418 // Now, our peer (with no inbuilt knowledge of newPolicyDocument) joins via RecoveryKey
419 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
420 self.fakeCuttlefishServer.joinListener = { joinRequest in
421 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
422 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
423
424 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
425 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
426
427 serverJoinExpectation.fulfill()
428 return nil
429 }
430
431 // It should recover and upload the FutureView TLK
432 self.assertAllCKKSViewsUpload(tlkShares: 1)
433
434 self.cuttlefishContext.startOctagonStateMachine()
435 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateUntrusted, within: 10 * NSEC_PER_SEC)
436
437 let joinWithRecoveryKeyExpectation = self.expectation(description: "joinWithRecoveryKey callback occurs")
438 self.cuttlefishContext.join(withRecoveryKey: recoveryKey) { error in
439 XCTAssertNil(error, "error should be nil")
440 joinWithRecoveryKeyExpectation.fulfill()
441 }
442 self.wait(for: [joinWithRecoveryKeyExpectation], timeout: 10)
443
444 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateReady, within: 10 * NSEC_PER_SEC)
445 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
446 self.verifyDatabaseMocks()
447
448 self.wait(for: [serverJoinExpectation], timeout: 10)
449 }
450
451 func testPreapprovedJoinUsingFuturePolicy() throws {
452 let peer2mockSOS = CKKSMockSOSPresentAdapter(selfPeer: self.createSOSPeer(peerID: "peer2ID"), trustedPeers: self.mockSOSAdapter.allPeers(), essential: false)
453 print(peer2mockSOS.allPeers())
454 self.mockSOSAdapter.trustedPeers.add(peer2mockSOS.selfPeer)
455
456 let futurePeerContext = self.manager.context(forContainerName: OTCKContainerName,
457 contextID: "futurePeer",
458 sosAdapter: peer2mockSOS,
459 authKitAdapter: self.mockAuthKit2,
460 lockStateTracker: self.lockStateTracker,
461 accountStateTracker: self.accountStateTracker,
462 deviceInformationAdapter: OTMockDeviceInfoAdapter(modelID: "iPhone9,1", deviceName: "test-SOS-iphone", serialNumber: "456", osVersion: "iOS (fake version)"))
463
464 let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()
465 futurePeerContext.policyOverride = newPolicyDocument.version
466
467 let serverEstablishExpectation = self.expectation(description: "futurePeer establishes successfully")
468 self.fakeCuttlefishServer.establishListener = { establishRequest in
469 XCTAssertTrue(establishRequest.peer.hasStableInfoAndSig, "Establishing peer should have a stable info")
470 let newStableInfo = establishRequest.peer.stableInfoAndSig.stableInfo()
471
472 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
473 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
474 serverEstablishExpectation.fulfill()
475 return nil
476 }
477
478 // Setup is complete. Join using a new peer
479 self.startCKAccountStatusMock()
480 futurePeerContext.startOctagonStateMachine()
481 self.assertEnters(context: futurePeerContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
482 self.wait(for: [serverEstablishExpectation], timeout: 10)
483
484 self.putFakeKeyHierarchiesInCloudKit()
485 try self.putSelfTLKSharesInCloudKit(context: futurePeerContext)
486
487 // Now, the default peer joins via SOS preapproval
488 let serverJoinExpectation = self.expectation(description: "peer1 joins successfully")
489 self.fakeCuttlefishServer.joinListener = { joinRequest in
490 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
491 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
492
493 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
494 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
495 serverJoinExpectation.fulfill()
496 return nil
497 }
498
499 self.mockSOSAdapter.circleStatus = SOSCCStatus(kSOSCCInCircle)
500 self.cuttlefishContext.startOctagonStateMachine()
501 self.assertEnters(context: self.cuttlefishContext, state: OctagonStateReady, within: 10 * NSEC_PER_SEC)
502 self.assertConsidersSelfTrusted(context: self.cuttlefishContext)
503 self.wait(for: [serverJoinExpectation], timeout: 10)
504
505 // But, since we're not mocking the remote peer sharing the TLKs, ckks should get stuck
506 self.assertAllCKKSViews(enter: SecCKKSZoneKeyStateWaitForTLK, within: 10 * NSEC_PER_SEC)
507 XCTAssertEqual(self.injectedManager!.policy?.version, newPolicyDocument.version, "CKKS should be configured with new policy")
508 self.verifyDatabaseMocks()
509 }
510
511 func testRespondToFuturePoliciesInPeerUpdates() throws {
512 self.startCKAccountStatusMock()
513 self.assertResetAndBecomeTrustedInDefaultContext()
514
515 // Now, another peer comes along and joins via BP recovery, using a new policy
516 let (newPolicyDocument, _) = try self.createOctagonAndCKKSUsingFuturePolicy()
517
518 let futurePeerContext = self.makeInitiatorContext(contextID: "futurePeer")
519 futurePeerContext.policyOverride = newPolicyDocument.version
520
521 let serverJoinExpectation = self.expectation(description: "futurePeer joins successfully")
522 self.fakeCuttlefishServer.joinListener = { joinRequest in
523 XCTAssertTrue(joinRequest.peer.hasStableInfoAndSig, "Joining peer should have a stable info")
524 let newStableInfo = joinRequest.peer.stableInfoAndSig.stableInfo()
525
526 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
527 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
528 serverJoinExpectation.fulfill()
529 return nil
530 }
531
532 let peer2ID = self.assertJoinViaEscrowRecovery(joiningContext: futurePeerContext, sponsor: self.cuttlefishContext)
533 self.wait(for: [serverJoinExpectation], timeout: 10)
534
535 // Now, tell our first peer about the new changes. It should trust the new peer, and update its policy
536 let updateTrustExpectation = self.expectation(description: "updateTrustExpectation successfully")
537 self.fakeCuttlefishServer.updateListener = { request in
538 let newStableInfo = request.stableInfoAndSig.stableInfo()
539
540 XCTAssertEqual(newStableInfo.frozenPolicyVersion, frozenPolicyVersion, "Policy version in peer should match frozen policy version")
541 XCTAssertEqual(newStableInfo.flexiblePolicyVersion, newPolicyDocument.version, "Prevailing policy version in peer should match new policy version")
542
543 let newDynamicInfo = request.dynamicInfoAndSig.dynamicInfo()
544 XCTAssert(newDynamicInfo.includedPeerIDs.contains(peer2ID), "Peer1 should trust peer2")
545
546 updateTrustExpectation.fulfill()
547 return nil
548 }
549
550 self.sendContainerChangeWaitForFetch(context: self.cuttlefishContext)
551 self.wait(for: [updateTrustExpectation], timeout: 10)
552 }
553 }
554
555 #endif