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, accountIsDemo: false, store: store)
163 func establish(reload: Bool,
165 allowedMachineIDs: Set<String> = Set(["aaa", "bbb", "ccc"]),
167 store: NSPersistentStoreDescription) throws -> (Container, String) {
168 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
170 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, accountIsDemo: accountIsDemo, listDifference: !allowedMachineIDs.isEmpty), "should be able to set allowed machine IDs")
172 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
174 let state = container.getStateSync(test: self)
175 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
176 let secret = container.loadSecretSync(test: self, label: peerID!)
177 XCTAssertNotNil(secret, "secret should not be nil")
178 XCTAssertNil(error, "error should be nil")
180 XCTAssertNotNil(peerID)
181 XCTAssertNotNil(permanentInfo)
182 XCTAssertNotNil(permanentInfoSig)
185 _ = container.dumpSync(test: self)
189 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
191 XCTFail("Creating container errored: \(error)")
195 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
197 XCTAssertNotNil(peerID2)
199 _ = container.dumpSync(test: self)
201 return (container, peerID!)
204 func testEstablishWithReload() throws {
205 let description = tmpStoreDescription(name: "container.db")
206 let (_, peerID) = try establish(reload: true, store: description)
208 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
211 func testEstablishNoReload() throws {
212 let description = tmpStoreDescription(name: "container.db")
213 _ = try establish(reload: false, store: description)
216 func testEstablishNotOnAllowListErrors() throws {
217 let description = tmpStoreDescription(name: "container.db")
218 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
220 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
222 let state = container.getStateSync(test: self)
223 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
224 let secret = container.loadSecretSync(test: self, label: peerID!)
225 XCTAssertNotNil(secret, "secret should not be nil")
226 XCTAssertNil(error, "error should be nil")
228 XCTAssertNotNil(peerID)
229 XCTAssertNotNil(permanentInfo)
230 XCTAssertNotNil(permanentInfoSig)
233 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
235 // Now set up a machine ID list that positively does not have our peer
236 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
238 let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
239 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
240 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
243 func joinByVoucher(sponsor: Container,
246 machineIDs: Set<String>,
248 store: NSPersistentStoreDescription) throws -> (Container, String) {
249 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
250 persistentStoreDescription: store,
251 cuttlefish: cuttlefish)
253 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: accountIsDemo, listDifference: !machineIDs.isEmpty), "Should be able to set machine IDs")
255 print("preparing \(containerID)")
256 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error) =
257 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
259 XCTAssertNotNil(peerID)
260 XCTAssertNotNil(permanentInfo)
261 XCTAssertNotNil(permanentInfoSig)
262 XCTAssertNotNil(stableInfo)
263 XCTAssertNotNil(stableInfoSig)
266 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
268 print("\(sponsor) vouches for \(containerID)")
269 let (voucherData, voucherSig, vouchError) =
270 sponsor.vouchSync(test: self,
272 permanentInfo: permanentInfo!,
273 permanentInfoSig: permanentInfoSig!,
274 stableInfo: stableInfo!,
275 stableInfoSig: stableInfoSig!,
276 ckksKeys: [self.manateeKeySet])
277 XCTAssertNil(vouchError)
278 XCTAssertNotNil(voucherData)
279 XCTAssertNotNil(voucherSig)
281 // As part of the join, the sponsor should have uploaded a tlk share
282 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
284 print("\(containerID) joins")
285 let (joinedPeerID, _, _, _, joinError) = c.joinSync(test: self,
286 voucherData: voucherData!,
287 voucherSig: voucherSig!,
290 XCTAssertNil(joinError)
291 XCTAssertEqual(joinedPeerID, peerID!)
297 func testJoin() throws {
298 let description = tmpStoreDescription(name: "container.db")
299 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
300 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
301 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
303 let machineIDs = Set(["aaa", "bbb", "ccc"])
304 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
305 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
306 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
309 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) =
310 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
311 XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare")
312 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
313 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
316 let state = containerA.getStateSync(test: self)
317 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
318 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
319 XCTAssertNotNil(secret, "secret should not be nil")
320 XCTAssertNil(error, "error should be nil")
323 XCTAssertNotNil(aPeerID)
324 XCTAssertNotNil(aPermanentInfo)
325 XCTAssertNotNil(aPermanentInfoSig)
327 print("establishing A")
329 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
331 XCTAssertNotNil(peerID)
335 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) =
336 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
338 let state = containerB.getStateSync(test: self)
339 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
340 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
341 XCTAssertNotNil(secret, "secret should not be nil")
342 XCTAssertNil(error, "error should be nil")
345 XCTAssertNotNil(bPeerID)
346 XCTAssertNotNil(bPermanentInfo)
347 XCTAssertNotNil(bPermanentInfoSig)
350 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
351 print("A vouches for B, but doesn't provide any TLKShares")
352 let (_, _, errorVouchingWithoutTLKs) =
353 containerA.vouchSync(test: self,
355 permanentInfo: bPermanentInfo!,
356 permanentInfoSig: bPermanentInfoSig!,
357 stableInfo: bStableInfo!,
358 stableInfoSig: bStableInfoSig!,
360 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
361 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
363 print("A vouches for B, but doesn't only has provisional TLKs at the time")
364 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
365 provisionalManateeKeySet.newUpload = true
367 let (_, _, errorVouchingWithProvisionalTLKs) =
368 containerA.vouchSync(test: self,
370 permanentInfo: bPermanentInfo!,
371 permanentInfoSig: bPermanentInfoSig!,
372 stableInfo: bStableInfo!,
373 stableInfoSig: bStableInfoSig!,
374 ckksKeys: [provisionalManateeKeySet])
375 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
376 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
378 print("A vouches for B")
379 let (voucherData, voucherSig, error3) =
380 containerA.vouchSync(test: self,
382 permanentInfo: bPermanentInfo!,
383 permanentInfoSig: bPermanentInfoSig!,
384 stableInfo: bStableInfo!,
385 stableInfoSig: bStableInfoSig!,
386 ckksKeys: [self.manateeKeySet])
388 XCTAssertNotNil(voucherData)
389 XCTAssertNotNil(voucherSig)
391 // As part of the vouch, A should have uploaded a tlkshare for B
392 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
395 let (peerID, _, _, _, error) = containerB.joinSync(test: self,
396 voucherData: voucherData!,
397 voucherSig: voucherSig!,
401 XCTAssertEqual(peerID, bPeerID!)
404 _ = containerA.dumpSync(test: self)
405 _ = containerB.dumpSync(test: self)
406 _ = containerC.dumpSync(test: self)
409 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, error4) =
410 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
412 let state = containerC.getStateSync(test: self)
413 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
414 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
415 XCTAssertNotNil(secret, "secret should not be nil")
416 XCTAssertNil(error, "error should be nil")
419 XCTAssertNotNil(cPeerID)
420 XCTAssertNotNil(cPermanentInfo)
421 XCTAssertNotNil(cPermanentInfoSig)
424 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
425 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
426 provisionalEngramKeySet.newUpload = true
428 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
429 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
431 print("B vouches for C")
432 let (voucherData, voucherSig, error) =
433 containerB.vouchSync(test: self,
435 permanentInfo: cPermanentInfo!,
436 permanentInfoSig: cPermanentInfoSig!,
437 stableInfo: cStableInfo!,
438 stableInfoSig: cStableInfoSig!,
439 ckksKeys: [self.manateeKeySet])
441 XCTAssertNotNil(voucherData)
442 XCTAssertNotNil(voucherSig)
444 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
447 let (peerID, _, _, _, error2) = containerC.joinSync(test: self,
448 voucherData: voucherData!,
449 voucherSig: voucherSig!,
450 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
453 XCTAssertEqual(peerID, cPeerID!)
455 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
456 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
457 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
462 let (_, error) = containerA.updateSync(test: self)
467 let state = containerA.getStateSync(test: self)
468 let a = state.peers[aPeerID!]!
469 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
472 _ = containerA.dumpSync(test: self)
473 _ = containerB.dumpSync(test: self)
474 _ = containerC.dumpSync(test: self)
477 func testJoinWithoutAllowListErrors() throws {
478 let description = tmpStoreDescription(name: "container.db")
479 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
480 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
482 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
484 let state = containerA.getStateSync(test: self)
485 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
486 let secret = containerA.loadSecretSync(test: self, label: peerID!)
487 XCTAssertNotNil(secret, "secret should not be nil")
488 XCTAssertNil(error, "error should be nil")
490 XCTAssertNil(error, "Should not have an error after preparing A")
491 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
492 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
493 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
495 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
497 let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
498 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
499 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
502 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, errorPrepareB) =
503 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
505 let state = containerA.getStateSync(test: self)
506 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
507 let secret = containerA.loadSecretSync(test: self, label: peerID!)
508 XCTAssertNotNil(secret, "secret should not be nil")
509 XCTAssertNil(error, "error should be nil")
511 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
512 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
513 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
514 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
516 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs on container B")
519 print("A vouches for B")
520 let (voucherData, voucherSig, error3) =
521 containerA.vouchSync(test: self,
523 permanentInfo: bPermanentInfo!,
524 permanentInfoSig: bPermanentInfoSig!,
525 stableInfo: bStableInfo!,
526 stableInfoSig: bStableInfoSig!,
528 XCTAssertNil(error3, "Should be no error vouching for B")
529 XCTAssertNotNil(voucherData, "Should have a voucher from A")
530 XCTAssertNotNil(voucherSig, "Should have a signature from A")
533 let (peerID, _, _, _, error) = containerB.joinSync(test: self,
534 voucherData: voucherData!,
535 voucherSig: voucherSig!,
538 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
539 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
543 func testReset() throws {
544 let description = tmpStoreDescription(name: "container.db")
545 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
547 let machineIDs = Set(["aaa"])
548 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
551 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
553 let state = containerA.getStateSync(test: self)
554 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
555 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
556 XCTAssertNotNil(secret, "secret should not be nil")
557 XCTAssertNil(error, "error should be nil")
560 XCTAssertNotNil(aPeerID)
561 XCTAssertNotNil(aPermanentInfo)
562 XCTAssertNotNil(aPermanentInfoSig)
564 print("establishing A")
566 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
567 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
569 XCTAssertNotNil(peerID)
570 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
575 let error = containerA.resetSync(resetReason: .testGenerated, test: self)
579 let (dict, error) = containerA.dumpSync(test: self)
581 XCTAssertNotNil(dict)
582 let peers: [Any] = dict!["peers"] as! [Any]
583 XCTAssertEqual(0, peers.count)
587 func testResetLocal() throws {
588 let description = tmpStoreDescription(name: "container.db")
589 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
591 let machineIDs = Set(["aaa"])
592 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
594 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
596 let state = containerA.getStateSync(test: self)
597 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
598 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
599 XCTAssertNotNil(secret, "secret should not be nil")
600 XCTAssertNil(error, "error should be nil")
602 XCTAssertNil(error, "Should be no error preparing an identity")
603 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
604 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
605 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
608 let (dict, error) = containerA.dumpSync(test: self)
609 XCTAssertNil(error, "Should be no error dumping")
610 XCTAssertNotNil(dict, "Should receive a dump dictionary")
612 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
613 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
615 let selfPeer: String? = selfInfo!["peerID"] as! String?
616 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
620 let error = containerA.localResetSync(test: self)
621 XCTAssertNil(error, "local-reset shouldn't error")
624 let (dict, error) = containerA.dumpSync(test: self)
626 XCTAssertNil(error, "Should be no error dumping")
627 XCTAssertNotNil(dict, "Should receive a dump dictionary")
629 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
630 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
632 let selfPeer: String? = selfInfo!["peerID"] as! String?
633 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
637 func testReplayAttack() throws {
638 let description = tmpStoreDescription(name: "container.db")
639 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
640 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
641 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
643 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
644 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
645 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
648 let (peerID, _, _, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
650 let state = containerA.getStateSync(test: self)
651 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
652 let secret = containerA.loadSecretSync(test: self, label: peerID!)
653 XCTAssertNotNil(secret, "secret should not be nil")
655 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
657 let state = containerB.getStateSync(test: self)
658 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
659 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
660 XCTAssertNotNil(secret, "secret should not be nil")
662 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
664 let state = containerC.getStateSync(test: self)
665 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
666 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
667 XCTAssertNotNil(secret, "secret should not be nil")
669 print("establishing A")
670 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
673 print("A vouches for B")
674 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
676 permanentInfo: bPermanentInfo!,
677 permanentInfoSig: bPermanentInfoSig!,
678 stableInfo: bStableInfo!,
679 stableInfoSig: bStableInfoSig!,
683 _ = containerB.joinSync(test: self,
684 voucherData: voucherData!,
685 voucherSig: voucherSig!,
691 _ = containerA.updateSync(test: self)
692 let earlyClock: TPCounter
694 let state = containerA.getStateSync(test: self)
695 let b = state.peers[bPeerID!]!
696 earlyClock = b.dynamicInfo!.clock
700 let snapshot = cuttlefish.state
703 print("B vouches for C")
704 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
705 permanentInfo: cPermanentInfo!,
706 permanentInfoSig: cPermanentInfoSig!,
707 stableInfo: cStableInfo!,
708 stableInfoSig: cStableInfoSig!,
712 _ = containerC.joinSync(test: self,
713 voucherData: voucherData!,
714 voucherSig: voucherSig!,
720 _ = containerB.updateSync(test: self)
723 _ = containerA.updateSync(test: self)
724 let lateClock: TPCounter
726 let state = containerA.getStateSync(test: self)
727 let b = state.peers[bPeerID!]!
728 lateClock = b.dynamicInfo!.clock
729 XCTAssertTrue(earlyClock < lateClock)
732 print("Reverting cuttlefish to the snapshot")
733 cuttlefish.state = snapshot
734 cuttlefish.makeSnapshot()
736 print("A updates, fetching the old snapshot from cuttlefish")
737 _ = containerA.updateSync(test: self)
739 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
740 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
742 let state = containerA.getStateSync(test: self)
743 let b = state.peers[bPeerID!]!
744 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
748 // TODO: need a real configurable mock cuttlefish
749 func testFetchPolicyDocuments() throws {
751 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
753 let missingTuple = TPPolicyVersion(version: 900, hash: "not a hash")
755 let policy1Tuple = TPPolicyVersion(version: 1, hash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=")
756 let policy1Data = Data(base64Encoded: "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
757 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
758 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2")!
760 let policy3Tuple = TPPolicyVersion(version: 3, hash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=")
761 let policy3Data = Data(base64Encoded: "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A=")!
763 let description = tmpStoreDescription(name: "container.db")
764 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
767 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, versions: [])
768 XCTAssertNil(error1, "No error querying for an empty list")
769 XCTAssertEqual(response1, [:], "Received empty dictionary")
773 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, versions: Set([policy1Tuple]))
774 XCTAssertNil(error2, "No error getting locally known policy document")
775 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
776 XCTAssert(response2?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1")
777 XCTAssertEqual(response2?[policy1Tuple], policy1Data, "retrieved data matches known data")
782 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, versions: [policy1Tuple, policy3Tuple])
783 XCTAssertNil(error3, "No error fetching local + remote policy")
784 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
786 XCTAssert(response3?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1")
787 XCTAssertEqual(response3?[policy1Tuple], policy1Data, "retrieved data matches known data")
789 XCTAssert(response3?.keys.contains(policy3Tuple) ?? false, "Should have retrieved request for policy3")
790 XCTAssertEqual(response3?[policy3Tuple], policy3Data, "retrieved data matches known data")
795 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple]))
796 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
797 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
802 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple,
805 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
806 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
810 func testEscrowKeys() throws {
812 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
813 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
815 let secretString = "i'm a secret!"
816 XCTAssertNotNil(secretString, "secretString should not be nil")
818 let secretData: Data? = secretString.data(using: .utf8)
819 XCTAssertNotNil(secretData, "secretData should not be nil")
821 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
822 XCTAssertNotNil(keys, "keys should not be nil")
824 XCTAssertNotNil(keys.secret, "secret should not be nil")
825 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
826 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
827 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
828 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
830 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
831 XCTAssertNotNil(hash, "hash should not be nil")
833 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
834 XCTAssertTrue(result, "result should be true")
836 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
837 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
839 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
840 XCTAssertNotNil(signingKey, "signingKey should not be nil")
841 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
842 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
845 func testEscrowKeyTestVectors() {
847 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
849 let secret = secretString.data(using: .utf8)
852 let testv1 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
853 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
855 let testv2 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
856 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
858 let testv3 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
859 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
861 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
862 let newSecret = newSecretString.data(using: .utf8)
864 let testv4 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
865 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
867 let testv5 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
868 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
870 let testv6 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
871 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
873 XCTFail("error testing escrow key test vectors \(error)")
877 func testRecoveryKeyTestVectors() {
878 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
880 let secret = secretString.data(using: .utf8)
883 let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID)
884 XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match")
886 let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID)
887 XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match")
889 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
890 let newSecret = newSecretString.data(using: .utf8)
892 let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID)
893 XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match")
895 let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID)
896 XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match")
898 XCTFail("error testing RecoveryKey test vectors \(error)")
902 func testJoiningWithBottle() throws {
903 var bottleA: BottleMO
905 let description = tmpStoreDescription(name: "container.db")
906 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
907 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
909 let machineIDs = Set(["aaa", "bbb"])
910 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
911 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
914 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
915 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
917 var state = containerA.getStateSync(test: self)
918 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
920 bottleA = state.bottles.removeFirst()
922 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
923 XCTAssertNotNil(secret, "secret should not be nil")
924 XCTAssertNil(error, "error should be nil")
926 XCTAssertNotNil(aPeerID)
927 XCTAssertNotNil(aPermanentInfo)
928 XCTAssertNotNil(aPermanentInfoSig)
930 print("establishing A")
932 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
933 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
935 XCTAssertNotNil(peerID)
936 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
939 let state = containerA.getStateSync(test: self)
940 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
941 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
943 XCTAssertNotNil(secret, "secret should not be nil")
944 XCTAssertNil(error, "error should be nil")
947 _ = containerB.updateSync(test: self)
950 let (bPeerID, _, _, _, _, _, _, error2) =
951 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
953 let state = containerB.getStateSync(test: self)
954 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
955 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
956 XCTAssertNotNil(secret, "secret should not be nil")
957 XCTAssertNil(error, "error should be nil")
962 print("B prepares to join via bottle")
964 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
965 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
966 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
967 XCTAssertNotNil(views, "Should have a set of views to restore")
968 XCTAssertNotNil(policy, "Should have a policy")
970 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
973 XCTAssertNotNil(voucherData)
974 XCTAssertNotNil(voucherSig)
976 // Before B joins, there should be no TLKShares for B
977 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
980 let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
982 XCTAssertEqual(peerID, bPeerID!)
984 // But afterward, it has one!
985 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
989 func testJoiningWithBottleAndEmptyBottleSalt() throws {
990 var bottleA: BottleMO
992 let description = tmpStoreDescription(name: "container.db")
993 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
994 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
996 let machineIDs = Set(["aaa", "bbb"])
997 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
998 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1000 print("preparing A")
1001 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1002 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1004 var state = containerA.getStateSync(test: self)
1005 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1007 bottleA = state.bottles.removeFirst()
1009 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1010 XCTAssertNotNil(secret, "secret should not be nil")
1011 XCTAssertNil(error, "error should be nil")
1013 XCTAssertNotNil(aPeerID)
1014 XCTAssertNotNil(aPermanentInfo)
1015 XCTAssertNotNil(aPermanentInfoSig)
1017 print("establishing A")
1019 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1020 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1022 XCTAssertNotNil(peerID)
1023 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1026 let state = containerA.getStateSync(test: self)
1027 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1028 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1030 XCTAssertNotNil(secret, "secret should not be nil")
1031 XCTAssertNil(error, "error should be nil")
1034 _ = containerB.updateSync(test: self)
1036 print("preparing B")
1037 let (bPeerID, _, _, _, _, _, _, error2) =
1038 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1040 let state = containerB.getStateSync(test: self)
1041 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1042 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1043 XCTAssertNotNil(secret, "secret should not be nil")
1044 XCTAssertNil(error, "error should be nil")
1046 XCTAssertNil(error2)
1049 print("B prepares to join via bottle")
1051 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1052 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1053 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1054 XCTAssertNotNil(views, "Should have a set of views to restore")
1055 XCTAssertNotNil(policy, "Should have a policy")
1057 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1059 XCTAssertNil(error3)
1060 XCTAssertNotNil(voucherData)
1061 XCTAssertNotNil(voucherSig)
1063 // Before B joins, there should be no TLKShares for B
1064 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1067 let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1069 XCTAssertEqual(peerID, bPeerID!)
1071 // But afterward, it has one!
1072 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1076 func testJoiningWithWrongEscrowRecordForBottle() throws {
1078 let description = tmpStoreDescription(name: "container.db")
1079 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1080 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1082 let machineIDs = Set(["aaa", "bbb"])
1083 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1084 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1086 print("preparing A")
1087 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1088 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1090 let state = containerA.getStateSync(test: self)
1091 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1092 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1093 XCTAssertNotNil(secret, "secret should not be nil")
1094 XCTAssertNil(error, "error should be nil")
1097 XCTAssertNotNil(aPeerID)
1098 XCTAssertNotNil(aPermanentInfo)
1099 XCTAssertNotNil(aPermanentInfoSig)
1101 print("establishing A")
1103 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1105 XCTAssertNotNil(peerID)
1108 let state = containerA.getStateSync(test: self)
1109 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1110 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1112 XCTAssertNotNil(secret, "secret should not be nil")
1113 XCTAssertNil(error, "error should be nil")
1116 _ = containerB.updateSync(test: self)
1118 print("preparing B")
1119 let (bPeerID, _, _, _, _, _, _, error2) =
1120 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1122 let state = containerB.getStateSync(test: self)
1123 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1124 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1125 XCTAssertNotNil(secret, "secret should not be nil")
1126 XCTAssertNil(error, "error should be nil")
1128 XCTAssertNil(error2)
1131 print("B joins via bottle")
1133 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record")
1134 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't exist")
1135 XCTAssertNil(bottlePeerID, "peerID should be nil for no bottle")
1136 XCTAssertNil(views, "Should not have a set of views to restore")
1137 XCTAssertNil(policy, "Should not have a policy")
1139 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1141 XCTAssertNotNil(error3)
1142 XCTAssertNil(voucherData)
1143 XCTAssertNil(voucherSig)
1147 func testJoiningWithWrongBottle() throws {
1148 var bottleB: BottleMO
1150 let description = tmpStoreDescription(name: "container.db")
1151 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1152 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1154 let machineIDs = Set(["aaa", "bbb"])
1155 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1156 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1158 print("preparing A")
1159 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1160 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1162 let state = containerA.getStateSync(test: self)
1163 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1164 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1165 XCTAssertNotNil(secret, "secret should not be nil")
1166 XCTAssertNil(error, "error should be nil")
1169 XCTAssertNotNil(aPeerID)
1170 XCTAssertNotNil(aPermanentInfo)
1171 XCTAssertNotNil(aPermanentInfoSig)
1173 print("establishing A")
1175 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1177 XCTAssertNotNil(peerID)
1180 let state = containerA.getStateSync(test: self)
1181 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1182 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1184 XCTAssertNotNil(secret, "secret should not be nil")
1185 XCTAssertNil(error, "error should be nil")
1188 print("preparing B")
1189 let (bPeerID, _, _, _, _, _, _, error2) =
1190 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1192 var state = containerB.getStateSync(test: self)
1193 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1194 bottleB = state.bottles.removeFirst()
1195 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1196 XCTAssertNotNil(secret, "secret should not be nil")
1197 XCTAssertNil(error, "error should be nil")
1199 XCTAssertNil(error2)
1202 print("B joins via bottle")
1204 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!)
1205 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't correspond to a peer")
1206 XCTAssertNil(bottlePeerID, "Should have no peer for invalid bottle")
1207 XCTAssertNil(views, "Should not have a set of views to restore")
1208 XCTAssertNil(policy, "Should not have a policy")
1210 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1212 XCTAssertNotNil(error3)
1213 XCTAssertNil(voucherData)
1214 XCTAssertNil(voucherSig)
1218 func testJoiningWithBottleAndBadSalt() throws {
1219 var bottleA: BottleMO
1221 let description = tmpStoreDescription(name: "container.db")
1222 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1223 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1225 let machineIDs = Set(["aaa", "bbb"])
1226 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1227 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1229 print("preparing A")
1230 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1231 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1233 var state = containerA.getStateSync(test: self)
1234 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1235 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1236 bottleA = state.bottles.removeFirst()
1237 XCTAssertNotNil(secret, "secret should not be nil")
1238 XCTAssertNil(error, "error should be nil")
1241 XCTAssertNotNil(aPeerID)
1242 XCTAssertNotNil(aPermanentInfo)
1243 XCTAssertNotNil(aPermanentInfoSig)
1245 print("establishing A")
1247 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1249 XCTAssertNotNil(peerID)
1252 let state = containerA.getStateSync(test: self)
1253 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1254 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1256 XCTAssertNotNil(secret, "secret should not be nil")
1257 XCTAssertNil(error, "error should be nil")
1260 _ = containerB.updateSync(test: self)
1262 print("preparing B")
1263 let (bPeerID, _, _, _, _, _, _, error2) =
1264 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1266 let state = containerB.getStateSync(test: self)
1267 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1268 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1269 XCTAssertNotNil(secret, "secret should not be nil")
1270 XCTAssertNil(error, "error should be nil")
1272 XCTAssertNil(error2)
1275 print("B joins via bottle")
1277 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1278 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1279 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1280 XCTAssertNotNil(views, "Should have a set of views to restore")
1281 XCTAssertNotNil(policy, "Should have a policy")
1283 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1285 XCTAssertNotNil(error3)
1286 XCTAssertNil(voucherData)
1287 XCTAssertNil(voucherSig)
1291 func testJoiningWithBottleAndBadSecret() throws {
1292 var bottleA: BottleMO
1293 let description = tmpStoreDescription(name: "container.db")
1294 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1295 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1297 let machineIDs = Set(["aaa", "bbb"])
1298 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1299 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1301 print("preparing A")
1302 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1303 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1305 var state = containerA.getStateSync(test: self)
1306 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1307 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1308 bottleA = state.bottles.removeFirst()
1309 XCTAssertNotNil(secret, "secret should not be nil")
1310 XCTAssertNil(error, "error should be nil")
1313 XCTAssertNotNil(aPeerID)
1314 XCTAssertNotNil(aPermanentInfo)
1315 XCTAssertNotNil(aPermanentInfoSig)
1317 print("establishing A")
1319 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1321 XCTAssertNotNil(peerID)
1324 let state = containerA.getStateSync(test: self)
1325 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1326 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1327 XCTAssertNotNil(secret, "secret should not be nil")
1328 XCTAssertNil(error, "error should be nil")
1331 _ = containerB.updateSync(test: self)
1333 print("preparing B")
1334 let (bPeerID, _, _, _, _, _, _, error2) =
1335 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1337 let state = containerB.getStateSync(test: self)
1338 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1339 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1340 XCTAssertNotNil(secret, "secret should not be nil")
1341 XCTAssertNil(error, "error should be nil")
1343 XCTAssertNil(error2)
1346 print("B joins via bottle")
1348 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1349 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1350 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1351 XCTAssertNotNil(views, "Should have a set of views to restore")
1352 XCTAssertNotNil(policy, "Should have a policy")
1354 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1356 XCTAssertNotNil(error3)
1357 XCTAssertNil(voucherData)
1358 XCTAssertNil(voucherSig)
1362 func testJoiningWithNoFetchAllBottles() throws {
1363 var bottleA: BottleMO
1365 let description = tmpStoreDescription(name: "container.db")
1366 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1367 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1369 let machineIDs = Set(["aaa", "bbb"])
1370 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1371 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1373 print("preparing A")
1374 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1375 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1377 var state = containerA.getStateSync(test: self)
1378 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1379 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1381 bottleA = state.bottles.removeFirst()
1382 XCTAssertNotNil(secret, "secret should not be nil")
1383 XCTAssertNil(error, "error should be nil")
1386 XCTAssertNotNil(aPeerID)
1387 XCTAssertNotNil(aPermanentInfo)
1388 XCTAssertNotNil(aPermanentInfoSig)
1390 print("establishing A")
1392 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1394 XCTAssertNotNil(peerID)
1397 let state = containerA.getStateSync(test: self)
1398 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1399 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1400 XCTAssertNotNil(secret, "secret should not be nil")
1401 XCTAssertNil(error, "error should be nil")
1404 print("preparing B")
1405 let (bPeerID, _, _, _, _, _, _, error2) =
1406 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1408 let state = containerB.getStateSync(test: self)
1409 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1410 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1411 XCTAssertNotNil(secret, "secret should not be nil")
1412 XCTAssertNil(error, "error should be nil")
1414 XCTAssertNil(error2)
1417 print("B joins via bottle")
1419 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1421 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1422 XCTAssertNotNil(errorPreflight, "Should be an error preflighting a vouch with bottle with a fetch error")
1423 XCTAssertNil(bottlePeerID, "peerID should be nil")
1424 XCTAssertNil(views, "Should not have a set of views to restore")
1425 XCTAssertNil(policy, "Should not have a policy")
1427 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1429 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1431 XCTAssertNotNil(error3)
1432 XCTAssertNil(voucherData)
1433 XCTAssertNil(voucherSig)
1437 func testJoinByPreapproval() throws {
1438 let description = tmpStoreDescription(name: "container.db")
1439 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1440 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1442 let machineIDs = Set(["aaa", "bbb"])
1443 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1444 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1446 print("preparing A")
1447 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) =
1448 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1450 XCTAssertNotNil(aPeerID)
1451 XCTAssertNotNil(aPermanentInfo)
1452 XCTAssertNotNil(aPermanentInfoSig)
1454 XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare")
1455 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
1456 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
1458 print("preparing B")
1459 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, _, error2) =
1460 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1462 let state = containerB.getStateSync(test: self)
1463 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1464 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1465 XCTAssertNotNil(secret, "secret should not be nil")
1466 XCTAssertNil(error, "error should be nil")
1468 XCTAssertNil(error2)
1469 XCTAssertNotNil(bPeerID)
1470 XCTAssertNotNil(bPermanentInfo)
1471 XCTAssertNotNil(bPermanentInfoSig)
1473 // Now, A establishes preapproving B
1474 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1476 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1477 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1479 print("establishing A")
1481 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
1483 XCTAssertNotNil(peerID)
1487 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1489 print("B joins by preapproval, and uploads all TLKShares that it has")
1490 let (bJoinedPeerID, _, views, policy, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
1491 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1492 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1493 XCTAssertNotNil(views, "should have a list of views to use")
1494 XCTAssertNotNil(policy, "Should have a policy back from preapprovedjoin")
1496 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1499 _ = containerA.dumpSync(test: self)
1500 _ = containerB.dumpSync(test: self)
1503 func testDepart() throws {
1504 let description = tmpStoreDescription(name: "container.db")
1505 let (container, peerID) = try establish(reload: false, store: description)
1507 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1508 assertDistrusts(context: container, peerIDs: [peerID])
1511 func testDistrustPeers() throws {
1512 let store = tmpStoreDescription(name: "container.db")
1513 let (c, peerID1) = try establish(reload: false, store: store)
1515 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1516 containerID: "second",
1518 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1521 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1522 containerID: "third",
1524 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1527 let (_, cUpdateError) = c.updateSync(test: self)
1528 XCTAssertNil(cUpdateError, "Should be able to update first container")
1529 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1531 // You can't distrust yourself via peerID.
1532 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1533 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1535 // Passing in nonsense should error too
1536 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1538 // Now, distrust both peers.
1539 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1540 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1542 // peers should accept their fates
1543 let (_, c2UpdateError) = c2.updateSync(test: self)
1544 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1545 assertDistrusts(context: c2, peerIDs: [peerID2])
1547 let (_, c3UpdateError) = c3.updateSync(test: self)
1548 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1549 assertDistrusts(context: c3, peerIDs: [peerID3])
1552 func testFetchWithBadChangeToken() throws {
1553 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1555 // But all that goes away, and a new peer establishes
1556 self.cuttlefish.state = FakeCuttlefishServer.State()
1557 let (_, peerID2) = try establish(reload: false, contextID: "second", accountIsDemo: false, store: tmpStoreDescription(name: "container-peer2.db"))
1559 // And the first container fetches again, which should succeed
1560 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1561 let (_, updateError) = c.updateSync(test: self)
1562 XCTAssertNil(updateError, "Update should have succeeded")
1564 // and c's model should only include peerID2
1565 c.moc.performAndWait {
1566 let modelPeers = c.model.allPeerIDs()
1567 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1568 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1569 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1573 func testFetchEscrowContents() throws {
1574 let description = tmpStoreDescription(name: "container.db")
1575 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1576 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1577 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1578 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1579 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1580 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1581 XCTAssertNil(spkiA, "Should not have an SPKI")
1583 let (c, peerID) = try establish(reload: false, store: description)
1584 XCTAssertNotNil(peerID, "establish should return a peer id")
1586 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1587 XCTAssertNil(error, "Should be no error fetching escrow contents")
1588 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1589 XCTAssertNotNil(bottleID, "Should have a bottleID")
1590 XCTAssertNotNil(spki, "Should have an SPKI")
1593 func testBottles() {
1595 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1596 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1597 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1599 let keys = bottle.escrowKeys
1600 XCTAssertNotNil(keys, "keys should not be nil")
1602 XCTAssertNotNil(bottle, "bottle should not be nil")
1604 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1606 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1607 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1609 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1611 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1612 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1614 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1615 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1617 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1618 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1620 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1623 XCTFail("error testing bottles \(error)")
1627 func testFetchBottles() throws {
1628 let store = tmpStoreDescription(name: "container.db")
1629 let (c, _) = try establish(reload: false, store: store)
1631 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1632 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1633 XCTAssertNotNil(bottles, "should have fetched some bottles")
1634 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1637 let state = c.getStateSync(test: self)
1638 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1641 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1643 let state = c2.getStateSync(test: self)
1644 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1647 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1648 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1649 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1650 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1653 let state = c2.getStateSync(test: self)
1654 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1658 func testTrustStatus() throws {
1659 let store = tmpStoreDescription(name: "container.db")
1661 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
1662 persistentStoreDescription: store,
1663 cuttlefish: self.cuttlefish)
1664 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
1665 XCTAssertNil(precStatusError, "No error fetching status")
1666 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
1667 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
1668 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
1669 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
1671 let (c, _) = try establish(reload: false, store: store)
1672 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1673 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1674 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1675 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1676 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1677 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1679 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
1680 persistentStoreDescription: store,
1681 cuttlefish: self.cuttlefish)
1683 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
1684 XCTAssertNil(statusError, "No error fetching status")
1685 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
1686 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
1687 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1688 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
1691 func testTrustStatusWhenMissingIdentityKeys() throws {
1692 let store = tmpStoreDescription(name: "container.db")
1693 let (c, _) = try establish(reload: false, store: store)
1694 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1695 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1696 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1697 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1698 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1699 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1701 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
1702 XCTAssertTrue(result, "result should be true")
1704 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
1705 XCTAssertNotNil(distrustedError, "error should not be nil")
1706 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
1707 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
1710 func testSetRecoveryKey() throws {
1711 let store = tmpStoreDescription(name: "container.db")
1713 let c = try Container(name: ContainerName(container: "c", context: "context"),
1714 persistentStoreDescription: store,
1715 cuttlefish: self.cuttlefish)
1717 let machineIDs = Set(["aaa", "bbb", "ccc"])
1718 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1720 print("preparing peer A")
1721 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1722 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1724 let state = c.getStateSync(test: self)
1725 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1726 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1727 XCTAssertNotNil(secret, "secret should not be nil")
1728 XCTAssertNil(error, "error should be nil")
1731 XCTAssertNotNil(aPeerID)
1732 XCTAssertNotNil(aPermanentInfo)
1733 XCTAssertNotNil(aPermanentInfoSig)
1735 print("establishing A")
1737 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1739 XCTAssertNotNil(peerID)
1741 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1742 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1744 let (setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1745 XCTAssertNil(setRecoveryError, "error should be nil")
1748 func roundTripThroughSetValueTransformer(set: Set<String>) {
1749 let t = SetValueTransformer()
1751 let transformedSet = t.transformedValue(set) as? Data
1752 XCTAssertNotNil(transformedSet, "SVT should return some data")
1754 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
1755 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
1757 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
1760 func testSetValueTransformer() {
1761 roundTripThroughSetValueTransformer(set: Set<String>([]))
1762 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
1763 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
1766 func testGetRepairSuggestion() throws {
1767 let store = tmpStoreDescription(name: "container.db")
1769 let c = try Container(name: ContainerName(container: "c", context: "context"),
1770 persistentStoreDescription: store,
1771 cuttlefish: self.cuttlefish)
1773 let machineIDs = Set(["aaa", "bbb", "ccc"])
1774 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1776 print("preparing peer A")
1777 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1778 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1780 let state = c.getStateSync(test: self)
1781 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1782 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1783 XCTAssertNotNil(secret, "secret should not be nil")
1784 XCTAssertNil(error, "error should be nil")
1787 XCTAssertNotNil(aPeerID)
1788 XCTAssertNotNil(aPermanentInfo)
1789 XCTAssertNotNil(aPermanentInfoSig)
1791 print("establishing A")
1793 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1795 XCTAssertNotNil(peerID)
1797 let (repairAccount, repairEscrow, resetOctagon, leaveTrust, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
1798 XCTAssertEqual(repairAccount, false, "")
1799 XCTAssertEqual(repairEscrow, false, "")
1800 XCTAssertEqual(resetOctagon, false, "")
1801 XCTAssertEqual(leaveTrust, false, "")
1802 XCTAssertNil(healthError)
1805 func testFetchChangesFailDuringVouchWithBottle() throws {
1806 var bottleA: BottleMO
1808 let description = tmpStoreDescription(name: "container.db")
1809 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1810 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1812 let machineIDs = Set(["aaa", "bbb"])
1813 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1814 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1816 print("preparing A")
1817 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, _, error) =
1818 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1820 var state = containerA.getStateSync(test: self)
1821 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1823 bottleA = state.bottles.removeFirst()
1825 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1826 XCTAssertNotNil(secret, "secret should not be nil")
1827 XCTAssertNil(error, "error should be nil")
1829 XCTAssertNotNil(aPeerID)
1830 XCTAssertNotNil(aPermanentInfo)
1831 XCTAssertNotNil(aPermanentInfoSig)
1833 print("establishing A")
1835 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1836 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1838 XCTAssertNotNil(peerID)
1839 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1842 let state = containerA.getStateSync(test: self)
1843 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1844 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1846 XCTAssertNotNil(secret, "secret should not be nil")
1847 XCTAssertNil(error, "error should be nil")
1850 _ = containerB.updateSync(test: self)
1852 print("preparing B")
1853 let (bPeerID, _, _, _, _, _, _, error2) =
1854 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1856 let state = containerB.getStateSync(test: self)
1857 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1858 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1859 XCTAssertNotNil(secret, "secret should not be nil")
1860 XCTAssertNil(error, "error should be nil")
1862 XCTAssertNil(error2)
1865 print("B prepares to join via bottle")
1867 let (bottlePeerID, views, policy, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1868 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1869 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1870 XCTAssertNotNil(views, "Should have a set of views to restore")
1871 XCTAssertNotNil(policy, "Should have a policy")
1873 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1875 XCTAssertNil(error3)
1876 XCTAssertNotNil(voucherData)
1877 XCTAssertNotNil(voucherSig)
1879 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1881 // Before B joins, there should be no TLKShares for B
1882 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1885 let (peerID, _, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1886 XCTAssertNotNil(error)
1887 XCTAssertNil(peerID)
1891 func testDistrustedPeerRecoveryKeyNotSet() throws {
1892 let description = tmpStoreDescription(name: "container.db")
1893 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1894 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1896 let machineIDs = Set(["aaa", "bbb"])
1897 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1898 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1900 print("preparing peer A")
1901 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, _, _, error) =
1902 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1904 let state = containerA.getStateSync(test: self)
1905 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1906 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1907 XCTAssertNotNil(secret, "secret should not be nil")
1908 XCTAssertNil(error, "error should be nil")
1911 XCTAssertNotNil(aPeerID)
1912 XCTAssertNotNil(aPermanentInfo)
1913 XCTAssertNotNil(aPermanentInfoSig)
1914 XCTAssertNotNil(aStableInfo)
1915 XCTAssertNotNil(aStableInfoSig)
1917 print("establishing A")
1919 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1921 XCTAssertNotNil(peerID)
1924 print("preparing B")
1925 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) =
1926 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1928 let state = containerB.getStateSync(test: self)
1929 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1930 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1931 XCTAssertNotNil(secret, "secret should not be nil")
1932 XCTAssertNil(error, "error should be nil")
1934 XCTAssertNil(error2)
1935 XCTAssertNotNil(bPeerID)
1936 XCTAssertNotNil(bPermanentInfo)
1937 XCTAssertNotNil(bPermanentInfoSig)
1940 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1941 print("A vouches for B, but doesn't provide any TLKShares")
1942 let (_, _, errorVouchingWithoutTLKs) =
1943 containerA.vouchSync(test: self,
1945 permanentInfo: bPermanentInfo!,
1946 permanentInfoSig: bPermanentInfoSig!,
1947 stableInfo: bStableInfo!,
1948 stableInfoSig: bStableInfoSig!,
1950 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
1951 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1953 print("A vouches for B, but doesn't only has provisional TLKs at the time")
1954 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1955 provisionalManateeKeySet.newUpload = true
1957 let (_, _, errorVouchingWithProvisionalTLKs) =
1958 containerA.vouchSync(test: self,
1960 permanentInfo: bPermanentInfo!,
1961 permanentInfoSig: bPermanentInfoSig!,
1962 stableInfo: bStableInfo!,
1963 stableInfoSig: bStableInfoSig!,
1964 ckksKeys: [provisionalManateeKeySet])
1965 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
1966 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1968 print("A vouches for B")
1969 let (voucherData, voucherSig, error3) =
1970 containerA.vouchSync(test: self,
1972 permanentInfo: bPermanentInfo!,
1973 permanentInfoSig: bPermanentInfoSig!,
1974 stableInfo: bStableInfo!,
1975 stableInfoSig: bStableInfoSig!,
1976 ckksKeys: [self.manateeKeySet])
1977 XCTAssertNil(error3)
1978 XCTAssertNotNil(voucherData)
1979 XCTAssertNotNil(voucherSig)
1981 // As part of the vouch, A should have uploaded a tlkshare for B
1982 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1985 let (peerID, _, _, _, error) = containerB.joinSync(test: self,
1986 voucherData: voucherData!,
1987 voucherSig: voucherSig!,
1991 XCTAssertEqual(peerID, bPeerID!)
1996 let (_, error) = containerA.updateSync(test: self)
2001 let (_, error) = containerB.updateSync(test: self)
2005 // Now, A distrusts B.
2006 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
2007 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
2009 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
2010 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
2012 let (setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
2013 XCTAssertNil(setRecoveryError, "error should be nil")
2017 let (_, error) = containerA.updateSync(test: self)
2022 let (_, error) = containerB.updateSync(test: self)
2027 let (dict, error) = containerA.dumpSync(test: self)
2029 XCTAssertNil(error, "Should be no error dumping")
2030 XCTAssertNotNil(dict, "Should receive a dump dictionary")
2032 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
2033 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
2035 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
2036 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
2038 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
2039 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
2041 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
2042 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
2047 func assert(container: Container,
2048 allowedMachineIDs: Set<String>,
2049 disallowedMachineIDs: Set<String>,
2050 unknownMachineIDs: Set<String> = Set(),
2051 persistentStore: NSPersistentStoreDescription,
2052 cuttlefish: FakeCuttlefishServer) throws {
2054 let midList = container.onqueueCurrentMIDList()
2055 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
2056 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
2057 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
2059 let (fetchedAllowList, fetchErr) = container.fetchAllowedMachineIDsSync(test: self)
2060 XCTAssertNil(fetchErr, "Should be no error fetching the allowed list")
2061 XCTAssertEqual(fetchedAllowList, allowedMachineIDs, "A fetched list of allowed machine IDs should match the loaded list")
2063 // if we reload the container, does it still match?
2064 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
2066 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
2067 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
2068 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
2069 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
2072 func testAllowListManipulation() throws {
2073 let description = tmpStoreDescription(name: "container.db")
2074 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2076 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2079 XCTAssertNotNil(peerID)
2080 XCTAssertNotNil(permanentInfo)
2081 XCTAssertNotNil(permanentInfoSig)
2083 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2085 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false), "should be able to set allowed machine IDs")
2086 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2087 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2089 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false), "should be able to set allowed machine IDs")
2090 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2091 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2093 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
2094 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2095 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2097 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2098 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2099 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2100 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2102 // once they're unknown, a full list set will make them disallowed
2103 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false), "should be able to set allowed machine IDs")
2104 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2105 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2107 // Resetting the list to what it is doesn't change the list
2108 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2109 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2110 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2112 // But changing it to something completely new does
2113 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"], accountIsDemo: false), "should be able to set allowed machine IDs")
2114 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2115 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2117 // And, readding a previously disallowed machine ID works too
2118 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
2119 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2120 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2122 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2123 let (_, updateError) = container.updateSync(test: self)
2124 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2125 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2127 let (_, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2128 XCTAssertNil(establishError, "Should be able to establish() with no error")
2129 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2130 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2132 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2133 let (_, updateError2) = container.updateSync(test: self)
2134 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2135 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2136 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2139 func testAllowListManipulationWithAddsAndRemoves() throws {
2140 let description = tmpStoreDescription(name: "container.db")
2141 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2143 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2145 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2146 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2147 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2149 // Now, an 'add' comes in for some peers
2150 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2151 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2152 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2154 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2155 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2156 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2157 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2159 // And a remove comes in for E. It becomes 'unknown'
2160 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2161 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2162 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2164 // and a list set after the remove confirms the removal
2165 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2166 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2167 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2169 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2170 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2171 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2172 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2174 // And another list set no longer includes D, so it should now be disallowed
2175 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2176 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2177 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2179 // And just to check the 48 hour boundary...
2180 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2181 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2182 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2184 container.moc.performAndWait {
2185 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2187 knownMachineMOs.forEach {
2188 if $0.machineID == "xxx" {
2189 $0.modified = Date(timeIntervalSinceNow: -60 * 60 * 72)
2193 try! container.moc.save()
2196 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2198 // Setting the list again should kick out X, since it was 'added' too long ago
2199 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2200 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2203 func testAllowSetUpgrade() throws {
2204 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2206 let description = tmpStoreDescription(name: "container.db")
2207 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2208 let mom = getOrMakeModel(url: url)
2209 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2210 persistentContainer.persistentStoreDescriptions = [description]
2212 persistentContainer.loadPersistentStores { _, error in
2213 XCTAssertNil(error, "Should be no error loading persistent stores")
2216 let moc = persistentContainer.newBackgroundContext()
2217 moc.performAndWait {
2218 let containerMO = ContainerMO(context: moc)
2219 containerMO.name = containerName.asSingleString()
2220 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2222 XCTAssertNoThrow(try! moc.save())
2225 // Now TPH boots up with a preexisting model
2226 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2228 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2231 XCTAssertNotNil(peerID)
2232 XCTAssertNotNil(permanentInfo)
2233 XCTAssertNotNil(permanentInfoSig)
2235 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2237 // Setting a new list should work fine
2238 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs")
2239 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2241 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2244 func testAllowStatusUpgrade() throws {
2245 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2247 let description = tmpStoreDescription(name: "container.db")
2248 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2249 let mom = getOrMakeModel(url: url)
2250 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2251 persistentContainer.persistentStoreDescriptions = [description]
2253 persistentContainer.loadPersistentStores { _, error in
2254 XCTAssertNil(error, "Should be no error loading persistent stores")
2257 let moc = persistentContainer.newBackgroundContext()
2258 moc.performAndWait {
2259 let containerMO = ContainerMO(context: moc)
2260 containerMO.name = containerName.asSingleString()
2262 let machine = MachineMO(context: moc)
2263 machine.allowed = true
2264 machine.modified = Date()
2265 machine.machineID = "aaa"
2266 containerMO.addToMachines(machine)
2268 let machineB = MachineMO(context: moc)
2269 machineB.allowed = false
2270 machineB.modified = Date()
2271 machineB.machineID = "bbb"
2272 containerMO.addToMachines(machineB)
2277 // Now TPH boots up with a preexisting model
2278 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2279 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2281 // Setting a new list should work fine
2282 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs")
2283 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2285 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2288 func testMachineIDListSetWithUnknownMachineIDs() throws {
2289 let description = tmpStoreDescription(name: "container.db")
2290 let (container, _) = try establish(reload: false, store: description)
2292 container.moc.performAndWait {
2293 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2295 knownMachineMOs.forEach {
2296 container.containerMO.removeFromMachines($0)
2299 try! container.moc.save()
2302 // and set the machine ID list to something that doesn't include 'aaa'
2303 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2304 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2305 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2307 // But check that it exists, and set its modification date to a while ago for an upcoming test
2308 container.moc.performAndWait {
2309 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2311 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2312 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2314 let aaaMO = aaaMOs.first!
2315 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2316 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2318 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2319 try! container.moc.save()
2322 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2323 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2325 // Setting it again is fine...
2326 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2327 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2329 // And doesn't reset the modified date on the record
2330 container.moc.performAndWait {
2331 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2333 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2334 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2336 let aaaMO = aaaMOs.first!
2337 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2338 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2340 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2343 // And can be promoted to 'allowed'
2344 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2345 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2346 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2349 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2350 let description = tmpStoreDescription(name: "container.db")
2351 var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(["aaa"]), accountIsDemo: false, store: description)
2353 // and set the machine ID list to something that doesn't include 'ddd'
2354 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2355 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2356 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2357 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2359 let unknownMachineID = "ddd"
2360 let (_, peerID3) = try self.joinByVoucher(sponsor: container,
2361 containerID: "second",
2362 machineID: unknownMachineID,
2363 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false,
2366 // And the first container accepts the join...
2367 let (_, cUpdateError) = container.updateSync(test: self)
2368 XCTAssertNil(cUpdateError, "Should be able to update first container")
2369 assertTrusts(context: container, peerIDs: [peerID1, peerID3])
2371 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2373 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2374 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2376 // But an entry for "ddd" should exist, as a peer in the model claims it as their MID
2377 container.moc.performAndWait {
2378 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2380 let unknownMOs = knownMachineMOs.filter { $0.machineID == unknownMachineID }
2381 XCTAssertEqual(unknownMOs.count, 1, "Should have one machine MO for ddd")
2383 let dddMO = unknownMOs.first!
2384 XCTAssertEqual(dddMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of ddd MO should be 'unknown'")
2385 XCTAssertFalse(dddMO.allowed, "allowed should no longer be a used field")
2387 // Pretend that ddd was added 49 hours ago
2388 dddMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49)
2389 try! container.moc.save()
2394 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2395 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2398 XCTFail("Creating container errored: \(error)")
2400 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2402 // And, setting the list again should disallow ddd, since it is so old
2403 // Note that this _should_ return a list difference, since D is promoted to disallowed
2404 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2405 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish)
2406 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2408 // Setting ths list again has no change
2409 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2410 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish)
2411 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2413 // But D can appear again, no problem.
2414 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc", "ddd"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2415 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2416 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2417 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2420 func testMachineIDListHandlingWithPeers() throws {
2421 let description = tmpStoreDescription(name: "container.db")
2422 let (container, peerID1) = try establish(reload: false, store: description)
2424 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2426 let unknownMachineID = "not-on-list"
2427 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2428 containerID: "second",
2429 machineID: unknownMachineID,
2430 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false,
2433 // And the first container accepts the join...
2434 let (_, cUpdateError) = container.updateSync(test: self)
2435 XCTAssertNil(cUpdateError, "Should be able to update first container")
2436 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2438 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2441 func testMachineIDListHandlingInDemoAccounts() throws {
2442 // Demo accounts have no machine IDs in their lists
2443 let description = tmpStoreDescription(name: "container.db")
2444 var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), accountIsDemo: true, store: description)
2446 // And so we just don't write down any MIDs
2447 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2449 // Even when joining...
2450 let unknownMachineID = "not-on-list"
2451 let (c2, peerID2) = try self.joinByVoucher(sponsor: container,
2452 containerID: "second",
2453 machineID: unknownMachineID,
2455 accountIsDemo: true,
2457 try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2459 c2.containerMO.honorIDMSListChanges = "NO"
2461 // And the first container accepts the join...
2462 let (_, cUpdateError) = container.updateSync(test: self)
2463 XCTAssertNil(cUpdateError, "Should be able to update first container")
2464 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2466 // And still has nothing in its list...
2467 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2471 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2472 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO")
2475 XCTFail("Creating container errored: \(error)")
2478 // Even after a full list set
2479 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], accountIsDemo: true, listDifference: false), "should be able to set allowed machine IDs")
2480 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2482 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2483 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO")
2486 func testContainerAndModelConsistency() throws {
2488 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2489 let description = tmpStoreDescription(name: "container.db")
2490 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2491 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, _, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2493 XCTAssertNotNil(peerID)
2494 XCTAssertNotNil(permanentInfo)
2495 XCTAssertNotNil(permanentInfoSig)
2496 XCTAssertNotNil(stableInfo)
2497 XCTAssertNotNil(stableInfoSig)
2499 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2500 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2501 let mom = getOrMakeModel(url: url)
2502 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2503 persistentContainer.persistentStoreDescriptions = [description]
2505 persistentContainer.loadPersistentStores { _, error in
2506 XCTAssertNil(error, "Should be no error loading persistent stores")
2509 let moc = persistentContainer.newBackgroundContext()
2510 moc.performAndWait {
2511 let containerMO = ContainerMO(context: moc)
2512 containerMO.name = containerName.asSingleString()
2513 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
2514 containerMO.egoPeerID = peerID
2515 containerMO.egoPeerPermanentInfo = permanentInfo
2516 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
2517 containerMO.egoPeerStableInfoSig = stableInfoSig
2518 containerMO.egoPeerStableInfo = stableInfo
2519 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
2521 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
2522 let info3 = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2,
2523 frozenPolicyVersion: containerEgoStableInfo!.frozenPolicyVersion,
2524 flexiblePolicyVersion: containerEgoStableInfo!.flexiblePolicyVersion!,
2525 policySecrets: containerEgoStableInfo!.policySecrets,
2526 deviceName: containerEgoStableInfo!.deviceName,
2527 serialNumber: containerEgoStableInfo!.serialNumber,
2528 osVersion: containerEgoStableInfo!.osVersion,
2529 signing: peerKeys.signingKey,
2530 recoverySigningPubKey: containerEgoStableInfo!.recoverySigningPublicKey,
2531 recoveryEncryptionPubKey: containerEgoStableInfo!.recoveryEncryptionPublicKey,
2534 //setting the containerMO's ego stable info to an old clock
2535 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
2536 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
2538 //now we are adding the ego stable info with a clock of 3 to the list of peers
2539 let peer = PeerMO(context: moc)
2540 peer.peerID = peerID
2541 peer.permanentInfo = permanentInfo
2542 peer.permanentInfoSig = permanentInfoSig
2543 peer.stableInfo = info3.data
2544 peer.stableInfoSig = info3.sig
2545 peer.isEgoPeer = true
2546 peer.container = containerMO
2548 containerMO.addToPeers(peer)
2550 //at this point the containerMO's egoStableInfo should have a clock of 1
2551 //the saved ego peer in the peer list has a clock of 3
2554 XCTFail("load ego keys failed: \(error)")
2556 XCTAssertNoThrow(try! moc.save())
2559 // Now TPH boots up with a preexisting model
2560 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2562 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
2563 XCTAssertNotNil(stableInfoAfterBoot)
2565 //after boot the clock should be updated to the one that was saved in the model
2566 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")
2569 func testRetryableError() throws {
2570 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)))
2571 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)))
2572 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.networkFailure.rawValue, userInfo: nil)))
2573 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: nil)))
2575 let sub0 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalServerInternalError.rawValue, userInfo: nil)
2576 let e0 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub0])
2577 XCTAssertTrue(RetryingInvocable.retryableError(error: e0))
2579 let sub1 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalGenericError.rawValue, userInfo: nil)
2580 let e1 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub1])
2581 XCTAssertFalse(RetryingInvocable.retryableError(error: e1))
2583 let cf2 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
2584 let int2 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf2])
2585 let e2 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int2])
2586 XCTAssertFalse(RetryingInvocable.retryableError(error: e2))
2588 let cf3 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.retryableServerFailure.rawValue, userInfo: nil)
2589 let int3 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf3])
2590 let e3 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int3])
2591 XCTAssertTrue(RetryingInvocable.retryableError(error: e3))
2593 let cf4 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.transactionalFailure.rawValue, userInfo: nil)
2594 let int4 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf4])
2595 let e4 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int4])
2596 XCTAssertTrue(RetryingInvocable.retryableError(error: e4))
2599 func testEstablishWithEnforceIDMSListNotSetBehavior() throws {
2600 let contextID = "OTDefaultContext"
2601 let description = tmpStoreDescription(name: "container.db")
2603 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish)
2604 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2606 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2608 let state = container.getStateSync(test: self)
2609 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
2610 let secret = container.loadSecretSync(test: self, label: peerID!)
2611 XCTAssertNotNil(secret, "secret should not be nil")
2612 XCTAssertNil(error, "error should be nil")
2614 XCTAssertNotNil(peerID)
2615 XCTAssertNotNil(permanentInfo)
2616 XCTAssertNotNil(permanentInfoSig)
2619 _ = container.dumpSync(test: self)
2622 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish)
2624 XCTFail("Creating container errored: \(error)")
2627 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2628 XCTAssertNil(error2)
2629 XCTAssertNotNil(peerID2)
2631 _ = container.dumpSync(test: self)
2632 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2635 func testJoinWithEnforceIDMSListNotSetBehavior() throws {
2636 let description = tmpStoreDescription(name: "container.db")
2637 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2638 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2639 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2641 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2642 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2643 XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2645 print("preparing A")
2646 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) =
2647 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2648 XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare")
2649 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
2650 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
2653 let state = containerA.getStateSync(test: self)
2654 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
2655 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
2656 XCTAssertNotNil(secret, "secret should not be nil")
2657 XCTAssertNil(error, "error should be nil")
2660 XCTAssertNotNil(aPeerID)
2661 XCTAssertNotNil(aPermanentInfo)
2662 XCTAssertNotNil(aPermanentInfoSig)
2664 print("establishing A")
2666 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
2668 XCTAssertNotNil(peerID)
2671 print("preparing B")
2672 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _, error2) =
2673 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2675 let state = containerB.getStateSync(test: self)
2676 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
2677 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
2678 XCTAssertNotNil(secret, "secret should not be nil")
2679 XCTAssertNil(error, "error should be nil")
2681 XCTAssertNil(error2)
2682 XCTAssertNotNil(bPeerID)
2683 XCTAssertNotNil(bPermanentInfo)
2684 XCTAssertNotNil(bPermanentInfoSig)
2687 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2688 print("A vouches for B, but doesn't provide any TLKShares")
2689 let (_, _, errorVouchingWithoutTLKs) =
2690 containerA.vouchSync(test: self,
2692 permanentInfo: bPermanentInfo!,
2693 permanentInfoSig: bPermanentInfoSig!,
2694 stableInfo: bStableInfo!,
2695 stableInfoSig: bStableInfoSig!,
2697 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
2698 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2700 print("A vouches for B, but doesn't only has provisional TLKs at the time")
2701 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2702 provisionalManateeKeySet.newUpload = true
2704 let (_, _, errorVouchingWithProvisionalTLKs) =
2705 containerA.vouchSync(test: self,
2707 permanentInfo: bPermanentInfo!,
2708 permanentInfoSig: bPermanentInfoSig!,
2709 stableInfo: bStableInfo!,
2710 stableInfoSig: bStableInfoSig!,
2711 ckksKeys: [provisionalManateeKeySet])
2712 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
2713 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2715 print("A vouches for B")
2716 let (voucherData, voucherSig, error3) =
2717 containerA.vouchSync(test: self,
2719 permanentInfo: bPermanentInfo!,
2720 permanentInfoSig: bPermanentInfoSig!,
2721 stableInfo: bStableInfo!,
2722 stableInfoSig: bStableInfoSig!,
2723 ckksKeys: [self.manateeKeySet])
2724 XCTAssertNil(error3)
2725 XCTAssertNotNil(voucherData)
2726 XCTAssertNotNil(voucherSig)
2728 // As part of the vouch, A should have uploaded a tlkshare for B
2729 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2732 let (peerID, _, _, _, error) = containerB.joinSync(test: self,
2733 voucherData: voucherData!,
2734 voucherSig: voucherSig!,
2738 XCTAssertEqual(peerID, bPeerID!)
2741 _ = containerA.dumpSync(test: self)
2742 _ = containerB.dumpSync(test: self)
2743 _ = containerC.dumpSync(test: self)
2745 print("preparing C")
2746 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _, error4) =
2747 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2749 let state = containerC.getStateSync(test: self)
2750 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
2751 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
2752 XCTAssertNotNil(secret, "secret should not be nil")
2753 XCTAssertNil(error, "error should be nil")
2755 XCTAssertNil(error4)
2756 XCTAssertNotNil(cPeerID)
2757 XCTAssertNotNil(cPermanentInfo)
2758 XCTAssertNotNil(cPermanentInfoSig)
2761 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
2762 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
2763 provisionalEngramKeySet.newUpload = true
2765 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2766 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
2768 print("B vouches for C")
2769 let (voucherData, voucherSig, error) =
2770 containerB.vouchSync(test: self,
2772 permanentInfo: cPermanentInfo!,
2773 permanentInfoSig: cPermanentInfoSig!,
2774 stableInfo: cStableInfo!,
2775 stableInfoSig: cStableInfoSig!,
2776 ckksKeys: [self.manateeKeySet])
2778 XCTAssertNotNil(voucherData)
2779 XCTAssertNotNil(voucherSig)
2781 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2784 let (peerID, _, _, _, error2) = containerC.joinSync(test: self,
2785 voucherData: voucherData!,
2786 voucherSig: voucherSig!,
2787 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
2789 XCTAssertNil(error2)
2790 XCTAssertEqual(peerID, cPeerID!)
2792 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
2793 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
2794 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
2799 let (_, error) = containerA.updateSync(test: self)
2804 let state = containerA.getStateSync(test: self)
2805 let a = state.peers[aPeerID!]!
2806 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
2809 _ = containerA.dumpSync(test: self)
2810 _ = containerB.dumpSync(test: self)
2811 _ = containerC.dumpSync(test: self)
2813 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2814 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2815 XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2818 func testPreApprovedJoinWithEnforceIDMSListNotSetBehavior() throws {
2819 let description = tmpStoreDescription(name: "container.db")
2820 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2821 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2823 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2824 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2826 print("preparing A")
2827 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aViewList, aPolicy, error) =
2828 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2830 XCTAssertNotNil(aPeerID)
2831 XCTAssertNotNil(aPermanentInfo)
2832 XCTAssertNotNil(aPermanentInfoSig)
2834 XCTAssertNotNil(aViewList, "Should have a view list coming back from a successful prepare")
2835 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
2836 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
2838 print("preparing B")
2839 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, _, error2) =
2840 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2842 let state = containerB.getStateSync(test: self)
2843 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
2844 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
2845 XCTAssertNotNil(secret, "secret should not be nil")
2846 XCTAssertNil(error, "error should be nil")
2848 XCTAssertNil(error2)
2849 XCTAssertNotNil(bPeerID)
2850 XCTAssertNotNil(bPermanentInfo)
2851 XCTAssertNotNil(bPermanentInfoSig)
2853 // Now, A establishes preapproving B
2854 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
2856 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
2857 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
2859 print("establishing A")
2861 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
2863 XCTAssertNotNil(peerID)
2867 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2869 print("B joins by preapproval, and uploads all TLKShares that it has")
2870 let (bJoinedPeerID, _, views, policy, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
2871 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
2872 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
2873 XCTAssertNotNil(views, "should have a list of views to use")
2874 XCTAssertNotNil(policy, "Should have a policy back from preapprovedjoin")
2876 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2879 _ = containerA.dumpSync(test: self)
2880 _ = containerB.dumpSync(test: self)
2882 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2883 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2886 func testReloadingContainerIDMSListVariable() throws {
2887 let store = tmpStoreDescription(name: "container.db")
2888 let contextID = "contextID"
2889 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
2891 let (peerID, permanentInfo, permanentInfoSig, _, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2893 let state = container.getStateSync(test: self)
2894 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
2895 let secret = container.loadSecretSync(test: self, label: peerID!)
2896 XCTAssertNotNil(secret, "secret should not be nil")
2897 XCTAssertNil(error, "error should be nil")
2899 XCTAssertNotNil(peerID)
2900 XCTAssertNotNil(permanentInfo)
2901 XCTAssertNotNil(permanentInfoSig)
2904 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2906 _ = container.dumpSync(test: self)
2909 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
2910 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
2913 XCTFail("Creating container errored: \(error)")
2916 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2917 XCTAssertNil(error2)
2918 XCTAssertNotNil(peerID2)
2920 _ = container.dumpSync(test: self)