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 class TrustedPeersHelperUnitTests: XCTestCase {
23 var cuttlefish: FakeCuttlefishServer!
25 var manateeKeySet: CKKSKeychainBackedKeySet!
27 override static func setUp() {
30 SecTapToRadar.disableTTRsEntirely()
32 // Turn on NO_SERVER stuff
33 securityd_init_local_spi()
38 override func setUp() {
41 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
42 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
44 // Make a new fake keychain
45 tmpPath = String(format: "/tmp/%@-%X", testName, arc4random())
46 tmpURL = URL(fileURLWithPath: tmpPath, isDirectory: true)
48 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
49 SetCustomHomeURLString(tmpPath as CFString)
50 SecKeychainDbReset(nil)
52 XCTFail("setUp failed: \(error)")
55 // Actually load the database.
56 kc_with_dbt(true, nil) { _ in
60 // Now that the keychain is alive, perform test setup
62 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
64 XCTFail("Creation of fake key hierarchies failed: \(error)")
68 override func tearDown() {
69 // Put teardown code here. This method is called after the invocation of each test method in the class.
74 func makeFakeKeyHierarchy(zoneID: CKRecordZone.ID) throws -> CKKSKeychainBackedKeySet {
75 // Remember, these keys come into TPH having round-tripped through an NSEncoding
76 let tlk = try CKKSKeychainBackedKey.randomKeyWrapped(bySelf: zoneID)
77 let classA = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassA)
78 let classC = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassC)
80 XCTAssertNoThrow(try tlk.saveMaterialToKeychain(), "Should be able to save TLK to keychain")
81 XCTAssertNoThrow(try classA.saveMaterialToKeychain(), "Should be able to save classA key to keychain")
82 XCTAssertNoThrow(try classC.saveMaterialToKeychain(), "Should be able to save classC key to keychain")
84 let tlkData = try NSKeyedArchiver.archivedData(withRootObject: tlk, requiringSecureCoding: true)
85 let classAData = try NSKeyedArchiver.archivedData(withRootObject: classA, requiringSecureCoding: true)
86 let classCData = try NSKeyedArchiver.archivedData(withRootObject: classC, requiringSecureCoding: true)
88 let decodedTLK = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: tlkData) as! CKKSKeychainBackedKey
89 let decodedClassA = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classAData) as! CKKSKeychainBackedKey
90 let decodedClassC = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classCData) as! CKKSKeychainBackedKey
92 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
95 func assertTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
96 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
97 tlkShare.receiver == peerID &&
98 tlkShare.keyUuid == keyUUID
101 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
104 func assertNoTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
105 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
106 tlkShare.receiver == peerID &&
107 tlkShare.keyUuid == keyUUID
110 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
113 func assertTrusts(context: Container, peerIDs: [String]) {
114 let state = context.getStateSync(test: self)
115 guard let egoPeerID = state.egoPeerID else {
116 XCTFail("context should have an ego peer ID")
120 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
121 XCTFail("No dynamicInfo for ego peer")
126 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
127 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
131 func assertDistrusts(context: Container, peerIDs: [String]) {
132 let state = context.getStateSync(test: self)
133 guard let egoPeerID = state.egoPeerID else {
134 XCTFail("context should have an ego peer ID")
138 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
139 XCTFail("No dynamicInfo for ego peer")
144 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
145 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
149 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
150 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
151 return NSPersistentStoreDescription(url: tmpStoreURL)
154 func establish(reload: Bool,
155 store: NSPersistentStoreDescription) throws -> (Container, String) {
156 return try self.establish(reload: reload, contextID: OTDefaultContext, store: store)
159 func establish(reload: Bool,
161 store: NSPersistentStoreDescription) throws -> (Container, String) {
162 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
164 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
166 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
168 let state = container.getStateSync(test: self)
169 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
170 let secret = container.loadSecretSync(test: self, label: peerID!)
171 XCTAssertNotNil(secret, "secret should not be nil")
172 XCTAssertNil(error, "error should be nil")
174 XCTAssertNotNil(peerID)
175 XCTAssertNotNil(permanentInfo)
176 XCTAssertNotNil(permanentInfoSig)
179 _ = container.dumpSync(test: self)
183 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
189 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
191 XCTAssertNotNil(peerID2)
193 _ = container.dumpSync(test: self)
195 return (container, peerID!)
198 func testEstablishWithReload() throws {
199 let description = tmpStoreDescription(name: "container.db")
200 let (_, peerID) = try establish(reload: true, store: description)
202 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
205 func testEstablishNoReload() throws {
206 let description = tmpStoreDescription(name: "container.db")
207 _ = try establish(reload: false, store: description)
210 func testEstablishNotOnAllowListErrors() throws {
211 let description = tmpStoreDescription(name: "container.db")
212 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
214 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
216 let state = container.getStateSync(test: self)
217 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
218 let secret = container.loadSecretSync(test: self, label: peerID!)
219 XCTAssertNotNil(secret, "secret should not be nil")
220 XCTAssertNil(error, "error should be nil")
222 XCTAssertNotNil(peerID)
223 XCTAssertNotNil(permanentInfo)
224 XCTAssertNotNil(permanentInfoSig)
227 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
229 // Now set up a machine ID list that positively does not have our peer
230 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
232 let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
233 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
234 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
237 func joinByVoucher(sponsor: Container,
240 machineIDs: Set<String>,
241 store: NSPersistentStoreDescription) throws -> (Container, String) {
242 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
243 persistentStoreDescription: store,
244 cuttlefish: cuttlefish)
246 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs), "Should be able to set machine IDs")
248 print("preparing \(containerID)")
249 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) =
250 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
252 XCTAssertNotNil(peerID)
253 XCTAssertNotNil(permanentInfo)
254 XCTAssertNotNil(permanentInfoSig)
255 XCTAssertNotNil(stableInfo)
256 XCTAssertNotNil(stableInfoSig)
259 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
261 print("\(sponsor) vouches for \(containerID)")
262 let (voucherData, voucherSig, vouchError) =
263 sponsor.vouchSync(test: self,
265 permanentInfo: permanentInfo!,
266 permanentInfoSig: permanentInfoSig!,
267 stableInfo: stableInfo!,
268 stableInfoSig: stableInfoSig!,
269 ckksKeys: [self.manateeKeySet])
270 XCTAssertNil(vouchError)
271 XCTAssertNotNil(voucherData)
272 XCTAssertNotNil(voucherSig)
274 // As part of the join, the sponsor should have uploaded a tlk share
275 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
277 print("\(containerID) joins")
278 let (joinedPeerID, _, joinError) = c.joinSync(test: self,
279 voucherData: voucherData!,
280 voucherSig: voucherSig!,
283 XCTAssertNil(joinError)
284 XCTAssertEqual(joinedPeerID, peerID!)
290 func testJoin() throws {
291 let description = tmpStoreDescription(name: "container.db")
292 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
293 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
294 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
296 let machineIDs = Set(["aaa", "bbb", "ccc"])
297 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
298 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
299 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
302 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
303 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
305 let state = containerA.getStateSync(test: self)
306 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
307 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
308 XCTAssertNotNil(secret, "secret should not be nil")
309 XCTAssertNil(error, "error should be nil")
312 XCTAssertNotNil(aPeerID)
313 XCTAssertNotNil(aPermanentInfo)
314 XCTAssertNotNil(aPermanentInfoSig)
316 print("establishing A")
318 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
320 XCTAssertNotNil(peerID)
324 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
325 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
327 let state = containerB.getStateSync(test: self)
328 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
329 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
330 XCTAssertNotNil(secret, "secret should not be nil")
331 XCTAssertNil(error, "error should be nil")
334 XCTAssertNotNil(bPeerID)
335 XCTAssertNotNil(bPermanentInfo)
336 XCTAssertNotNil(bPermanentInfoSig)
339 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
340 print("A vouches for B, but doesn't provide any TLKShares")
341 let (_, _, errorVouchingWithoutTLKs) =
342 containerA.vouchSync(test: self,
344 permanentInfo: bPermanentInfo!,
345 permanentInfoSig: bPermanentInfoSig!,
346 stableInfo: bStableInfo!,
347 stableInfoSig: bStableInfoSig!,
349 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
350 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
352 print("A vouches for B, but doesn't only has provisional TLKs at the time")
353 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
354 provisionalManateeKeySet.newUpload = true
356 let (_, _, errorVouchingWithProvisionalTLKs) =
357 containerA.vouchSync(test: self,
359 permanentInfo: bPermanentInfo!,
360 permanentInfoSig: bPermanentInfoSig!,
361 stableInfo: bStableInfo!,
362 stableInfoSig: bStableInfoSig!,
363 ckksKeys: [provisionalManateeKeySet])
364 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
365 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
367 print("A vouches for B")
368 let (voucherData, voucherSig, error3) =
369 containerA.vouchSync(test: self,
371 permanentInfo: bPermanentInfo!,
372 permanentInfoSig: bPermanentInfoSig!,
373 stableInfo: bStableInfo!,
374 stableInfoSig: bStableInfoSig!,
375 ckksKeys: [self.manateeKeySet])
377 XCTAssertNotNil(voucherData)
378 XCTAssertNotNil(voucherSig)
380 // As part of the vouch, A should have uploaded a tlkshare for B
381 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
384 let (peerID, _, error) = containerB.joinSync(test: self,
385 voucherData: voucherData!,
386 voucherSig: voucherSig!,
390 XCTAssertEqual(peerID, bPeerID!)
393 _ = containerA.dumpSync(test: self)
394 _ = containerB.dumpSync(test: self)
395 _ = containerC.dumpSync(test: self)
398 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, error4) =
399 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
401 let state = containerC.getStateSync(test: self)
402 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
403 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
404 XCTAssertNotNil(secret, "secret should not be nil")
405 XCTAssertNil(error, "error should be nil")
408 XCTAssertNotNil(cPeerID)
409 XCTAssertNotNil(cPermanentInfo)
410 XCTAssertNotNil(cPermanentInfoSig)
413 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
414 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
415 provisionalEngramKeySet.newUpload = true
417 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
418 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
420 print("B vouches for C")
421 let (voucherData, voucherSig, error) =
422 containerB.vouchSync(test: self,
424 permanentInfo: cPermanentInfo!,
425 permanentInfoSig: cPermanentInfoSig!,
426 stableInfo: cStableInfo!,
427 stableInfoSig: cStableInfoSig!,
428 ckksKeys: [self.manateeKeySet])
430 XCTAssertNotNil(voucherData)
431 XCTAssertNotNil(voucherSig)
433 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
436 let (peerID, _, error2) = containerC.joinSync(test: self,
437 voucherData: voucherData!,
438 voucherSig: voucherSig!,
439 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
442 XCTAssertEqual(peerID, cPeerID!)
444 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
445 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
446 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
451 let (_, error) = containerA.updateSync(test: self)
456 let state = containerA.getStateSync(test: self)
457 let a = state.peers[aPeerID!]!
458 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
461 _ = containerA.dumpSync(test: self)
462 _ = containerB.dumpSync(test: self)
463 _ = containerC.dumpSync(test: self)
466 func testJoinWithoutAllowListErrors() throws {
467 let description = tmpStoreDescription(name: "container.db")
468 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
469 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
471 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
473 let state = containerA.getStateSync(test: self)
474 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
475 let secret = containerA.loadSecretSync(test: self, label: peerID!)
476 XCTAssertNotNil(secret, "secret should not be nil")
477 XCTAssertNil(error, "error should be nil")
479 XCTAssertNil(error, "Should not have an error after preparing A")
480 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
481 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
482 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
484 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
486 let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
487 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
488 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
491 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, errorPrepareB) =
492 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
494 let state = containerA.getStateSync(test: self)
495 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
496 let secret = containerA.loadSecretSync(test: self, label: peerID!)
497 XCTAssertNotNil(secret, "secret should not be nil")
498 XCTAssertNil(error, "error should be nil")
500 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
501 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
502 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
503 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
505 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs on container B")
508 print("A vouches for B")
509 let (voucherData, voucherSig, error3) =
510 containerA.vouchSync(test: self,
512 permanentInfo: bPermanentInfo!,
513 permanentInfoSig: bPermanentInfoSig!,
514 stableInfo: bStableInfo!,
515 stableInfoSig: bStableInfoSig!,
517 XCTAssertNil(error3, "Should be no error vouching for B")
518 XCTAssertNotNil(voucherData, "Should have a voucher from A")
519 XCTAssertNotNil(voucherSig, "Should have a signature from A")
522 let (peerID, _, error) = containerB.joinSync(test: self,
523 voucherData: voucherData!,
524 voucherSig: voucherSig!,
527 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
528 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
532 func testReset() throws {
533 let description = tmpStoreDescription(name: "container.db")
534 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
536 let machineIDs = Set(["aaa"])
537 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
540 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
542 let state = containerA.getStateSync(test: self)
543 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
544 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
545 XCTAssertNotNil(secret, "secret should not be nil")
546 XCTAssertNil(error, "error should be nil")
549 XCTAssertNotNil(aPeerID)
550 XCTAssertNotNil(aPermanentInfo)
551 XCTAssertNotNil(aPermanentInfoSig)
553 print("establishing A")
555 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
556 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
558 XCTAssertNotNil(peerID)
559 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
564 let error = containerA.resetSync(test: self)
568 let (dict, error) = containerA.dumpSync(test: self)
570 XCTAssertNotNil(dict)
571 let peers: Array<Any> = dict!["peers"] as! Array<Any>
572 XCTAssertEqual(0, peers.count)
576 func testResetLocal() throws {
577 let description = tmpStoreDescription(name: "container.db")
578 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
580 let machineIDs = Set(["aaa"])
581 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
583 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
585 let state = containerA.getStateSync(test: self)
586 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
587 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
588 XCTAssertNotNil(secret, "secret should not be nil")
589 XCTAssertNil(error, "error should be nil")
591 XCTAssertNil(error, "Should be no error preparing an identity")
592 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
593 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
594 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
597 let (dict, error) = containerA.dumpSync(test: self)
598 XCTAssertNil(error, "Should be no error dumping")
599 XCTAssertNotNil(dict, "Should receive a dump dictionary")
601 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
602 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
604 let selfPeer: String? = selfInfo!["peerID"] as! String?
605 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
609 let error = containerA.localResetSync(test: self)
610 XCTAssertNil(error, "local-reset shouldn't error")
613 let (dict, error) = containerA.dumpSync(test: self)
615 XCTAssertNil(error, "Should be no error dumping")
616 XCTAssertNotNil(dict, "Should receive a dump dictionary")
618 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
619 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
621 let selfPeer: String? = selfInfo!["peerID"] as! String?
622 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
626 func testReplayAttack() throws {
627 let description = tmpStoreDescription(name: "container.db")
628 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
629 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
630 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
632 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
633 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
634 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
637 let (peerID, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
639 let state = containerA.getStateSync(test: self)
640 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
641 let secret = containerA.loadSecretSync(test: self, label: peerID!)
642 XCTAssertNotNil(secret, "secret should not be nil")
644 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
646 let state = containerB.getStateSync(test: self)
647 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
648 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
649 XCTAssertNotNil(secret, "secret should not be nil")
651 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
653 let state = containerC.getStateSync(test: self)
654 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
655 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
656 XCTAssertNotNil(secret, "secret should not be nil")
658 print("establishing A")
659 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
662 print("A vouches for B")
663 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
665 permanentInfo: bPermanentInfo!,
666 permanentInfoSig: bPermanentInfoSig!,
667 stableInfo: bStableInfo!,
668 stableInfoSig: bStableInfoSig!,
672 _ = containerB.joinSync(test: self,
673 voucherData: voucherData!,
674 voucherSig: voucherSig!,
680 _ = containerA.updateSync(test: self)
681 let earlyClock: TPCounter
683 let state = containerA.getStateSync(test: self)
684 let b = state.peers[bPeerID!]!
685 earlyClock = b.dynamicInfo!.clock
689 let snapshot = cuttlefish.state
692 print("B vouches for C")
693 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
694 permanentInfo: cPermanentInfo!,
695 permanentInfoSig: cPermanentInfoSig!,
696 stableInfo: cStableInfo!,
697 stableInfoSig: cStableInfoSig!,
701 _ = containerC.joinSync(test: self,
702 voucherData: voucherData!,
703 voucherSig: voucherSig!,
709 _ = containerB.updateSync(test: self)
712 _ = containerA.updateSync(test: self)
713 let lateClock: TPCounter
715 let state = containerA.getStateSync(test: self)
716 let b = state.peers[bPeerID!]!
717 lateClock = b.dynamicInfo!.clock
718 XCTAssertTrue(earlyClock < lateClock)
721 print("Reverting cuttlefish to the snapshot")
722 cuttlefish.state = snapshot
723 cuttlefish.makeSnapshot()
725 print("A updates, fetching the old snapshot from cuttlefish")
726 _ = containerA.updateSync(test: self)
728 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
729 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
731 let state = containerA.getStateSync(test: self)
732 let b = state.peers[bPeerID!]!
733 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
737 // TODO: need a real configurable mock cuttlefish
738 func testFetchPolicyDocuments() throws {
740 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
743 1: ("SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=",
744 "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
745 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
746 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2"),
747 3: ("SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=",
748 "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A="),
750 let (request1, data1) = policies[1]!
751 let (request3, data3) = policies[3]!
753 let description = tmpStoreDescription(name: "container.db")
754 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
757 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, keys: [:])
758 XCTAssertNil(error1, "No error querying for an empty list")
759 XCTAssertEqual(response1, [:], "Received empty dictionary")
762 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1])
763 XCTAssertNil(error2, "No error getting locally known policy document")
764 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
765 XCTAssertEqual(response2?[1]?[0], request1, "retrieved hash matches request hash")
766 XCTAssertEqual(response2?[1]?[1], data1, "retrieved data matches known data")
769 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1, 3: request3])
770 XCTAssertNil(error3, "No error fetching local + remote policy")
771 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
772 XCTAssertEqual(response3?[1]?[0], request1, "retrieved hash matches local request hash")
773 XCTAssertEqual(response3?[1]?[1], data1, "retrieved data matches local known data")
774 XCTAssertEqual(response3?[3]?[0], request3, "retrieved hash matches remote request hash")
775 XCTAssertEqual(response3?[3]?[1], data3, "retrieved data matches remote known data")
778 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash"])
779 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
780 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
783 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash",
786 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
787 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
790 func testEscrowKeys() throws {
792 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
793 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
795 let secretString = "i'm a secret!"
796 XCTAssertNotNil(secretString, "secretString should not be nil")
798 let secretData: Data? = secretString.data(using: .utf8)
799 XCTAssertNotNil(secretData, "secretData should not be nil")
801 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
802 XCTAssertNotNil(keys, "keys should not be nil")
804 XCTAssertNotNil(keys.secret, "secret should not be nil")
805 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
806 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
807 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
808 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
810 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
811 XCTAssertNotNil(hash, "hash should not be nil")
813 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
814 XCTAssertTrue(result, "result should be true")
816 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
817 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
819 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
820 XCTAssertNotNil(signingKey, "signingKey should not be nil")
821 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
822 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
825 func testEscrowKeyTestVectors() {
827 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
829 let secret = secretString.data(using: .utf8)
832 let testv1 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
833 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
835 let testv2 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
836 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
838 let testv3 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
839 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
841 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
842 let newSecret = newSecretString.data(using: .utf8)
844 let testv4 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
845 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
847 let testv5 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
848 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
850 let testv6 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
851 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
853 XCTFail("error testing escrow key test vectors \(error)")
857 func testJoiningWithBottle() throws {
858 var bottleA: BottleMO
860 let description = tmpStoreDescription(name: "container.db")
861 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
862 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
864 let machineIDs = Set(["aaa", "bbb"])
865 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
866 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
869 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
870 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
872 var state = containerA.getStateSync(test: self)
873 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
875 bottleA = state.bottles.removeFirst()
877 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
878 XCTAssertNotNil(secret, "secret should not be nil")
879 XCTAssertNil(error, "error should be nil")
881 XCTAssertNotNil(aPeerID)
882 XCTAssertNotNil(aPermanentInfo)
883 XCTAssertNotNil(aPermanentInfoSig)
885 print("establishing A")
887 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
888 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
890 XCTAssertNotNil(peerID)
891 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
894 let state = containerA.getStateSync(test: self)
895 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
896 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
898 XCTAssertNotNil(secret, "secret should not be nil")
899 XCTAssertNil(error, "error should be nil")
902 _ = containerB.updateSync(test: self)
905 let (bPeerID, _, _, _, _, error2) =
906 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
908 let state = containerB.getStateSync(test: self)
909 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
910 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
911 XCTAssertNotNil(secret, "secret should not be nil")
912 XCTAssertNil(error, "error should be nil")
917 print("B prepares to join via bottle")
919 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
922 XCTAssertNotNil(voucherData)
923 XCTAssertNotNil(voucherSig)
925 // Before B joins, there should be no TLKShares for B
926 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
929 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
931 XCTAssertEqual(peerID, bPeerID!)
933 // But afterward, it has one!
934 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
938 func testJoiningWithBottleAndEmptyBottleSalt() throws {
939 var bottleA: BottleMO
941 let description = tmpStoreDescription(name: "container.db")
942 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
943 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
945 let machineIDs = Set(["aaa", "bbb"])
946 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
947 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
950 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
951 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
953 var state = containerA.getStateSync(test: self)
954 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
956 bottleA = state.bottles.removeFirst()
958 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
959 XCTAssertNotNil(secret, "secret should not be nil")
960 XCTAssertNil(error, "error should be nil")
962 XCTAssertNotNil(aPeerID)
963 XCTAssertNotNil(aPermanentInfo)
964 XCTAssertNotNil(aPermanentInfoSig)
966 print("establishing A")
968 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
969 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
971 XCTAssertNotNil(peerID)
972 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
975 let state = containerA.getStateSync(test: self)
976 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
977 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
979 XCTAssertNotNil(secret, "secret should not be nil")
980 XCTAssertNil(error, "error should be nil")
983 _ = containerB.updateSync(test: self)
986 let (bPeerID, _, _, _, _, error2) =
987 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
989 let state = containerB.getStateSync(test: self)
990 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
991 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
992 XCTAssertNotNil(secret, "secret should not be nil")
993 XCTAssertNil(error, "error should be nil")
998 print("B prepares to join via bottle")
1000 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1002 XCTAssertNil(error3)
1003 XCTAssertNotNil(voucherData)
1004 XCTAssertNotNil(voucherSig)
1006 // Before B joins, there should be no TLKShares for B
1007 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1010 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1012 XCTAssertEqual(peerID, bPeerID!)
1014 // But afterward, it has one!
1015 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1019 func testJoiningWithWrongEscrowRecordForBottle() throws {
1021 let description = tmpStoreDescription(name: "container.db")
1022 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1023 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1025 let machineIDs = Set(["aaa", "bbb"])
1026 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1027 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1029 print("preparing A")
1030 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1031 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1033 let state = containerA.getStateSync(test: self)
1034 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1035 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1036 XCTAssertNotNil(secret, "secret should not be nil")
1037 XCTAssertNil(error, "error should be nil")
1040 XCTAssertNotNil(aPeerID)
1041 XCTAssertNotNil(aPermanentInfo)
1042 XCTAssertNotNil(aPermanentInfoSig)
1044 print("establishing A")
1046 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1048 XCTAssertNotNil(peerID)
1051 let state = containerA.getStateSync(test: self)
1052 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1053 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1055 XCTAssertNotNil(secret, "secret should not be nil")
1056 XCTAssertNil(error, "error should be nil")
1059 _ = containerB.updateSync(test: self)
1061 print("preparing B")
1062 let (bPeerID, _, _, _, _, error2) =
1063 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1065 let state = containerB.getStateSync(test: self)
1066 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1067 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1068 XCTAssertNotNil(secret, "secret should not be nil")
1069 XCTAssertNil(error, "error should be nil")
1071 XCTAssertNil(error2)
1074 print("B joins via bottle")
1076 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1078 XCTAssertNotNil(error3)
1079 XCTAssertNil(voucherData)
1080 XCTAssertNil(voucherSig)
1084 func testJoiningWithWrongBottle() throws {
1085 var bottleB: BottleMO
1087 let description = tmpStoreDescription(name: "container.db")
1088 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1089 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1091 let machineIDs = Set(["aaa", "bbb"])
1092 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1093 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1095 print("preparing A")
1096 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1097 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1099 let state = containerA.getStateSync(test: self)
1100 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1101 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1102 XCTAssertNotNil(secret, "secret should not be nil")
1103 XCTAssertNil(error, "error should be nil")
1106 XCTAssertNotNil(aPeerID)
1107 XCTAssertNotNil(aPermanentInfo)
1108 XCTAssertNotNil(aPermanentInfoSig)
1110 print("establishing A")
1112 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1114 XCTAssertNotNil(peerID)
1117 let state = containerA.getStateSync(test: self)
1118 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1119 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1121 XCTAssertNotNil(secret, "secret should not be nil")
1122 XCTAssertNil(error, "error should be nil")
1125 print("preparing B")
1126 let (bPeerID, _, _, _, _, error2) =
1127 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1129 var state = containerB.getStateSync(test: self)
1130 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1131 bottleB = state.bottles.removeFirst()
1132 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1133 XCTAssertNotNil(secret, "secret should not be nil")
1134 XCTAssertNil(error, "error should be nil")
1136 XCTAssertNil(error2)
1139 print("B joins via bottle")
1141 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1143 XCTAssertNotNil(error3)
1144 XCTAssertNil(voucherData)
1145 XCTAssertNil(voucherSig)
1149 func testJoiningWithBottleAndBadSalt() throws {
1150 var bottleA: BottleMO
1152 let description = tmpStoreDescription(name: "container.db")
1153 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1154 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1156 let machineIDs = Set(["aaa", "bbb"])
1157 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1158 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1160 print("preparing A")
1161 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1162 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1164 var state = containerA.getStateSync(test: self)
1165 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1166 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1167 bottleA = state.bottles.removeFirst()
1168 XCTAssertNotNil(secret, "secret should not be nil")
1169 XCTAssertNil(error, "error should be nil")
1172 XCTAssertNotNil(aPeerID)
1173 XCTAssertNotNil(aPermanentInfo)
1174 XCTAssertNotNil(aPermanentInfoSig)
1176 print("establishing A")
1178 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1180 XCTAssertNotNil(peerID)
1183 let state = containerA.getStateSync(test: self)
1184 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1185 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1187 XCTAssertNotNil(secret, "secret should not be nil")
1188 XCTAssertNil(error, "error should be nil")
1191 _ = containerB.updateSync(test: self)
1193 print("preparing B")
1194 let (bPeerID, _, _, _, _, error2) =
1195 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1197 let state = containerB.getStateSync(test: self)
1198 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1199 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1200 XCTAssertNotNil(secret, "secret should not be nil")
1201 XCTAssertNil(error, "error should be nil")
1203 XCTAssertNil(error2)
1206 print("B joins via bottle")
1208 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1210 XCTAssertNotNil(error3)
1211 XCTAssertNil(voucherData)
1212 XCTAssertNil(voucherSig)
1216 func testJoiningWithBottleAndBadSecret() throws {
1217 var bottleA: BottleMO
1218 let description = tmpStoreDescription(name: "container.db")
1219 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1220 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1222 let machineIDs = Set(["aaa", "bbb"])
1223 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1224 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1226 print("preparing A")
1227 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1228 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1230 var state = containerA.getStateSync(test: self)
1231 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1232 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1233 bottleA = state.bottles.removeFirst()
1234 XCTAssertNotNil(secret, "secret should not be nil")
1235 XCTAssertNil(error, "error should be nil")
1238 XCTAssertNotNil(aPeerID)
1239 XCTAssertNotNil(aPermanentInfo)
1240 XCTAssertNotNil(aPermanentInfoSig)
1242 print("establishing A")
1244 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1246 XCTAssertNotNil(peerID)
1249 let state = containerA.getStateSync(test: self)
1250 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1251 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1252 XCTAssertNotNil(secret, "secret should not be nil")
1253 XCTAssertNil(error, "error should be nil")
1256 _ = containerB.updateSync(test: self)
1258 print("preparing B")
1259 let (bPeerID, _, _, _, _, error2) =
1260 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1262 let state = containerB.getStateSync(test: self)
1263 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1264 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1265 XCTAssertNotNil(secret, "secret should not be nil")
1266 XCTAssertNil(error, "error should be nil")
1268 XCTAssertNil(error2)
1271 print("B joins via bottle")
1273 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1275 XCTAssertNotNil(error3)
1276 XCTAssertNil(voucherData)
1277 XCTAssertNil(voucherSig)
1281 func testJoiningWithNoFetchAllBottles() throws {
1282 var bottleA: BottleMO
1284 let description = tmpStoreDescription(name: "container.db")
1285 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1286 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1288 let machineIDs = Set(["aaa", "bbb"])
1289 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1290 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1292 print("preparing A")
1293 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1294 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1296 var state = containerA.getStateSync(test: self)
1297 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1298 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1300 bottleA = state.bottles.removeFirst()
1301 XCTAssertNotNil(secret, "secret should not be nil")
1302 XCTAssertNil(error, "error should be nil")
1305 XCTAssertNotNil(aPeerID)
1306 XCTAssertNotNil(aPermanentInfo)
1307 XCTAssertNotNil(aPermanentInfoSig)
1309 print("establishing A")
1311 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1313 XCTAssertNotNil(peerID)
1316 let state = containerA.getStateSync(test: self)
1317 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1318 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1319 XCTAssertNotNil(secret, "secret should not be nil")
1320 XCTAssertNil(error, "error should be nil")
1323 print("preparing B")
1324 let (bPeerID, _, _, _, _, error2) =
1325 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1327 let state = containerB.getStateSync(test: self)
1328 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1329 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1330 XCTAssertNotNil(secret, "secret should not be nil")
1331 XCTAssertNil(error, "error should be nil")
1333 XCTAssertNil(error2)
1336 print("B joins via bottle")
1338 // And the first container fetches again, which should succeed
1339 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1340 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1341 self.cuttlefish.fetchViableBottlesError.append(ckError)
1343 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1345 XCTAssertNotNil(error3)
1346 XCTAssertNil(voucherData)
1347 XCTAssertNil(voucherSig)
1351 func testJoinByPreapproval() throws {
1352 let description = tmpStoreDescription(name: "container.db")
1353 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1354 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1356 let machineIDs = Set(["aaa", "bbb"])
1357 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1358 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1360 print("preparing A")
1361 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1362 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1364 XCTAssertNotNil(aPeerID)
1365 XCTAssertNotNil(aPermanentInfo)
1366 XCTAssertNotNil(aPermanentInfoSig)
1368 print("preparing B")
1369 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, error2) =
1370 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1372 let state = containerB.getStateSync(test: self)
1373 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1374 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1375 XCTAssertNotNil(secret, "secret should not be nil")
1376 XCTAssertNil(error, "error should be nil")
1378 XCTAssertNil(error2)
1379 XCTAssertNotNil(bPeerID)
1380 XCTAssertNotNil(bPermanentInfo)
1381 XCTAssertNotNil(bPermanentInfoSig)
1383 // Now, A establishes preapproving B
1384 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1386 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1387 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1389 print("establishing A")
1391 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
1393 XCTAssertNotNil(peerID)
1397 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1399 print("B joins by preapproval, and uploads all TLKShares that it has")
1400 let (bJoinedPeerID, _, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
1401 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1402 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1404 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1407 _ = containerA.dumpSync(test: self)
1408 _ = containerB.dumpSync(test: self)
1411 func testDepart() throws {
1412 let description = tmpStoreDescription(name: "container.db")
1413 let (container, peerID) = try establish(reload: false, store: description)
1415 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1416 assertDistrusts(context: container, peerIDs: [peerID])
1419 func testDistrustPeers() throws {
1420 let store = tmpStoreDescription(name: "container.db")
1421 let (c, peerID1) = try establish(reload: false, store: store)
1423 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1424 containerID: "second",
1426 machineIDs: ["aaa", "bbb", "ccc"],
1429 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1430 containerID: "third",
1432 machineIDs: ["aaa", "bbb", "ccc"],
1435 let (_, cUpdateError) = c.updateSync(test: self)
1436 XCTAssertNil(cUpdateError, "Should be able to update first container")
1437 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1439 // You can't distrust yourself via peerID.
1440 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1441 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1443 // Passing in nonsense should error too
1444 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1446 // Now, distrust both peers.
1447 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1448 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1450 // peers should accept their fates
1451 let (_, c2UpdateError) = c2.updateSync(test: self)
1452 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1453 assertDistrusts(context: c2, peerIDs: [peerID2])
1455 let (_, c3UpdateError) = c3.updateSync(test: self)
1456 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1457 assertDistrusts(context: c3, peerIDs: [peerID3])
1460 func testFetchWithBadChangeToken() throws {
1461 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1463 // But all that goes away, and a new peer establishes
1464 self.cuttlefish.state = FakeCuttlefishServer.State()
1465 let (_, peerID2) = try establish(reload: false, contextID: "second", store: tmpStoreDescription(name: "container-peer2.db"))
1467 // And the first container fetches again, which should succeed
1468 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1469 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1470 self.cuttlefish.nextFetchErrors.append(ckError)
1471 _ = c.updateSync(test: self)
1473 // and c's model should only include peerID2
1474 c.moc.performAndWait {
1475 let modelPeers = c.model.allPeerIDs()
1476 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1477 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1478 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1482 func testFetchEscrowContents() throws {
1483 let description = tmpStoreDescription(name: "container.db")
1484 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1485 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1486 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1487 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1488 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1489 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1490 XCTAssertNil(spkiA, "Should not have an SPKI")
1492 let (c, peerID) = try establish(reload: false, store: description)
1493 XCTAssertNotNil(peerID, "establish should return a peer id")
1495 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1496 XCTAssertNil(error, "Should be no error fetching escrow contents")
1497 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1498 XCTAssertNotNil(bottleID, "Should have a bottleID")
1499 XCTAssertNotNil(spki, "Should have an SPKI")
1502 func testBottles() {
1504 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1505 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1506 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1508 let keys = bottle.escrowKeys
1509 XCTAssertNotNil(keys, "keys should not be nil")
1511 XCTAssertNotNil(bottle, "bottle should not be nil")
1513 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1515 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1516 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1518 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1520 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1521 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1523 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1524 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1526 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1527 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1529 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1532 XCTFail("error testing bottles \(error)")
1536 func testFetchBottles() throws {
1537 let store = tmpStoreDescription(name: "container.db")
1538 let (c, _) = try establish(reload: false, store: store)
1540 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1541 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1542 XCTAssertNotNil(bottles, "should have fetched some bottles")
1543 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1546 let state = c.getStateSync(test: self)
1547 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1550 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1552 let state = c2.getStateSync(test: self)
1553 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1556 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1557 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1558 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1559 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1562 let state = c2.getStateSync(test: self)
1563 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1567 func testTrustStatus() throws {
1568 let store = tmpStoreDescription(name: "container.db")
1570 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
1571 persistentStoreDescription: store,
1572 cuttlefish: self.cuttlefish)
1573 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
1574 XCTAssertNil(precStatusError, "No error fetching status")
1575 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
1576 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
1577 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
1578 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
1580 let (c, _) = try establish(reload: false, store: store)
1581 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1582 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1583 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1584 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1585 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1586 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1588 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
1589 persistentStoreDescription: store,
1590 cuttlefish: self.cuttlefish)
1592 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
1593 XCTAssertNil(statusError, "No error fetching status")
1594 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
1595 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
1596 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1597 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
1600 func testTrustStatusWhenMissingIdentityKeys() throws {
1601 let store = tmpStoreDescription(name: "container.db")
1602 let (c, _) = try establish(reload: false, store: store)
1603 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1604 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1605 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1606 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1607 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1608 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1610 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
1611 XCTAssertTrue(result, "result should be true")
1613 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
1614 XCTAssertNotNil(distrustedError, "error should not be nil")
1615 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
1616 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
1619 func testSetRecoveryKey() throws {
1620 let store = tmpStoreDescription(name: "container.db")
1622 let c = try Container(name: ContainerName(container: "c", context: "context"),
1623 persistentStoreDescription: store,
1624 cuttlefish: self.cuttlefish)
1626 let machineIDs = Set(["aaa", "bbb", "ccc"])
1627 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1629 print("preparing peer A")
1630 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1631 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1633 let state = c.getStateSync(test: self)
1634 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1635 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1636 XCTAssertNotNil(secret, "secret should not be nil")
1637 XCTAssertNil(error, "error should be nil")
1640 XCTAssertNotNil(aPeerID)
1641 XCTAssertNotNil(aPermanentInfo)
1642 XCTAssertNotNil(aPermanentInfoSig)
1644 print("establishing A")
1646 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1648 XCTAssertNotNil(peerID)
1650 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1651 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1653 let (setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1654 XCTAssertNil(setRecoveryError, "error should be nil")
1657 func roundTripThroughSetValueTransformer(set: Set<String>) {
1658 let t = SetValueTransformer()
1660 let transformedSet = t.transformedValue(set) as? Data
1661 XCTAssertNotNil(transformedSet, "SVT should return some data")
1663 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
1664 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
1666 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
1669 func testSetValueTransformer() {
1670 roundTripThroughSetValueTransformer(set: Set<String>([]))
1671 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
1672 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
1675 func testGetRepairSuggestion() throws {
1676 let store = tmpStoreDescription(name: "container.db")
1678 let c = try Container(name: ContainerName(container: "c", context: "context"),
1679 persistentStoreDescription: store,
1680 cuttlefish: self.cuttlefish)
1682 let machineIDs = Set(["aaa", "bbb", "ccc"])
1683 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1685 print("preparing peer A")
1686 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1687 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1689 let state = c.getStateSync(test: self)
1690 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1691 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1692 XCTAssertNotNil(secret, "secret should not be nil")
1693 XCTAssertNil(error, "error should be nil")
1696 XCTAssertNotNil(aPeerID)
1697 XCTAssertNotNil(aPermanentInfo)
1698 XCTAssertNotNil(aPermanentInfoSig)
1700 print("establishing A")
1702 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1704 XCTAssertNotNil(peerID)
1706 let (repairAccount, repairEscrow, resetOctagon, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
1707 XCTAssertEqual(repairAccount, false, "")
1708 XCTAssertEqual(repairEscrow, false, "")
1709 XCTAssertEqual(resetOctagon, false, "")
1710 XCTAssertNil(healthError)
1713 func testFetchChangesFailDuringVouchWithBottle() throws {
1714 var bottleA: BottleMO
1716 let description = tmpStoreDescription(name: "container.db")
1717 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1718 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1720 let machineIDs = Set(["aaa", "bbb"])
1721 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1722 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1724 print("preparing A")
1725 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1726 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1728 var state = containerA.getStateSync(test: self)
1729 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1731 bottleA = state.bottles.removeFirst()
1733 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1734 XCTAssertNotNil(secret, "secret should not be nil")
1735 XCTAssertNil(error, "error should be nil")
1737 XCTAssertNotNil(aPeerID)
1738 XCTAssertNotNil(aPermanentInfo)
1739 XCTAssertNotNil(aPermanentInfoSig)
1741 print("establishing A")
1743 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1744 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1746 XCTAssertNotNil(peerID)
1747 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1750 let state = containerA.getStateSync(test: self)
1751 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1752 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1754 XCTAssertNotNil(secret, "secret should not be nil")
1755 XCTAssertNil(error, "error should be nil")
1758 _ = containerB.updateSync(test: self)
1760 print("preparing B")
1761 let (bPeerID, _, _, _, _, error2) =
1762 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1764 let state = containerB.getStateSync(test: self)
1765 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1766 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1767 XCTAssertNotNil(secret, "secret should not be nil")
1768 XCTAssertNil(error, "error should be nil")
1770 XCTAssertNil(error2)
1773 print("B prepares to join via bottle")
1775 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1777 XCTAssertNil(error3)
1778 XCTAssertNotNil(voucherData)
1779 XCTAssertNotNil(voucherSig)
1781 // And the first container fetches again, which should succeed
1782 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1783 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1784 self.cuttlefish.nextFetchErrors.append(ckError)
1786 // Before B joins, there should be no TLKShares for B
1787 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1790 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1791 XCTAssertNotNil(error)
1792 XCTAssertNil(peerID)
1796 func testDistrustedPeerRecoveryKeyNotSet() throws {
1797 let description = tmpStoreDescription(name: "container.db")
1798 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1799 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1802 let machineIDs = Set(["aaa", "bbb"])
1803 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1804 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1806 print("preparing peer A")
1807 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, error) =
1808 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1810 let state = containerA.getStateSync(test: self)
1811 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1812 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1813 XCTAssertNotNil(secret, "secret should not be nil")
1814 XCTAssertNil(error, "error should be nil")
1817 XCTAssertNotNil(aPeerID)
1818 XCTAssertNotNil(aPermanentInfo)
1819 XCTAssertNotNil(aPermanentInfoSig)
1820 XCTAssertNotNil(aStableInfo)
1821 XCTAssertNotNil(aStableInfoSig)
1823 print("establishing A")
1825 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1827 XCTAssertNotNil(peerID)
1830 print("preparing B")
1831 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
1832 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1834 let state = containerB.getStateSync(test: self)
1835 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1836 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1837 XCTAssertNotNil(secret, "secret should not be nil")
1838 XCTAssertNil(error, "error should be nil")
1840 XCTAssertNil(error2)
1841 XCTAssertNotNil(bPeerID)
1842 XCTAssertNotNil(bPermanentInfo)
1843 XCTAssertNotNil(bPermanentInfoSig)
1846 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1847 print("A vouches for B, but doesn't provide any TLKShares")
1848 let (_, _, errorVouchingWithoutTLKs) =
1849 containerA.vouchSync(test: self,
1851 permanentInfo: bPermanentInfo!,
1852 permanentInfoSig: bPermanentInfoSig!,
1853 stableInfo: bStableInfo!,
1854 stableInfoSig: bStableInfoSig!,
1856 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
1857 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1859 print("A vouches for B, but doesn't only has provisional TLKs at the time")
1860 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1861 provisionalManateeKeySet.newUpload = true
1863 let (_, _, errorVouchingWithProvisionalTLKs) =
1864 containerA.vouchSync(test: self,
1866 permanentInfo: bPermanentInfo!,
1867 permanentInfoSig: bPermanentInfoSig!,
1868 stableInfo: bStableInfo!,
1869 stableInfoSig: bStableInfoSig!,
1870 ckksKeys: [provisionalManateeKeySet])
1871 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
1872 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1874 print("A vouches for B")
1875 let (voucherData, voucherSig, error3) =
1876 containerA.vouchSync(test: self,
1878 permanentInfo: bPermanentInfo!,
1879 permanentInfoSig: bPermanentInfoSig!,
1880 stableInfo: bStableInfo!,
1881 stableInfoSig: bStableInfoSig!,
1882 ckksKeys: [self.manateeKeySet])
1883 XCTAssertNil(error3)
1884 XCTAssertNotNil(voucherData)
1885 XCTAssertNotNil(voucherSig)
1887 // As part of the vouch, A should have uploaded a tlkshare for B
1888 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1891 let (peerID, _, error) = containerB.joinSync(test: self,
1892 voucherData: voucherData!,
1893 voucherSig: voucherSig!,
1897 XCTAssertEqual(peerID, bPeerID!)
1902 let (_, error) = containerA.updateSync(test: self)
1907 let (_, error) = containerB.updateSync(test: self)
1911 // Now, A distrusts B.
1912 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
1913 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
1916 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1917 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1919 let (setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1920 XCTAssertNil(setRecoveryError, "error should be nil")
1924 let (_, error) = containerA.updateSync(test: self)
1929 let (_, error) = containerB.updateSync(test: self)
1934 let (dict, error) = containerA.dumpSync(test: self)
1936 XCTAssertNil(error, "Should be no error dumping")
1937 XCTAssertNotNil(dict, "Should receive a dump dictionary")
1939 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
1940 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
1942 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
1943 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
1945 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
1946 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
1948 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
1949 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
1954 func assert(container: Container,
1955 allowedMachineIDs: Set<String>,
1956 disallowedMachineIDs: Set<String>,
1957 unknownMachineIDs: Set<String> = Set(),
1958 persistentStore: NSPersistentStoreDescription,
1959 cuttlefish: FakeCuttlefishServer) throws {
1961 let midList = container.onqueueCurrentMIDList()
1962 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
1963 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
1964 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
1966 // if we reload the container, does it still match?
1967 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
1969 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
1970 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
1971 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
1972 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
1975 func testAllowListManipulation() throws {
1976 let description = tmpStoreDescription(name: "container.db")
1977 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1979 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1982 XCTAssertNotNil(peerID)
1983 XCTAssertNotNil(permanentInfo)
1984 XCTAssertNotNil(permanentInfoSig)
1986 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
1988 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
1989 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
1990 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
1992 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"]), "should be able to set allowed machine IDs")
1993 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
1994 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
1996 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
1997 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
1998 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2000 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2001 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2002 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2003 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2005 // once they're unknown, a full list set will make them disallowed
2006 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"]), "should be able to set allowed machine IDs")
2007 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2008 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2010 // Resetting the list to what it is doesn't change the list
2011 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], listDifference: false), "should be able to set allowed machine IDs")
2012 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2013 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2015 // But changing it to something completely new does
2016 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"]), "should be able to set allowed machine IDs")
2017 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2018 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2020 // And, readding a previously disallowed machine ID works too
2021 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"]), "should be able to set allowed machine IDs")
2022 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2023 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2025 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2026 let (_, updateError) = container.updateSync(test: self)
2027 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2028 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2030 let (_, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2031 XCTAssertNil(establishError, "Should be able to establish() with no error")
2032 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2033 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2035 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2036 let (_, updateError2) = container.updateSync(test: self)
2037 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2038 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2039 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2042 func testAllowListManipulationWithAddsAndRemoves() throws {
2043 let description = tmpStoreDescription(name: "container.db")
2044 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2046 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2048 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2049 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2050 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2052 // Now, an 'add' comes in for some peers
2053 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2054 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2055 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2057 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2058 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2059 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2060 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2062 // And a remove comes in for E. It becomes 'unknown'
2063 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2064 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2065 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2067 // and a list set after the remove confirms the removal
2068 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2069 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2070 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2072 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2073 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], listDifference: false), "should be able to set allowed machine IDs")
2074 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2075 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2077 // And another list set no longer includes D, so it should now be disallowed
2078 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2079 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2080 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2082 // And just to check the 48 hour boundary...
2083 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2084 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: false), "should be able to set allowed machine IDs")
2085 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2087 container.moc.performAndWait {
2088 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2090 knownMachineMOs.forEach {
2091 if $0.machineID == "xxx" {
2092 $0.modified = Date(timeIntervalSinceNow: -60*60*72)
2096 try! container.moc.save()
2099 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2101 // Setting the list again should kick out X, since it was 'added' too long ago
2102 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2103 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2106 func testAllowSetUpgrade() throws {
2107 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2109 let description = tmpStoreDescription(name: "container.db")
2110 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2111 let mom = getOrMakeModel(url: url)
2112 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2113 persistentContainer.persistentStoreDescriptions = [description]
2115 persistentContainer.loadPersistentStores { _, error in
2116 XCTAssertNil(error, "Should be no error loading persistent stores")
2119 let moc = persistentContainer.newBackgroundContext()
2120 moc.performAndWait {
2121 let containerMO = ContainerMO(context: moc)
2122 containerMO.name = containerName.asSingleString()
2123 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2125 XCTAssertNoThrow(try! moc.save())
2128 // Now TPH boots up with a preexisting model
2129 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2131 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2134 XCTAssertNotNil(peerID)
2135 XCTAssertNotNil(permanentInfo)
2136 XCTAssertNotNil(permanentInfoSig)
2138 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2140 // Setting a new list should work fine
2141 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"]), "should be able to set allowed machine IDs")
2142 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2144 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2147 func testAllowStatusUpgrade() throws {
2148 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2150 let description = tmpStoreDescription(name: "container.db")
2151 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2152 let mom = getOrMakeModel(url: url)
2153 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2154 persistentContainer.persistentStoreDescriptions = [description]
2156 persistentContainer.loadPersistentStores { _, error in
2157 XCTAssertNil(error, "Should be no error loading persistent stores")
2160 let moc = persistentContainer.newBackgroundContext()
2161 moc.performAndWait {
2162 let containerMO = ContainerMO(context: moc)
2163 containerMO.name = containerName.asSingleString()
2165 let machine = MachineMO(context: moc)
2166 machine.allowed = true
2167 machine.modified = Date()
2168 machine.machineID = "aaa"
2169 containerMO.addToMachines(machine)
2171 let machineB = MachineMO(context: moc)
2172 machineB.allowed = false
2173 machineB.modified = Date()
2174 machineB.machineID = "bbb"
2175 containerMO.addToMachines(machineB)
2180 // Now TPH boots up with a preexisting model
2181 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2182 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2184 // Setting a new list should work fine
2185 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"]), "should be able to set allowed machine IDs")
2186 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2188 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2191 func testMachineIDListSetWithUnknownMachineIDs() throws {
2192 let description = tmpStoreDescription(name: "container.db")
2193 let (container, _) = try establish(reload: false, store: description)
2195 container.moc.performAndWait {
2196 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2198 knownMachineMOs.forEach {
2199 container.containerMO.removeFromMachines($0)
2202 try! container.moc.save()
2205 // and set the machine ID list to something that doesn't include 'aaa'
2206 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2207 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2208 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2210 // But check that it exists, and set its modification date to a while ago for an upcoming test
2211 container.moc.performAndWait {
2212 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2214 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2215 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2217 let aaaMO = aaaMOs.first!
2218 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2219 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2221 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2222 try! container.moc.save()
2225 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2226 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2228 // Setting it again is fine...
2229 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2230 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2232 // And doesn't reset the modified date on the record
2233 container.moc.performAndWait {
2234 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2236 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2237 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2239 let aaaMO = aaaMOs.first!
2240 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2241 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2243 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2246 // And can be promoted to 'allowed'
2247 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2248 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2249 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2252 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2253 let description = tmpStoreDescription(name: "container.db")
2254 let (container, _) = try establish(reload: false, store: description)
2256 container.moc.performAndWait {
2257 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2259 knownMachineMOs.forEach {
2260 container.containerMO.removeFromMachines($0)
2263 try! container.moc.save()
2266 // and set the machine ID list to something that doesn't include 'aaa'
2267 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2268 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2269 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2271 // But an entry for "aaa" should exist, as a peer in the model claims it as their MID
2272 container.moc.performAndWait {
2273 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2275 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2276 XCTAssertEqual(aaaMOs.count, 1, "Should have one machine MO for aaa")
2278 let aaaMO = aaaMOs.first!
2279 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2280 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2282 // Pretend that aaa was added 49 hours ago
2283 aaaMO.modified = Date(timeIntervalSinceNow: -60*60*49)
2284 try! container.moc.save()
2287 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2289 // And, setting the list again should disallow aaa, since it is so old
2290 // Note that this _should_ return a list difference, since A is promoted to disallowed
2291 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2292 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2293 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2295 // Setting ths list again has no change
2296 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2297 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2298 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2300 // But A can appear again, no problem.
2301 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2302 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2303 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2306 func testMachineIDListHandlingWithPeers() throws {
2307 let description = tmpStoreDescription(name: "container.db")
2308 let (container, peerID1) = try establish(reload: false, store: description)
2310 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2312 let unknownMachineID = "not-on-list"
2313 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2314 containerID: "second",
2315 machineID: unknownMachineID,
2316 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]),
2319 // And the first container accepts the join...
2320 let (_, cUpdateError) = container.updateSync(test: self)
2321 XCTAssertNil(cUpdateError, "Should be able to update first container")
2322 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2324 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2327 func testContainerAndModelConsistency() throws{
2329 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2330 let description = tmpStoreDescription(name: "container.db")
2331 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2332 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2334 XCTAssertNotNil(peerID)
2335 XCTAssertNotNil(permanentInfo)
2336 XCTAssertNotNil(permanentInfoSig)
2337 XCTAssertNotNil(stableInfo)
2338 XCTAssertNotNil(stableInfoSig)
2340 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2341 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2342 let mom = getOrMakeModel(url: url)
2343 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2344 persistentContainer.persistentStoreDescriptions = [description]
2346 persistentContainer.loadPersistentStores { _, error in
2347 XCTAssertNil(error, "Should be no error loading persistent stores")
2350 let moc = persistentContainer.newBackgroundContext()
2351 moc.performAndWait {
2352 let containerMO = ContainerMO(context: moc)
2353 containerMO.name = containerName.asSingleString()
2354 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
2355 containerMO.egoPeerID = peerID
2356 containerMO.egoPeerPermanentInfo = permanentInfo
2357 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
2358 containerMO.egoPeerStableInfoSig = stableInfoSig
2359 containerMO.egoPeerStableInfo = stableInfo
2360 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
2362 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
2363 let info3: TPPeerStableInfo = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2,
2364 policyVersion:containerEgoStableInfo!.policyVersion,
2365 policyHash:containerEgoStableInfo!.policyHash,
2366 policySecrets:containerEgoStableInfo!.policySecrets,
2367 deviceName:containerEgoStableInfo!.deviceName,
2368 serialNumber:containerEgoStableInfo!.serialNumber,
2369 osVersion:containerEgoStableInfo!.osVersion,
2370 signing:peerKeys.signingKey,
2371 recoverySigningPubKey:containerEgoStableInfo!.recoverySigningPublicKey,
2372 recoveryEncryptionPubKey:containerEgoStableInfo!.recoveryEncryptionPublicKey,
2375 //setting the containerMO's ego stable info to an old clock
2376 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
2377 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
2379 //now we are adding the ego stable info with a clock of 3 to the list of peers
2380 let peer = PeerMO(context: moc)
2381 peer.peerID = peerID
2382 peer.permanentInfo = permanentInfo
2383 peer.permanentInfoSig = permanentInfoSig
2384 peer.stableInfo = info3.data
2385 peer.stableInfoSig = info3.sig
2386 peer.isEgoPeer = true
2387 peer.container = containerMO
2389 containerMO.addToPeers(peer)
2391 //at this point the containerMO's egoStableInfo should have a clock of 1
2392 //the saved ego peer in the peer list has a clock of 3
2395 XCTFail("load ego keys failed: \(error)")
2397 XCTAssertNoThrow(try! moc.save())
2400 // Now TPH boots up with a preexisting model
2401 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2403 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
2404 XCTAssertNotNil(stableInfoAfterBoot)
2406 //after boot the clock should be updated to the one that was saved in the model
2407 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")