2 // TrustedPeersHelperUnitTests.swift
3 // TrustedPeersHelperUnitTests
5 // Created by Ben Williamson on 5/1/18.
11 let testDSID = "123456789"
13 let signingKey_384 = Data(base64Encoded: "BOQbPoiBnzuA0Cgc2QegjKGJqDtpkRenHwAxkYKJH1xELdaoIh8ifSch8sl18tpBYVUpEfdxz2ZSKif+dx7UPfu8WeTtpHkqm3M+9PTjr/KNNJCSR1PQNB5Jh+sRiQ+cpJnoTzm+IZSIukylamAcL3eA0nMUM0Zc2u4TijrbTgVND22WzSirUkwSK3mA/prk9A==")
15 let encryptionKey_384 = Data(base64Encoded: "BE1RuazBWmSEx0XVGhobbrdSE6fRQOrUrYEQnBkGl4zJq9GCeRoYvbuWNYFcOH0ijCRz9pYILsTn3ajT1OknlvcKmuQ7SeoGWzk9cBZzT5bBEwozn2gZxn80DQoDkmejywlH3D0/cuV6Bxexu5KMAFGqg6eN6th4sQABL5EuI9zKPuxHStM/b9B1LyqcnRKQHA==")
17 let symmetricKey_384 = Data(base64Encoded: "MfHje3Y/mWV0q+grjwZ4VxuqB7OreYHLxYkeeCiNjjY=")
19 let recovery_signingKey_384 = Data(base64Encoded: "BK5nrmP6oitJHtGV2Josk5cUKnG3pqxgEP8uzyPtNXgAMNHZoDKwCKFXpUzQSgbYiR4G2XZY2Q0+qSCKN7YSY2KNKE0hM9p4GvABBmAWKW/O9eFd5ugKQWisn25a/7nieIw8CQ81hBDR7R/vBpfLVtzE8ieRA8JPGqulQ5RdLcClFrD3B8BPJAZpLv4OP1CLDA==")
21 let recovery_encryptionKey_384 = Data(base64Encoded: "BKkZpYHTbMi2yrWFo+ErM3HbcYJCngPuWDYoVUD7egKkmiHFvv1Bsk0j/Dcj3xTR12vj5QOpZQV3GzE5estf75BV+EZz1cjUUSi/MysfpKsqEbwYrhIEkmeyMGr7CVWQWRLR2LnoihnQajvWi1LmO0AoDl3+LzVgTJBjjDQ5ANyw0Yv1EgOgBvZsLA9UTN4oAg==")
23 class TrustedPeersHelperUnitTests: XCTestCase {
27 var cuttlefish: FakeCuttlefishServer!
29 var manateeKeySet: CKKSKeychainBackedKeySet!
31 override static func setUp() {
34 SecTapToRadar.disableTTRsEntirely()
36 // Turn on NO_SERVER stuff
37 securityd_init_local_spi()
42 override func setUp() {
45 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
46 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
48 // Make a new fake keychain
49 tmpPath = String(format: "/tmp/%@-%X", testName, arc4random())
50 tmpURL = URL(fileURLWithPath: tmpPath, isDirectory: true)
52 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
53 SetCustomHomeURLString(tmpPath as CFString)
54 SecKeychainDbReset(nil)
56 XCTFail("setUp failed: \(error)")
59 // Actually load the database.
60 kc_with_dbt(true, nil) { _ in
64 // Now that the keychain is alive, perform test setup
66 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
68 XCTFail("Creation of fake key hierarchies failed: \(error)")
72 override func tearDown() {
73 // Put teardown code here. This method is called after the invocation of each test method in the class.
78 func makeFakeKeyHierarchy(zoneID: CKRecordZone.ID) throws -> CKKSKeychainBackedKeySet {
79 // Remember, these keys come into TPH having round-tripped through an NSEncoding
80 let tlk = try CKKSKeychainBackedKey.randomKeyWrapped(bySelf: zoneID)
81 let classA = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassA)
82 let classC = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassC)
84 XCTAssertNoThrow(try tlk.saveMaterialToKeychain(), "Should be able to save TLK to keychain")
85 XCTAssertNoThrow(try classA.saveMaterialToKeychain(), "Should be able to save classA key to keychain")
86 XCTAssertNoThrow(try classC.saveMaterialToKeychain(), "Should be able to save classC key to keychain")
88 let tlkData = try NSKeyedArchiver.archivedData(withRootObject: tlk, requiringSecureCoding: true)
89 let classAData = try NSKeyedArchiver.archivedData(withRootObject: classA, requiringSecureCoding: true)
90 let classCData = try NSKeyedArchiver.archivedData(withRootObject: classC, requiringSecureCoding: true)
92 let decodedTLK = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: tlkData) as! CKKSKeychainBackedKey
93 let decodedClassA = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classAData) as! CKKSKeychainBackedKey
94 let decodedClassC = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classCData) as! CKKSKeychainBackedKey
96 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
99 func assertTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
100 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
101 tlkShare.receiver == peerID &&
102 tlkShare.keyUuid == keyUUID
105 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
108 func assertNoTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
109 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
110 tlkShare.receiver == peerID &&
111 tlkShare.keyUuid == keyUUID
114 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
117 func assertTrusts(context: Container, peerIDs: [String]) {
118 let state = context.getStateSync(test: self)
119 guard let egoPeerID = state.egoPeerID else {
120 XCTFail("context should have an ego peer ID")
124 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
125 XCTFail("No dynamicInfo for ego peer")
130 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
131 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
135 func assertDistrusts(context: Container, peerIDs: [String]) {
136 let state = context.getStateSync(test: self)
137 guard let egoPeerID = state.egoPeerID else {
138 XCTFail("context should have an ego peer ID")
142 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
143 XCTFail("No dynamicInfo for ego peer")
148 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
149 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
153 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
154 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
155 return NSPersistentStoreDescription(url: tmpStoreURL)
158 func establish(reload: Bool,
159 store: NSPersistentStoreDescription) throws -> (Container, String) {
160 return try self.establish(reload: reload, contextID: OTDefaultContext, store: store)
163 func establish(reload: Bool,
165 allowedMachineIDs: Set<String> = Set(["aaa", "bbb", "ccc"]),
166 store: NSPersistentStoreDescription) throws -> (Container, String) {
167 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
169 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, listDifference: allowedMachineIDs.count > 0), "should be able to set allowed machine IDs")
171 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
173 let state = container.getStateSync(test: self)
174 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
175 let secret = container.loadSecretSync(test: self, label: peerID!)
176 XCTAssertNotNil(secret, "secret should not be nil")
177 XCTAssertNil(error, "error should be nil")
179 XCTAssertNotNil(peerID)
180 XCTAssertNotNil(permanentInfo)
181 XCTAssertNotNil(permanentInfoSig)
184 _ = container.dumpSync(test: self)
188 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
194 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
196 XCTAssertNotNil(peerID2)
198 _ = container.dumpSync(test: self)
200 return (container, peerID!)
203 func testEstablishWithReload() throws {
204 let description = tmpStoreDescription(name: "container.db")
205 let (_, peerID) = try establish(reload: true, store: description)
207 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
210 func testEstablishNoReload() throws {
211 let description = tmpStoreDescription(name: "container.db")
212 _ = try establish(reload: false, store: description)
215 func testEstablishNotOnAllowListErrors() throws {
216 let description = tmpStoreDescription(name: "container.db")
217 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
219 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
221 let state = container.getStateSync(test: self)
222 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
223 let secret = container.loadSecretSync(test: self, label: peerID!)
224 XCTAssertNotNil(secret, "secret should not be nil")
225 XCTAssertNil(error, "error should be nil")
227 XCTAssertNotNil(peerID)
228 XCTAssertNotNil(permanentInfo)
229 XCTAssertNotNil(permanentInfoSig)
232 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
234 // Now set up a machine ID list that positively does not have our peer
235 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
237 let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
238 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
239 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
242 func joinByVoucher(sponsor: Container,
245 machineIDs: Set<String>,
246 store: NSPersistentStoreDescription) throws -> (Container, String) {
247 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
248 persistentStoreDescription: store,
249 cuttlefish: cuttlefish)
251 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, listDifference: machineIDs.count > 0), "Should be able to set machine IDs")
253 print("preparing \(containerID)")
254 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) =
255 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
257 XCTAssertNotNil(peerID)
258 XCTAssertNotNil(permanentInfo)
259 XCTAssertNotNil(permanentInfoSig)
260 XCTAssertNotNil(stableInfo)
261 XCTAssertNotNil(stableInfoSig)
264 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
266 print("\(sponsor) vouches for \(containerID)")
267 let (voucherData, voucherSig, vouchError) =
268 sponsor.vouchSync(test: self,
270 permanentInfo: permanentInfo!,
271 permanentInfoSig: permanentInfoSig!,
272 stableInfo: stableInfo!,
273 stableInfoSig: stableInfoSig!,
274 ckksKeys: [self.manateeKeySet])
275 XCTAssertNil(vouchError)
276 XCTAssertNotNil(voucherData)
277 XCTAssertNotNil(voucherSig)
279 // As part of the join, the sponsor should have uploaded a tlk share
280 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
282 print("\(containerID) joins")
283 let (joinedPeerID, _, joinError) = c.joinSync(test: self,
284 voucherData: voucherData!,
285 voucherSig: voucherSig!,
288 XCTAssertNil(joinError)
289 XCTAssertEqual(joinedPeerID, peerID!)
295 func testJoin() throws {
296 let description = tmpStoreDescription(name: "container.db")
297 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
298 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
299 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
301 let machineIDs = Set(["aaa", "bbb", "ccc"])
302 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
303 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
304 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
307 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
308 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
310 let state = containerA.getStateSync(test: self)
311 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
312 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
313 XCTAssertNotNil(secret, "secret should not be nil")
314 XCTAssertNil(error, "error should be nil")
317 XCTAssertNotNil(aPeerID)
318 XCTAssertNotNil(aPermanentInfo)
319 XCTAssertNotNil(aPermanentInfoSig)
321 print("establishing A")
323 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
325 XCTAssertNotNil(peerID)
329 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
330 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
332 let state = containerB.getStateSync(test: self)
333 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
334 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
335 XCTAssertNotNil(secret, "secret should not be nil")
336 XCTAssertNil(error, "error should be nil")
339 XCTAssertNotNil(bPeerID)
340 XCTAssertNotNil(bPermanentInfo)
341 XCTAssertNotNil(bPermanentInfoSig)
344 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
345 print("A vouches for B, but doesn't provide any TLKShares")
346 let (_, _, errorVouchingWithoutTLKs) =
347 containerA.vouchSync(test: self,
349 permanentInfo: bPermanentInfo!,
350 permanentInfoSig: bPermanentInfoSig!,
351 stableInfo: bStableInfo!,
352 stableInfoSig: bStableInfoSig!,
354 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
355 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
357 print("A vouches for B, but doesn't only has provisional TLKs at the time")
358 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
359 provisionalManateeKeySet.newUpload = true
361 let (_, _, errorVouchingWithProvisionalTLKs) =
362 containerA.vouchSync(test: self,
364 permanentInfo: bPermanentInfo!,
365 permanentInfoSig: bPermanentInfoSig!,
366 stableInfo: bStableInfo!,
367 stableInfoSig: bStableInfoSig!,
368 ckksKeys: [provisionalManateeKeySet])
369 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
370 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
372 print("A vouches for B")
373 let (voucherData, voucherSig, error3) =
374 containerA.vouchSync(test: self,
376 permanentInfo: bPermanentInfo!,
377 permanentInfoSig: bPermanentInfoSig!,
378 stableInfo: bStableInfo!,
379 stableInfoSig: bStableInfoSig!,
380 ckksKeys: [self.manateeKeySet])
382 XCTAssertNotNil(voucherData)
383 XCTAssertNotNil(voucherSig)
385 // As part of the vouch, A should have uploaded a tlkshare for B
386 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
389 let (peerID, _, error) = containerB.joinSync(test: self,
390 voucherData: voucherData!,
391 voucherSig: voucherSig!,
395 XCTAssertEqual(peerID, bPeerID!)
398 _ = containerA.dumpSync(test: self)
399 _ = containerB.dumpSync(test: self)
400 _ = containerC.dumpSync(test: self)
403 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, error4) =
404 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
406 let state = containerC.getStateSync(test: self)
407 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
408 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
409 XCTAssertNotNil(secret, "secret should not be nil")
410 XCTAssertNil(error, "error should be nil")
413 XCTAssertNotNil(cPeerID)
414 XCTAssertNotNil(cPermanentInfo)
415 XCTAssertNotNil(cPermanentInfoSig)
418 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
419 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
420 provisionalEngramKeySet.newUpload = true
422 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
423 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
425 print("B vouches for C")
426 let (voucherData, voucherSig, error) =
427 containerB.vouchSync(test: self,
429 permanentInfo: cPermanentInfo!,
430 permanentInfoSig: cPermanentInfoSig!,
431 stableInfo: cStableInfo!,
432 stableInfoSig: cStableInfoSig!,
433 ckksKeys: [self.manateeKeySet])
435 XCTAssertNotNil(voucherData)
436 XCTAssertNotNil(voucherSig)
438 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
441 let (peerID, _, error2) = containerC.joinSync(test: self,
442 voucherData: voucherData!,
443 voucherSig: voucherSig!,
444 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
447 XCTAssertEqual(peerID, cPeerID!)
449 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
450 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
451 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
456 let (_, error) = containerA.updateSync(test: self)
461 let state = containerA.getStateSync(test: self)
462 let a = state.peers[aPeerID!]!
463 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
466 _ = containerA.dumpSync(test: self)
467 _ = containerB.dumpSync(test: self)
468 _ = containerC.dumpSync(test: self)
471 func testJoinWithoutAllowListErrors() throws {
472 let description = tmpStoreDescription(name: "container.db")
473 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
474 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
476 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
478 let state = containerA.getStateSync(test: self)
479 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
480 let secret = containerA.loadSecretSync(test: self, label: peerID!)
481 XCTAssertNotNil(secret, "secret should not be nil")
482 XCTAssertNil(error, "error should be nil")
484 XCTAssertNil(error, "Should not have an error after preparing A")
485 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
486 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
487 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
489 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
491 let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
492 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
493 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
496 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, errorPrepareB) =
497 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
499 let state = containerA.getStateSync(test: self)
500 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
501 let secret = containerA.loadSecretSync(test: self, label: peerID!)
502 XCTAssertNotNil(secret, "secret should not be nil")
503 XCTAssertNil(error, "error should be nil")
505 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
506 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
507 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
508 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
510 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs on container B")
513 print("A vouches for B")
514 let (voucherData, voucherSig, error3) =
515 containerA.vouchSync(test: self,
517 permanentInfo: bPermanentInfo!,
518 permanentInfoSig: bPermanentInfoSig!,
519 stableInfo: bStableInfo!,
520 stableInfoSig: bStableInfoSig!,
522 XCTAssertNil(error3, "Should be no error vouching for B")
523 XCTAssertNotNil(voucherData, "Should have a voucher from A")
524 XCTAssertNotNil(voucherSig, "Should have a signature from A")
527 let (peerID, _, error) = containerB.joinSync(test: self,
528 voucherData: voucherData!,
529 voucherSig: voucherSig!,
532 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
533 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
537 func testReset() throws {
538 let description = tmpStoreDescription(name: "container.db")
539 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
541 let machineIDs = Set(["aaa"])
542 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
545 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
547 let state = containerA.getStateSync(test: self)
548 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
549 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
550 XCTAssertNotNil(secret, "secret should not be nil")
551 XCTAssertNil(error, "error should be nil")
554 XCTAssertNotNil(aPeerID)
555 XCTAssertNotNil(aPermanentInfo)
556 XCTAssertNotNil(aPermanentInfoSig)
558 print("establishing A")
560 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
561 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
563 XCTAssertNotNil(peerID)
564 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
569 let error = containerA.resetSync(resetReason: .testGenerated, test: self)
573 let (dict, error) = containerA.dumpSync(test: self)
575 XCTAssertNotNil(dict)
576 let peers: Array<Any> = dict!["peers"] as! Array<Any>
577 XCTAssertEqual(0, peers.count)
581 func testResetLocal() throws {
582 let description = tmpStoreDescription(name: "container.db")
583 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
585 let machineIDs = Set(["aaa"])
586 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
588 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
590 let state = containerA.getStateSync(test: self)
591 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
592 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
593 XCTAssertNotNil(secret, "secret should not be nil")
594 XCTAssertNil(error, "error should be nil")
596 XCTAssertNil(error, "Should be no error preparing an identity")
597 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
598 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
599 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
602 let (dict, error) = containerA.dumpSync(test: self)
603 XCTAssertNil(error, "Should be no error dumping")
604 XCTAssertNotNil(dict, "Should receive a dump dictionary")
606 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
607 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
609 let selfPeer: String? = selfInfo!["peerID"] as! String?
610 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
614 let error = containerA.localResetSync(test: self)
615 XCTAssertNil(error, "local-reset shouldn't error")
618 let (dict, error) = containerA.dumpSync(test: self)
620 XCTAssertNil(error, "Should be no error dumping")
621 XCTAssertNotNil(dict, "Should receive a dump dictionary")
623 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
624 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
626 let selfPeer: String? = selfInfo!["peerID"] as! String?
627 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
631 func testReplayAttack() throws {
632 let description = tmpStoreDescription(name: "container.db")
633 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
634 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
635 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
637 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
638 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
639 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
642 let (peerID, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
644 let state = containerA.getStateSync(test: self)
645 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
646 let secret = containerA.loadSecretSync(test: self, label: peerID!)
647 XCTAssertNotNil(secret, "secret should not be nil")
649 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
651 let state = containerB.getStateSync(test: self)
652 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
653 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
654 XCTAssertNotNil(secret, "secret should not be nil")
656 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
658 let state = containerC.getStateSync(test: self)
659 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
660 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
661 XCTAssertNotNil(secret, "secret should not be nil")
663 print("establishing A")
664 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
667 print("A vouches for B")
668 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
670 permanentInfo: bPermanentInfo!,
671 permanentInfoSig: bPermanentInfoSig!,
672 stableInfo: bStableInfo!,
673 stableInfoSig: bStableInfoSig!,
677 _ = containerB.joinSync(test: self,
678 voucherData: voucherData!,
679 voucherSig: voucherSig!,
685 _ = containerA.updateSync(test: self)
686 let earlyClock: TPCounter
688 let state = containerA.getStateSync(test: self)
689 let b = state.peers[bPeerID!]!
690 earlyClock = b.dynamicInfo!.clock
694 let snapshot = cuttlefish.state
697 print("B vouches for C")
698 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
699 permanentInfo: cPermanentInfo!,
700 permanentInfoSig: cPermanentInfoSig!,
701 stableInfo: cStableInfo!,
702 stableInfoSig: cStableInfoSig!,
706 _ = containerC.joinSync(test: self,
707 voucherData: voucherData!,
708 voucherSig: voucherSig!,
714 _ = containerB.updateSync(test: self)
717 _ = containerA.updateSync(test: self)
718 let lateClock: TPCounter
720 let state = containerA.getStateSync(test: self)
721 let b = state.peers[bPeerID!]!
722 lateClock = b.dynamicInfo!.clock
723 XCTAssertTrue(earlyClock < lateClock)
726 print("Reverting cuttlefish to the snapshot")
727 cuttlefish.state = snapshot
728 cuttlefish.makeSnapshot()
730 print("A updates, fetching the old snapshot from cuttlefish")
731 _ = containerA.updateSync(test: self)
733 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
734 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
736 let state = containerA.getStateSync(test: self)
737 let b = state.peers[bPeerID!]!
738 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
742 // TODO: need a real configurable mock cuttlefish
743 func testFetchPolicyDocuments() throws {
745 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
748 1: ("SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=",
749 "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
750 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
751 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2"),
752 3: ("SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=",
753 "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A="),
755 let (request1, data1) = policies[1]!
756 let (request3, data3) = policies[3]!
758 let description = tmpStoreDescription(name: "container.db")
759 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
762 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, keys: [:])
763 XCTAssertNil(error1, "No error querying for an empty list")
764 XCTAssertEqual(response1, [:], "Received empty dictionary")
767 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1])
768 XCTAssertNil(error2, "No error getting locally known policy document")
769 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
770 XCTAssertEqual(response2?[1]?[0], request1, "retrieved hash matches request hash")
771 XCTAssertEqual(response2?[1]?[1], data1, "retrieved data matches known data")
774 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1, 3: request3])
775 XCTAssertNil(error3, "No error fetching local + remote policy")
776 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
777 XCTAssertEqual(response3?[1]?[0], request1, "retrieved hash matches local request hash")
778 XCTAssertEqual(response3?[1]?[1], data1, "retrieved data matches local known data")
779 XCTAssertEqual(response3?[3]?[0], request3, "retrieved hash matches remote request hash")
780 XCTAssertEqual(response3?[3]?[1], data3, "retrieved data matches remote known data")
783 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash"])
784 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
785 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
788 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash",
791 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
792 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
795 func testEscrowKeys() throws {
797 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
798 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
800 let secretString = "i'm a secret!"
801 XCTAssertNotNil(secretString, "secretString should not be nil")
803 let secretData: Data? = secretString.data(using: .utf8)
804 XCTAssertNotNil(secretData, "secretData should not be nil")
806 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
807 XCTAssertNotNil(keys, "keys should not be nil")
809 XCTAssertNotNil(keys.secret, "secret should not be nil")
810 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
811 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
812 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
813 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
815 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
816 XCTAssertNotNil(hash, "hash should not be nil")
818 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
819 XCTAssertTrue(result, "result should be true")
821 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
822 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
824 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
825 XCTAssertNotNil(signingKey, "signingKey should not be nil")
826 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
827 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
830 func testEscrowKeyTestVectors() {
832 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
834 let secret = secretString.data(using: .utf8)
837 let testv1 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
838 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
840 let testv2 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
841 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
843 let testv3 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
844 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
846 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
847 let newSecret = newSecretString.data(using: .utf8)
849 let testv4 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
850 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
852 let testv5 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
853 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
855 let testv6 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
856 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
858 XCTFail("error testing escrow key test vectors \(error)")
862 func testRecoveryKeyTestVectors() {
863 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
865 let secret = secretString.data(using: .utf8)
868 let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID)
869 XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match")
871 let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID)
872 XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match")
874 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
875 let newSecret = newSecretString.data(using: .utf8)
877 let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID)
878 XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match")
880 let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: recoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID)
881 XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match")
883 XCTFail("error testing RecoveryKey test vectors \(error)")
887 func testJoiningWithBottle() throws {
888 var bottleA: BottleMO
890 let description = tmpStoreDescription(name: "container.db")
891 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
892 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
894 let machineIDs = Set(["aaa", "bbb"])
895 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
896 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
899 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
900 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
902 var state = containerA.getStateSync(test: self)
903 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
905 bottleA = state.bottles.removeFirst()
907 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
908 XCTAssertNotNil(secret, "secret should not be nil")
909 XCTAssertNil(error, "error should be nil")
911 XCTAssertNotNil(aPeerID)
912 XCTAssertNotNil(aPermanentInfo)
913 XCTAssertNotNil(aPermanentInfoSig)
915 print("establishing A")
917 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
918 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
920 XCTAssertNotNil(peerID)
921 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
924 let state = containerA.getStateSync(test: self)
925 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
926 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
928 XCTAssertNotNil(secret, "secret should not be nil")
929 XCTAssertNil(error, "error should be nil")
932 _ = containerB.updateSync(test: self)
935 let (bPeerID, _, _, _, _, error2) =
936 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
938 let state = containerB.getStateSync(test: self)
939 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
940 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
941 XCTAssertNotNil(secret, "secret should not be nil")
942 XCTAssertNil(error, "error should be nil")
947 print("B prepares to join via bottle")
949 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
950 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
951 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
953 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
956 XCTAssertNotNil(voucherData)
957 XCTAssertNotNil(voucherSig)
959 // Before B joins, there should be no TLKShares for B
960 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
963 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
965 XCTAssertEqual(peerID, bPeerID!)
967 // But afterward, it has one!
968 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
972 func testJoiningWithBottleAndEmptyBottleSalt() throws {
973 var bottleA: BottleMO
975 let description = tmpStoreDescription(name: "container.db")
976 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
977 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
979 let machineIDs = Set(["aaa", "bbb"])
980 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
981 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
984 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
985 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
987 var state = containerA.getStateSync(test: self)
988 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
990 bottleA = state.bottles.removeFirst()
992 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
993 XCTAssertNotNil(secret, "secret should not be nil")
994 XCTAssertNil(error, "error should be nil")
996 XCTAssertNotNil(aPeerID)
997 XCTAssertNotNil(aPermanentInfo)
998 XCTAssertNotNil(aPermanentInfoSig)
1000 print("establishing A")
1002 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1003 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1005 XCTAssertNotNil(peerID)
1006 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1009 let state = containerA.getStateSync(test: self)
1010 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1011 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1013 XCTAssertNotNil(secret, "secret should not be nil")
1014 XCTAssertNil(error, "error should be nil")
1017 _ = containerB.updateSync(test: self)
1019 print("preparing B")
1020 let (bPeerID, _, _, _, _, error2) =
1021 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1023 let state = containerB.getStateSync(test: self)
1024 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1025 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1026 XCTAssertNotNil(secret, "secret should not be nil")
1027 XCTAssertNil(error, "error should be nil")
1029 XCTAssertNil(error2)
1032 print("B prepares to join via bottle")
1034 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1035 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1036 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1038 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1040 XCTAssertNil(error3)
1041 XCTAssertNotNil(voucherData)
1042 XCTAssertNotNil(voucherSig)
1044 // Before B joins, there should be no TLKShares for B
1045 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1048 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1050 XCTAssertEqual(peerID, bPeerID!)
1052 // But afterward, it has one!
1053 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1057 func testJoiningWithWrongEscrowRecordForBottle() throws {
1059 let description = tmpStoreDescription(name: "container.db")
1060 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1061 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1063 let machineIDs = Set(["aaa", "bbb"])
1064 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1065 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1067 print("preparing A")
1068 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1069 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1071 let state = containerA.getStateSync(test: self)
1072 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1073 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1074 XCTAssertNotNil(secret, "secret should not be nil")
1075 XCTAssertNil(error, "error should be nil")
1078 XCTAssertNotNil(aPeerID)
1079 XCTAssertNotNil(aPermanentInfo)
1080 XCTAssertNotNil(aPermanentInfoSig)
1082 print("establishing A")
1084 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1086 XCTAssertNotNil(peerID)
1089 let state = containerA.getStateSync(test: self)
1090 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1091 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1093 XCTAssertNotNil(secret, "secret should not be nil")
1094 XCTAssertNil(error, "error should be nil")
1097 _ = containerB.updateSync(test: self)
1099 print("preparing B")
1100 let (bPeerID, _, _, _, _, error2) =
1101 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1103 let state = containerB.getStateSync(test: self)
1104 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1105 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1106 XCTAssertNotNil(secret, "secret should not be nil")
1107 XCTAssertNil(error, "error should be nil")
1109 XCTAssertNil(error2)
1112 print("B joins via bottle")
1114 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record")
1115 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't exist")
1116 XCTAssertNil(bottlePeerID, "peerID should be nil for no bottle")
1118 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1120 XCTAssertNotNil(error3)
1121 XCTAssertNil(voucherData)
1122 XCTAssertNil(voucherSig)
1126 func testJoiningWithWrongBottle() throws {
1127 var bottleB: BottleMO
1129 let description = tmpStoreDescription(name: "container.db")
1130 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1131 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1133 let machineIDs = Set(["aaa", "bbb"])
1134 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1135 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1137 print("preparing A")
1138 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1139 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1141 let state = containerA.getStateSync(test: self)
1142 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1143 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1144 XCTAssertNotNil(secret, "secret should not be nil")
1145 XCTAssertNil(error, "error should be nil")
1148 XCTAssertNotNil(aPeerID)
1149 XCTAssertNotNil(aPermanentInfo)
1150 XCTAssertNotNil(aPermanentInfoSig)
1152 print("establishing A")
1154 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1156 XCTAssertNotNil(peerID)
1159 let state = containerA.getStateSync(test: self)
1160 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1161 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1163 XCTAssertNotNil(secret, "secret should not be nil")
1164 XCTAssertNil(error, "error should be nil")
1167 print("preparing B")
1168 let (bPeerID, _, _, _, _, error2) =
1169 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1171 var state = containerB.getStateSync(test: self)
1172 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1173 bottleB = state.bottles.removeFirst()
1174 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1175 XCTAssertNotNil(secret, "secret should not be nil")
1176 XCTAssertNil(error, "error should be nil")
1178 XCTAssertNil(error2)
1181 print("B joins via bottle")
1183 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!)
1184 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1185 XCTAssertEqual(bottlePeerID, bPeerID, "Bottle should be for peer B")
1187 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1189 XCTAssertNotNil(error3)
1190 XCTAssertNil(voucherData)
1191 XCTAssertNil(voucherSig)
1195 func testJoiningWithBottleAndBadSalt() throws {
1196 var bottleA: BottleMO
1198 let description = tmpStoreDescription(name: "container.db")
1199 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1200 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1202 let machineIDs = Set(["aaa", "bbb"])
1203 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1204 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1206 print("preparing A")
1207 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1208 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1210 var state = containerA.getStateSync(test: self)
1211 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1212 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1213 bottleA = state.bottles.removeFirst()
1214 XCTAssertNotNil(secret, "secret should not be nil")
1215 XCTAssertNil(error, "error should be nil")
1218 XCTAssertNotNil(aPeerID)
1219 XCTAssertNotNil(aPermanentInfo)
1220 XCTAssertNotNil(aPermanentInfoSig)
1222 print("establishing A")
1224 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1226 XCTAssertNotNil(peerID)
1229 let state = containerA.getStateSync(test: self)
1230 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1231 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1233 XCTAssertNotNil(secret, "secret should not be nil")
1234 XCTAssertNil(error, "error should be nil")
1237 _ = containerB.updateSync(test: self)
1239 print("preparing B")
1240 let (bPeerID, _, _, _, _, error2) =
1241 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1243 let state = containerB.getStateSync(test: self)
1244 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1245 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1246 XCTAssertNotNil(secret, "secret should not be nil")
1247 XCTAssertNil(error, "error should be nil")
1249 XCTAssertNil(error2)
1252 print("B joins via bottle")
1254 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1255 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1256 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1258 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1260 XCTAssertNotNil(error3)
1261 XCTAssertNil(voucherData)
1262 XCTAssertNil(voucherSig)
1266 func testJoiningWithBottleAndBadSecret() throws {
1267 var bottleA: BottleMO
1268 let description = tmpStoreDescription(name: "container.db")
1269 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1270 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1272 let machineIDs = Set(["aaa", "bbb"])
1273 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1274 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1276 print("preparing A")
1277 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1278 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1280 var state = containerA.getStateSync(test: self)
1281 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1282 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1283 bottleA = state.bottles.removeFirst()
1284 XCTAssertNotNil(secret, "secret should not be nil")
1285 XCTAssertNil(error, "error should be nil")
1288 XCTAssertNotNil(aPeerID)
1289 XCTAssertNotNil(aPermanentInfo)
1290 XCTAssertNotNil(aPermanentInfoSig)
1292 print("establishing A")
1294 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1296 XCTAssertNotNil(peerID)
1299 let state = containerA.getStateSync(test: self)
1300 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1301 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1302 XCTAssertNotNil(secret, "secret should not be nil")
1303 XCTAssertNil(error, "error should be nil")
1306 _ = containerB.updateSync(test: self)
1308 print("preparing B")
1309 let (bPeerID, _, _, _, _, error2) =
1310 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1312 let state = containerB.getStateSync(test: self)
1313 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1314 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1315 XCTAssertNotNil(secret, "secret should not be nil")
1316 XCTAssertNil(error, "error should be nil")
1318 XCTAssertNil(error2)
1321 print("B joins via bottle")
1323 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1324 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1325 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1327 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1329 XCTAssertNotNil(error3)
1330 XCTAssertNil(voucherData)
1331 XCTAssertNil(voucherSig)
1335 func testJoiningWithNoFetchAllBottles() throws {
1336 var bottleA: BottleMO
1338 let description = tmpStoreDescription(name: "container.db")
1339 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1340 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1342 let machineIDs = Set(["aaa", "bbb"])
1343 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1344 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1346 print("preparing A")
1347 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1348 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1350 var state = containerA.getStateSync(test: self)
1351 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1352 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1354 bottleA = state.bottles.removeFirst()
1355 XCTAssertNotNil(secret, "secret should not be nil")
1356 XCTAssertNil(error, "error should be nil")
1359 XCTAssertNotNil(aPeerID)
1360 XCTAssertNotNil(aPermanentInfo)
1361 XCTAssertNotNil(aPermanentInfoSig)
1363 print("establishing A")
1365 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1367 XCTAssertNotNil(peerID)
1370 let state = containerA.getStateSync(test: self)
1371 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1372 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1373 XCTAssertNotNil(secret, "secret should not be nil")
1374 XCTAssertNil(error, "error should be nil")
1377 print("preparing B")
1378 let (bPeerID, _, _, _, _, error2) =
1379 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1381 let state = containerB.getStateSync(test: self)
1382 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1383 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1384 XCTAssertNotNil(secret, "secret should not be nil")
1385 XCTAssertNil(error, "error should be nil")
1387 XCTAssertNil(error2)
1390 print("B joins via bottle")
1392 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1394 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1395 XCTAssertNotNil(errorPreflight, "Should be an error preflighting a vouch with bottle with a fetch error")
1396 XCTAssertNil(bottlePeerID, "peerID should be nil")
1398 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1400 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1402 XCTAssertNotNil(error3)
1403 XCTAssertNil(voucherData)
1404 XCTAssertNil(voucherSig)
1408 func testJoinByPreapproval() throws {
1409 let description = tmpStoreDescription(name: "container.db")
1410 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1411 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1413 let machineIDs = Set(["aaa", "bbb"])
1414 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1415 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1417 print("preparing A")
1418 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1419 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1421 XCTAssertNotNil(aPeerID)
1422 XCTAssertNotNil(aPermanentInfo)
1423 XCTAssertNotNil(aPermanentInfoSig)
1425 print("preparing B")
1426 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, error2) =
1427 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1429 let state = containerB.getStateSync(test: self)
1430 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1431 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1432 XCTAssertNotNil(secret, "secret should not be nil")
1433 XCTAssertNil(error, "error should be nil")
1435 XCTAssertNil(error2)
1436 XCTAssertNotNil(bPeerID)
1437 XCTAssertNotNil(bPermanentInfo)
1438 XCTAssertNotNil(bPermanentInfoSig)
1440 // Now, A establishes preapproving B
1441 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1443 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1444 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1446 print("establishing A")
1448 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
1450 XCTAssertNotNil(peerID)
1454 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1456 print("B joins by preapproval, and uploads all TLKShares that it has")
1457 let (bJoinedPeerID, _, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
1458 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1459 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1461 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1464 _ = containerA.dumpSync(test: self)
1465 _ = containerB.dumpSync(test: self)
1468 func testDepart() throws {
1469 let description = tmpStoreDescription(name: "container.db")
1470 let (container, peerID) = try establish(reload: false, store: description)
1472 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1473 assertDistrusts(context: container, peerIDs: [peerID])
1476 func testDistrustPeers() throws {
1477 let store = tmpStoreDescription(name: "container.db")
1478 let (c, peerID1) = try establish(reload: false, store: store)
1480 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1481 containerID: "second",
1483 machineIDs: ["aaa", "bbb", "ccc"],
1486 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1487 containerID: "third",
1489 machineIDs: ["aaa", "bbb", "ccc"],
1492 let (_, cUpdateError) = c.updateSync(test: self)
1493 XCTAssertNil(cUpdateError, "Should be able to update first container")
1494 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1496 // You can't distrust yourself via peerID.
1497 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1498 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1500 // Passing in nonsense should error too
1501 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1503 // Now, distrust both peers.
1504 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1505 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1507 // peers should accept their fates
1508 let (_, c2UpdateError) = c2.updateSync(test: self)
1509 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1510 assertDistrusts(context: c2, peerIDs: [peerID2])
1512 let (_, c3UpdateError) = c3.updateSync(test: self)
1513 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1514 assertDistrusts(context: c3, peerIDs: [peerID3])
1517 func testFetchWithBadChangeToken() throws {
1518 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1520 // But all that goes away, and a new peer establishes
1521 self.cuttlefish.state = FakeCuttlefishServer.State()
1522 let (_, peerID2) = try establish(reload: false, contextID: "second", store: tmpStoreDescription(name: "container-peer2.db"))
1524 // And the first container fetches again, which should succeed
1525 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1526 let (_, updateError) = c.updateSync(test: self)
1527 XCTAssertNil(updateError, "Update should have succeeded")
1529 // and c's model should only include peerID2
1530 c.moc.performAndWait {
1531 let modelPeers = c.model.allPeerIDs()
1532 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1533 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1534 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1538 func testFetchEscrowContents() throws {
1539 let description = tmpStoreDescription(name: "container.db")
1540 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1541 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1542 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1543 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1544 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1545 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1546 XCTAssertNil(spkiA, "Should not have an SPKI")
1548 let (c, peerID) = try establish(reload: false, store: description)
1549 XCTAssertNotNil(peerID, "establish should return a peer id")
1551 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1552 XCTAssertNil(error, "Should be no error fetching escrow contents")
1553 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1554 XCTAssertNotNil(bottleID, "Should have a bottleID")
1555 XCTAssertNotNil(spki, "Should have an SPKI")
1558 func testBottles() {
1560 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1561 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1562 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1564 let keys = bottle.escrowKeys
1565 XCTAssertNotNil(keys, "keys should not be nil")
1567 XCTAssertNotNil(bottle, "bottle should not be nil")
1569 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1571 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1572 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1574 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1576 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1577 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1579 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1580 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1582 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1583 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1585 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1588 XCTFail("error testing bottles \(error)")
1592 func testFetchBottles() throws {
1593 let store = tmpStoreDescription(name: "container.db")
1594 let (c, _) = try establish(reload: false, store: store)
1596 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1597 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1598 XCTAssertNotNil(bottles, "should have fetched some bottles")
1599 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1602 let state = c.getStateSync(test: self)
1603 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1606 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1608 let state = c2.getStateSync(test: self)
1609 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1612 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1613 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1614 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1615 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1618 let state = c2.getStateSync(test: self)
1619 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1623 func testTrustStatus() throws {
1624 let store = tmpStoreDescription(name: "container.db")
1626 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
1627 persistentStoreDescription: store,
1628 cuttlefish: self.cuttlefish)
1629 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
1630 XCTAssertNil(precStatusError, "No error fetching status")
1631 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
1632 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
1633 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
1634 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
1636 let (c, _) = try establish(reload: false, store: store)
1637 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1638 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1639 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1640 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1641 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1642 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1644 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
1645 persistentStoreDescription: store,
1646 cuttlefish: self.cuttlefish)
1648 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
1649 XCTAssertNil(statusError, "No error fetching status")
1650 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
1651 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
1652 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1653 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
1656 func testTrustStatusWhenMissingIdentityKeys() throws {
1657 let store = tmpStoreDescription(name: "container.db")
1658 let (c, _) = try establish(reload: false, store: store)
1659 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1660 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1661 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1662 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1663 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1664 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1666 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
1667 XCTAssertTrue(result, "result should be true")
1669 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
1670 XCTAssertNotNil(distrustedError, "error should not be nil")
1671 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
1672 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
1675 func testSetRecoveryKey() throws {
1676 let store = tmpStoreDescription(name: "container.db")
1678 let c = try Container(name: ContainerName(container: "c", context: "context"),
1679 persistentStoreDescription: store,
1680 cuttlefish: self.cuttlefish)
1682 let machineIDs = Set(["aaa", "bbb", "ccc"])
1683 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1685 print("preparing peer A")
1686 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1687 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1689 let state = c.getStateSync(test: self)
1690 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1691 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1692 XCTAssertNotNil(secret, "secret should not be nil")
1693 XCTAssertNil(error, "error should be nil")
1696 XCTAssertNotNil(aPeerID)
1697 XCTAssertNotNil(aPermanentInfo)
1698 XCTAssertNotNil(aPermanentInfoSig)
1700 print("establishing A")
1702 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1704 XCTAssertNotNil(peerID)
1706 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1707 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1709 let (setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1710 XCTAssertNil(setRecoveryError, "error should be nil")
1713 func roundTripThroughSetValueTransformer(set: Set<String>) {
1714 let t = SetValueTransformer()
1716 let transformedSet = t.transformedValue(set) as? Data
1717 XCTAssertNotNil(transformedSet, "SVT should return some data")
1719 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
1720 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
1722 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
1725 func testSetValueTransformer() {
1726 roundTripThroughSetValueTransformer(set: Set<String>([]))
1727 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
1728 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
1731 func testGetRepairSuggestion() throws {
1732 let store = tmpStoreDescription(name: "container.db")
1734 let c = try Container(name: ContainerName(container: "c", context: "context"),
1735 persistentStoreDescription: store,
1736 cuttlefish: self.cuttlefish)
1738 let machineIDs = Set(["aaa", "bbb", "ccc"])
1739 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1741 print("preparing peer A")
1742 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1743 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1745 let state = c.getStateSync(test: self)
1746 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1747 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1748 XCTAssertNotNil(secret, "secret should not be nil")
1749 XCTAssertNil(error, "error should be nil")
1752 XCTAssertNotNil(aPeerID)
1753 XCTAssertNotNil(aPermanentInfo)
1754 XCTAssertNotNil(aPermanentInfoSig)
1756 print("establishing A")
1758 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1760 XCTAssertNotNil(peerID)
1762 let (repairAccount, repairEscrow, resetOctagon, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
1763 XCTAssertEqual(repairAccount, false, "")
1764 XCTAssertEqual(repairEscrow, false, "")
1765 XCTAssertEqual(resetOctagon, false, "")
1766 XCTAssertNil(healthError)
1769 func testFetchChangesFailDuringVouchWithBottle() throws {
1770 var bottleA: BottleMO
1772 let description = tmpStoreDescription(name: "container.db")
1773 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1774 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1776 let machineIDs = Set(["aaa", "bbb"])
1777 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1778 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1780 print("preparing A")
1781 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1782 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1784 var state = containerA.getStateSync(test: self)
1785 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1787 bottleA = state.bottles.removeFirst()
1789 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1790 XCTAssertNotNil(secret, "secret should not be nil")
1791 XCTAssertNil(error, "error should be nil")
1793 XCTAssertNotNil(aPeerID)
1794 XCTAssertNotNil(aPermanentInfo)
1795 XCTAssertNotNil(aPermanentInfoSig)
1797 print("establishing A")
1799 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1800 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1802 XCTAssertNotNil(peerID)
1803 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1806 let state = containerA.getStateSync(test: self)
1807 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1808 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1810 XCTAssertNotNil(secret, "secret should not be nil")
1811 XCTAssertNil(error, "error should be nil")
1814 _ = containerB.updateSync(test: self)
1816 print("preparing B")
1817 let (bPeerID, _, _, _, _, error2) =
1818 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1820 let state = containerB.getStateSync(test: self)
1821 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1822 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1823 XCTAssertNotNil(secret, "secret should not be nil")
1824 XCTAssertNil(error, "error should be nil")
1826 XCTAssertNil(error2)
1829 print("B prepares to join via bottle")
1831 let (bottlePeerID, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1832 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1833 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1835 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1837 XCTAssertNil(error3)
1838 XCTAssertNotNil(voucherData)
1839 XCTAssertNotNil(voucherSig)
1841 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1843 // Before B joins, there should be no TLKShares for B
1844 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1847 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1848 XCTAssertNotNil(error)
1849 XCTAssertNil(peerID)
1853 func testDistrustedPeerRecoveryKeyNotSet() throws {
1854 let description = tmpStoreDescription(name: "container.db")
1855 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1856 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1858 let machineIDs = Set(["aaa", "bbb"])
1859 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1860 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1862 print("preparing peer A")
1863 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, error) =
1864 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1866 let state = containerA.getStateSync(test: self)
1867 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1868 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1869 XCTAssertNotNil(secret, "secret should not be nil")
1870 XCTAssertNil(error, "error should be nil")
1873 XCTAssertNotNil(aPeerID)
1874 XCTAssertNotNil(aPermanentInfo)
1875 XCTAssertNotNil(aPermanentInfoSig)
1876 XCTAssertNotNil(aStableInfo)
1877 XCTAssertNotNil(aStableInfoSig)
1879 print("establishing A")
1881 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1883 XCTAssertNotNil(peerID)
1886 print("preparing B")
1887 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
1888 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1890 let state = containerB.getStateSync(test: self)
1891 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1892 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1893 XCTAssertNotNil(secret, "secret should not be nil")
1894 XCTAssertNil(error, "error should be nil")
1896 XCTAssertNil(error2)
1897 XCTAssertNotNil(bPeerID)
1898 XCTAssertNotNil(bPermanentInfo)
1899 XCTAssertNotNil(bPermanentInfoSig)
1902 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1903 print("A vouches for B, but doesn't provide any TLKShares")
1904 let (_, _, errorVouchingWithoutTLKs) =
1905 containerA.vouchSync(test: self,
1907 permanentInfo: bPermanentInfo!,
1908 permanentInfoSig: bPermanentInfoSig!,
1909 stableInfo: bStableInfo!,
1910 stableInfoSig: bStableInfoSig!,
1912 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
1913 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1915 print("A vouches for B, but doesn't only has provisional TLKs at the time")
1916 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1917 provisionalManateeKeySet.newUpload = true
1919 let (_, _, errorVouchingWithProvisionalTLKs) =
1920 containerA.vouchSync(test: self,
1922 permanentInfo: bPermanentInfo!,
1923 permanentInfoSig: bPermanentInfoSig!,
1924 stableInfo: bStableInfo!,
1925 stableInfoSig: bStableInfoSig!,
1926 ckksKeys: [provisionalManateeKeySet])
1927 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
1928 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1930 print("A vouches for B")
1931 let (voucherData, voucherSig, error3) =
1932 containerA.vouchSync(test: self,
1934 permanentInfo: bPermanentInfo!,
1935 permanentInfoSig: bPermanentInfoSig!,
1936 stableInfo: bStableInfo!,
1937 stableInfoSig: bStableInfoSig!,
1938 ckksKeys: [self.manateeKeySet])
1939 XCTAssertNil(error3)
1940 XCTAssertNotNil(voucherData)
1941 XCTAssertNotNil(voucherSig)
1943 // As part of the vouch, A should have uploaded a tlkshare for B
1944 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1947 let (peerID, _, error) = containerB.joinSync(test: self,
1948 voucherData: voucherData!,
1949 voucherSig: voucherSig!,
1953 XCTAssertEqual(peerID, bPeerID!)
1958 let (_, error) = containerA.updateSync(test: self)
1963 let (_, error) = containerB.updateSync(test: self)
1967 // Now, A distrusts B.
1968 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
1969 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
1971 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1972 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1974 let (setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1975 XCTAssertNil(setRecoveryError, "error should be nil")
1979 let (_, error) = containerA.updateSync(test: self)
1984 let (_, error) = containerB.updateSync(test: self)
1989 let (dict, error) = containerA.dumpSync(test: self)
1991 XCTAssertNil(error, "Should be no error dumping")
1992 XCTAssertNotNil(dict, "Should receive a dump dictionary")
1994 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
1995 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
1997 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
1998 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
2000 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
2001 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
2003 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
2004 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
2009 func assert(container: Container,
2010 allowedMachineIDs: Set<String>,
2011 disallowedMachineIDs: Set<String>,
2012 unknownMachineIDs: Set<String> = Set(),
2013 persistentStore: NSPersistentStoreDescription,
2014 cuttlefish: FakeCuttlefishServer) throws {
2016 let midList = container.onqueueCurrentMIDList()
2017 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
2018 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
2019 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
2022 let (fetchedAllowList, fetchErr) = container.fetchAllowedMachineIDsSync(test: self)
2023 XCTAssertNil(fetchErr, "Should be no error fetching the allowed list")
2024 XCTAssertEqual(fetchedAllowList, allowedMachineIDs, "A fetched list of allowed machine IDs should match the loaded list")
2026 // if we reload the container, does it still match?
2027 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
2029 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
2030 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
2031 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
2032 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
2035 func testAllowListManipulation() throws {
2036 let description = tmpStoreDescription(name: "container.db")
2037 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2039 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2042 XCTAssertNotNil(peerID)
2043 XCTAssertNotNil(permanentInfo)
2044 XCTAssertNotNil(permanentInfoSig)
2046 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2048 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
2049 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2050 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2052 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"]), "should be able to set allowed machine IDs")
2053 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2054 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2056 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
2057 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2058 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2060 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2061 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2062 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2063 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2065 // once they're unknown, a full list set will make them disallowed
2066 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"]), "should be able to set allowed machine IDs")
2067 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2068 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2070 // Resetting the list to what it is doesn't change the list
2071 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], listDifference: false), "should be able to set allowed machine IDs")
2072 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2073 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2075 // But changing it to something completely new does
2076 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"]), "should be able to set allowed machine IDs")
2077 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2078 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2080 // And, readding a previously disallowed machine ID works too
2081 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"]), "should be able to set allowed machine IDs")
2082 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2083 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2085 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2086 let (_, updateError) = container.updateSync(test: self)
2087 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2088 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2090 let (_, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2091 XCTAssertNil(establishError, "Should be able to establish() with no error")
2092 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2093 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2095 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2096 let (_, updateError2) = container.updateSync(test: self)
2097 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2098 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2099 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2102 func testAllowListManipulationWithAddsAndRemoves() throws {
2103 let description = tmpStoreDescription(name: "container.db")
2104 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2106 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2108 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2109 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2110 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2112 // Now, an 'add' comes in for some peers
2113 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2114 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2115 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2117 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2118 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2119 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2120 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2122 // And a remove comes in for E. It becomes 'unknown'
2123 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2124 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2125 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2127 // and a list set after the remove confirms the removal
2128 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2129 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2130 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2132 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2133 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], listDifference: false), "should be able to set allowed machine IDs")
2134 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2135 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2137 // And another list set no longer includes D, so it should now be disallowed
2138 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2139 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2140 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2142 // And just to check the 48 hour boundary...
2143 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2144 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: false), "should be able to set allowed machine IDs")
2145 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2147 container.moc.performAndWait {
2148 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2150 knownMachineMOs.forEach {
2151 if $0.machineID == "xxx" {
2152 $0.modified = Date(timeIntervalSinceNow: -60 * 60 * 72)
2156 try! container.moc.save()
2159 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2161 // Setting the list again should kick out X, since it was 'added' too long ago
2162 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2163 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2166 func testAllowSetUpgrade() throws {
2167 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2169 let description = tmpStoreDescription(name: "container.db")
2170 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2171 let mom = getOrMakeModel(url: url)
2172 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2173 persistentContainer.persistentStoreDescriptions = [description]
2175 persistentContainer.loadPersistentStores { _, error in
2176 XCTAssertNil(error, "Should be no error loading persistent stores")
2179 let moc = persistentContainer.newBackgroundContext()
2180 moc.performAndWait {
2181 let containerMO = ContainerMO(context: moc)
2182 containerMO.name = containerName.asSingleString()
2183 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2185 XCTAssertNoThrow(try! moc.save())
2188 // Now TPH boots up with a preexisting model
2189 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2191 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2194 XCTAssertNotNil(peerID)
2195 XCTAssertNotNil(permanentInfo)
2196 XCTAssertNotNil(permanentInfoSig)
2198 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2200 // Setting a new list should work fine
2201 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"]), "should be able to set allowed machine IDs")
2202 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2204 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2207 func testAllowStatusUpgrade() throws {
2208 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2210 let description = tmpStoreDescription(name: "container.db")
2211 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2212 let mom = getOrMakeModel(url: url)
2213 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2214 persistentContainer.persistentStoreDescriptions = [description]
2216 persistentContainer.loadPersistentStores { _, error in
2217 XCTAssertNil(error, "Should be no error loading persistent stores")
2220 let moc = persistentContainer.newBackgroundContext()
2221 moc.performAndWait {
2222 let containerMO = ContainerMO(context: moc)
2223 containerMO.name = containerName.asSingleString()
2225 let machine = MachineMO(context: moc)
2226 machine.allowed = true
2227 machine.modified = Date()
2228 machine.machineID = "aaa"
2229 containerMO.addToMachines(machine)
2231 let machineB = MachineMO(context: moc)
2232 machineB.allowed = false
2233 machineB.modified = Date()
2234 machineB.machineID = "bbb"
2235 containerMO.addToMachines(machineB)
2240 // Now TPH boots up with a preexisting model
2241 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2242 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2244 // Setting a new list should work fine
2245 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"]), "should be able to set allowed machine IDs")
2246 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2248 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2251 func testMachineIDListSetWithUnknownMachineIDs() throws {
2252 let description = tmpStoreDescription(name: "container.db")
2253 let (container, _) = try establish(reload: false, store: description)
2255 container.moc.performAndWait {
2256 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2258 knownMachineMOs.forEach {
2259 container.containerMO.removeFromMachines($0)
2262 try! container.moc.save()
2265 // and set the machine ID list to something that doesn't include 'aaa'
2266 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2267 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2268 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2270 // But check that it exists, and set its modification date to a while ago for an upcoming test
2271 container.moc.performAndWait {
2272 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2274 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2275 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2277 let aaaMO = aaaMOs.first!
2278 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2279 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2281 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2282 try! container.moc.save()
2285 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2286 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2288 // Setting it again is fine...
2289 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2290 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2292 // And doesn't reset the modified date on the record
2293 container.moc.performAndWait {
2294 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2296 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2297 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2299 let aaaMO = aaaMOs.first!
2300 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2301 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2303 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2306 // And can be promoted to 'allowed'
2307 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2308 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2309 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2312 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2313 let description = tmpStoreDescription(name: "container.db")
2314 let (container, _) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description)
2316 // and set the machine ID list to something that doesn't include 'aaa'
2317 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2318 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2319 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2321 // But an entry for "aaa" should exist, as a peer in the model claims it as their MID
2322 container.moc.performAndWait {
2323 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2325 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2326 XCTAssertEqual(aaaMOs.count, 1, "Should have one machine MO for aaa")
2328 let aaaMO = aaaMOs.first!
2329 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2330 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2332 // Pretend that aaa was added 49 hours ago
2333 aaaMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49)
2334 try! container.moc.save()
2337 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2339 // And, setting the list again should disallow aaa, since it is so old
2340 // Note that this _should_ return a list difference, since A is promoted to disallowed
2341 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2342 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2343 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2345 // Setting ths list again has no change
2346 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2347 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2348 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2350 // But A can appear again, no problem.
2351 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2352 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2353 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2356 func testMachineIDListHandlingWithPeers() throws {
2357 let description = tmpStoreDescription(name: "container.db")
2358 let (container, peerID1) = try establish(reload: false, store: description)
2360 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2362 let unknownMachineID = "not-on-list"
2363 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2364 containerID: "second",
2365 machineID: unknownMachineID,
2366 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]),
2369 // And the first container accepts the join...
2370 let (_, cUpdateError) = container.updateSync(test: self)
2371 XCTAssertNil(cUpdateError, "Should be able to update first container")
2372 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2374 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2377 func testMachineIDListHandlingInDemoAccounts() throws {
2378 // Demo accounts have no machine IDs in their lists
2379 let description = tmpStoreDescription(name: "container.db")
2380 let (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), store: description)
2382 // And so we just don't write down any MIDs
2383 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2385 // Even when joining...
2386 let unknownMachineID = "not-on-list"
2387 let (c2, peerID2) = try self.joinByVoucher(sponsor: container,
2388 containerID: "second",
2389 machineID: unknownMachineID,
2392 try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2394 // And the first container accepts the join...
2395 let (_, cUpdateError) = container.updateSync(test: self)
2396 XCTAssertNil(cUpdateError, "Should be able to update first container")
2397 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2399 // And still has nothing in its list...
2400 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2402 // Even after a full list set
2403 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], listDifference: false), "should be able to set allowed machine IDs")
2404 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2406 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2409 func testContainerAndModelConsistency() throws {
2411 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2412 let description = tmpStoreDescription(name: "container.db")
2413 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2414 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2416 XCTAssertNotNil(peerID)
2417 XCTAssertNotNil(permanentInfo)
2418 XCTAssertNotNil(permanentInfoSig)
2419 XCTAssertNotNil(stableInfo)
2420 XCTAssertNotNil(stableInfoSig)
2422 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2423 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2424 let mom = getOrMakeModel(url: url)
2425 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2426 persistentContainer.persistentStoreDescriptions = [description]
2428 persistentContainer.loadPersistentStores { _, error in
2429 XCTAssertNil(error, "Should be no error loading persistent stores")
2432 let moc = persistentContainer.newBackgroundContext()
2433 moc.performAndWait {
2434 let containerMO = ContainerMO(context: moc)
2435 containerMO.name = containerName.asSingleString()
2436 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
2437 containerMO.egoPeerID = peerID
2438 containerMO.egoPeerPermanentInfo = permanentInfo
2439 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
2440 containerMO.egoPeerStableInfoSig = stableInfoSig
2441 containerMO.egoPeerStableInfo = stableInfo
2442 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
2444 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
2445 let info3 = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2,
2446 policyVersion: containerEgoStableInfo!.policyVersion,
2447 policyHash: containerEgoStableInfo!.policyHash,
2448 policySecrets: containerEgoStableInfo!.policySecrets,
2449 deviceName: containerEgoStableInfo!.deviceName,
2450 serialNumber: containerEgoStableInfo!.serialNumber,
2451 osVersion: containerEgoStableInfo!.osVersion,
2452 signing: peerKeys.signingKey,
2453 recoverySigningPubKey: containerEgoStableInfo!.recoverySigningPublicKey,
2454 recoveryEncryptionPubKey: containerEgoStableInfo!.recoveryEncryptionPublicKey,
2457 //setting the containerMO's ego stable info to an old clock
2458 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
2459 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
2461 //now we are adding the ego stable info with a clock of 3 to the list of peers
2462 let peer = PeerMO(context: moc)
2463 peer.peerID = peerID
2464 peer.permanentInfo = permanentInfo
2465 peer.permanentInfoSig = permanentInfoSig
2466 peer.stableInfo = info3.data
2467 peer.stableInfoSig = info3.sig
2468 peer.isEgoPeer = true
2469 peer.container = containerMO
2471 containerMO.addToPeers(peer)
2473 //at this point the containerMO's egoStableInfo should have a clock of 1
2474 //the saved ego peer in the peer list has a clock of 3
2477 XCTFail("load ego keys failed: \(error)")
2479 XCTAssertNoThrow(try! moc.save())
2482 // Now TPH boots up with a preexisting model
2483 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2485 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
2486 XCTAssertNotNil(stableInfoAfterBoot)
2488 //after boot the clock should be updated to the one that was saved in the model
2489 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")
2492 func testRetryableError() throws {
2493 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)))
2494 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)))
2495 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.networkFailure.rawValue, userInfo: nil)))
2496 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: nil)))
2498 let sub0 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalServerInternalError.rawValue, userInfo: nil)
2499 let e0 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub0])
2500 XCTAssertTrue(RetryingInvocable.retryableError(error: e0))
2502 let sub1 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalGenericError.rawValue, userInfo: nil)
2503 let e1 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub1])
2504 XCTAssertFalse(RetryingInvocable.retryableError(error: e1))
2506 let cf2 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
2507 let int2 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf2])
2508 let e2 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int2])
2509 XCTAssertFalse(RetryingInvocable.retryableError(error: e2))
2511 let cf3 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.retryableServerFailure.rawValue, userInfo: nil)
2512 let int3 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf3])
2513 let e3 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int3])
2514 XCTAssertTrue(RetryingInvocable.retryableError(error: e3))
2516 let cf4 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.transactionalFailure.rawValue, userInfo: nil)
2517 let int4 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf4])
2518 let e4 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int4])
2519 XCTAssertTrue(RetryingInvocable.retryableError(error: e4))