2 // TrustedPeersHelperUnitTests.swift
3 // TrustedPeersHelperUnitTests
5 // Created by Ben Williamson on 5/1/18.
8 import CloudKitCodeProtobuf
12 let testDSID = "123456789"
14 let signingKey_384 = Data(base64Encoded: "BOQbPoiBnzuA0Cgc2QegjKGJqDtpkRenHwAxkYKJH1xELdaoIh8ifSch8sl18tpBYVUpEfdxz2ZSKif+dx7UPfu8WeTtpHkqm3M+9PTjr/KNNJCSR1PQNB5Jh+sRiQ+cpJnoTzm+IZSIukylamAcL3eA0nMUM0Zc2u4TijrbTgVND22WzSirUkwSK3mA/prk9A==")
16 let encryptionKey_384 = Data(base64Encoded: "BE1RuazBWmSEx0XVGhobbrdSE6fRQOrUrYEQnBkGl4zJq9GCeRoYvbuWNYFcOH0ijCRz9pYILsTn3ajT1OknlvcKmuQ7SeoGWzk9cBZzT5bBEwozn2gZxn80DQoDkmejywlH3D0/cuV6Bxexu5KMAFGqg6eN6th4sQABL5EuI9zKPuxHStM/b9B1LyqcnRKQHA==")
18 let symmetricKey_384 = Data(base64Encoded: "MfHje3Y/mWV0q+grjwZ4VxuqB7OreYHLxYkeeCiNjjY=")
20 let recovery_signingKey_384 = Data(base64Encoded: "BK5nrmP6oitJHtGV2Josk5cUKnG3pqxgEP8uzyPtNXgAMNHZoDKwCKFXpUzQSgbYiR4G2XZY2Q0+qSCKN7YSY2KNKE0hM9p4GvABBmAWKW/O9eFd5ugKQWisn25a/7nieIw8CQ81hBDR7R/vBpfLVtzE8ieRA8JPGqulQ5RdLcClFrD3B8BPJAZpLv4OP1CLDA==")
22 let recovery_encryptionKey_384 = Data(base64Encoded: "BKkZpYHTbMi2yrWFo+ErM3HbcYJCngPuWDYoVUD7egKkmiHFvv1Bsk0j/Dcj3xTR12vj5QOpZQV3GzE5estf75BV+EZz1cjUUSi/MysfpKsqEbwYrhIEkmeyMGr7CVWQWRLR2LnoihnQajvWi1LmO0AoDl3+LzVgTJBjjDQ5ANyw0Yv1EgOgBvZsLA9UTN4oAg==")
24 class TrustedPeersHelperUnitTests: XCTestCase {
27 var cuttlefish: FakeCuttlefishServer!
29 var manateeKeySet: CKKSKeychainBackedKeySet!
31 override static func setUp() {
34 SecTapToRadar.disableTTRsEntirely()
36 // Turn on NO_SERVER stuff
37 securityd_init_local_spi()
42 override func setUp() {
45 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
46 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
48 // Make a new fake keychain
49 tmpPath = String(format: "/tmp/%@-%X", testName, Int.random(in: 0..<1000000))
50 tmpURL = URL(fileURLWithPath: tmpPath, isDirectory: true)
52 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
53 SetCustomHomeURLString(tmpPath as CFString)
54 SecKeychainDbReset(nil)
56 XCTFail("setUp failed: \(error)")
59 // Actually load the database.
60 kc_with_dbt(true, nil) { _ in
64 // Now that the keychain is alive, perform test setup
66 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
68 XCTFail("Creation of fake key hierarchies failed: \(error)")
72 override func tearDown() {
73 // Put teardown code here. This method is called after the invocation of each test method in the class.
76 if let nskeychainDir : NSURL = SecCopyHomeURL(), let keychainDir : URL = nskeychainDir as URL? {
77 SecItemDataSourceFactoryReleaseAll()
78 SecKeychainDbForceClose()
79 SecKeychainDbReset(nil)
81 // Only perform the destructive step if the url matches what we expect!
82 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
83 if keychainDir.path.hasPrefix("/tmp/" + testName) {
85 try FileManager.default.removeItem(at: keychainDir)
87 print("Failed to remove keychain directory: \(error)")
95 func makeFakeKeyHierarchy(zoneID: CKRecordZone.ID) throws -> CKKSKeychainBackedKeySet {
96 // Remember, these keys come into TPH having round-tripped through an NSEncoding
97 let tlk = try CKKSKeychainBackedKey.randomKeyWrapped(bySelf: zoneID)
98 let classA = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassA)
99 let classC = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassC)
101 XCTAssertNoThrow(try tlk.saveMaterialToKeychain(), "Should be able to save TLK to keychain")
102 XCTAssertNoThrow(try classA.saveMaterialToKeychain(), "Should be able to save classA key to keychain")
103 XCTAssertNoThrow(try classC.saveMaterialToKeychain(), "Should be able to save classC key to keychain")
105 let tlkData = try NSKeyedArchiver.archivedData(withRootObject: tlk, requiringSecureCoding: true)
106 let classAData = try NSKeyedArchiver.archivedData(withRootObject: classA, requiringSecureCoding: true)
107 let classCData = try NSKeyedArchiver.archivedData(withRootObject: classC, requiringSecureCoding: true)
109 let decodedTLK = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: tlkData) as! CKKSKeychainBackedKey
110 let decodedClassA = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classAData) as! CKKSKeychainBackedKey
111 let decodedClassC = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classCData) as! CKKSKeychainBackedKey
113 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
116 func assertTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
117 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
118 tlkShare.receiver == peerID &&
119 tlkShare.keyUuid == keyUUID
122 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
125 func assertNoTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
126 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
127 tlkShare.receiver == peerID &&
128 tlkShare.keyUuid == keyUUID
131 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
134 func assertTrusts(context: Container, peerIDs: [String]) {
135 let state = context.getStateSync(test: self)
136 guard let egoPeerID = state.egoPeerID else {
137 XCTFail("context should have an ego peer ID")
141 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
142 XCTFail("No dynamicInfo for ego peer")
147 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
148 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
152 func assertDistrusts(context: Container, peerIDs: [String]) {
153 let state = context.getStateSync(test: self)
154 guard let egoPeerID = state.egoPeerID else {
155 XCTFail("context should have an ego peer ID")
159 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
160 XCTFail("No dynamicInfo for ego peer")
165 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
166 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
170 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
171 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
172 return NSPersistentStoreDescription(url: tmpStoreURL)
175 func establish(reload: Bool,
176 store: NSPersistentStoreDescription) throws -> (Container, String) {
177 return try self.establish(reload: reload, contextID: OTDefaultContext, accountIsDemo: false, store: store)
180 func establish(reload: Bool,
182 allowedMachineIDs: Set<String> = Set(["aaa", "bbb", "ccc"]),
184 modelID: String = "iPhone1,1",
185 syncUserControllableViews: TPPBPeerStableInfo_UserControllableViewStatus = .UNKNOWN,
186 store: NSPersistentStoreDescription) throws -> (Container, String) {
187 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
189 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, accountIsDemo: accountIsDemo, listDifference: !allowedMachineIDs.isEmpty), "should be able to set allowed machine IDs")
191 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self,
194 bottleSalt: "123456789",
195 bottleID: UUID().uuidString,
197 syncUserControllableViews: syncUserControllableViews)
200 let state = container.getStateSync(test: self)
201 XCTAssertTrue(state.bottles.contains { $0.peerID == peerID }, "should have a bottle for peer")
202 let secret = container.loadSecretSync(test: self, label: peerID!)
203 XCTAssertNotNil(secret, "secret should not be nil")
204 XCTAssertNil(error, "error should be nil")
206 XCTAssertNotNil(peerID)
207 XCTAssertNotNil(permanentInfo)
208 XCTAssertNotNil(permanentInfoSig)
211 _ = container.dumpSync(test: self)
215 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
217 XCTFail("Creating container errored: \(error)")
221 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
223 XCTAssertNotNil(peerID2)
225 _ = container.dumpSync(test: self)
227 return (container, peerID!)
230 func testEstablishWithReload() throws {
231 let description = tmpStoreDescription(name: "container.db")
232 let (container, peerID) = try establish(reload: true, store: description)
234 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
236 // With no other input, the syncing policy should say to sync user views
237 let (policy, _, policyError) = container.fetchCurrentPolicySync(test: self)
238 XCTAssertNil(policyError, "Should be no error fetching aPolicy")
239 XCTAssertNotNil(policy, "Should have a syncing policy")
240 XCTAssertEqual(policy?.syncUserControllableViews, .DISABLED, "Peer should not desire to sync user controllable views (as the client didn't have any input)")
243 func testEstablishNoReload() throws {
244 let description = tmpStoreDescription(name: "container.db")
245 let (container, peerID) = try establish(reload: false, store: description)
247 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
249 // With no other input, the syncing policy should say to sync user views
250 let (policy, _, policyError) = container.fetchCurrentPolicySync(test: self)
251 XCTAssertNil(policyError, "Should be no error fetching aPolicy")
252 XCTAssertNotNil(policy, "Should have a syncing policy")
253 XCTAssertEqual(policy?.syncUserControllableViews, .DISABLED, "Peer should not desire to sync user controllable views (as the client didn't have any input)")
256 func testEstablishWithUserSyncableViews() throws {
257 let description = tmpStoreDescription(name: "container.db")
259 let (container, peerID) = try self.establish(reload: false,
260 contextID: OTDefaultContext,
261 accountIsDemo: false,
262 syncUserControllableViews: .ENABLED,
265 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
267 //The syncing policy should say not to sync user views
268 let (policy, _, policyError) = container.fetchCurrentPolicySync(test: self)
269 XCTAssertNil(policyError, "Should be no error fetching aPolicy")
270 XCTAssertNotNil(policy, "Should have a syncing policy")
272 XCTAssertEqual(policy?.syncUserControllableViews, .ENABLED, "Peer should desire to sync user controllable views (per request)")
275 func testEstablishWithoutUserSyncableViews() throws {
276 let description = tmpStoreDescription(name: "container.db")
278 let (container, peerID) = try self.establish(reload: false,
279 contextID: OTDefaultContext,
280 accountIsDemo: false,
281 syncUserControllableViews: .DISABLED,
284 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
286 //The syncing policy should say not to sync user views
287 let (policy, _, policyError) = container.fetchCurrentPolicySync(test: self)
288 XCTAssertNil(policyError, "Should be no error fetching aPolicy")
289 XCTAssertNotNil(policy, "Should have a syncing policy")
291 XCTAssertEqual(policy?.syncUserControllableViews, .DISABLED, "Peer should not desire to sync user controllable views (per request)")
294 func testEstablishWithoutUserSyncableViewsOnWatch() throws {
295 let description = tmpStoreDescription(name: "container.db")
297 // Watches will listen to the input here. If we set FOLLOWING, it should remain FOLLOWING (as some watches don't have UI to change this value)
298 let (container, peerID) = try self.establish(reload: false,
299 contextID: OTDefaultContext,
300 accountIsDemo: false,
302 syncUserControllableViews: .FOLLOWING,
305 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
307 //The syncing policy should say not to sync user views
308 let (policy, _, policyError) = container.fetchCurrentPolicySync(test: self)
309 XCTAssertNil(policyError, "Should be no error fetching aPolicy")
310 XCTAssertNotNil(policy, "Should have a syncing policy")
312 XCTAssertEqual(policy?.syncUserControllableViews, .FOLLOWING, "Peer should desire to sync user controllable views (ignoring the request)")
315 func testEstablishNotOnAllowListErrors() throws {
316 let description = tmpStoreDescription(name: "container.db")
317 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
319 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
321 let state = container.getStateSync(test: self)
322 XCTAssertTrue(state.bottles.contains { $0.peerID == peerID }, "should have a bottle for peer")
323 let secret = container.loadSecretSync(test: self, label: peerID!)
324 XCTAssertNotNil(secret, "secret should not be nil")
325 XCTAssertNil(error, "error should be nil")
327 XCTAssertNotNil(peerID)
328 XCTAssertNotNil(permanentInfo)
329 XCTAssertNotNil(permanentInfoSig)
332 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
334 // Now set up a machine ID list that positively does not have our peer
335 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
337 let (peerID3, _, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
338 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
339 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
342 func joinByVoucher(sponsor: Container,
345 machineIDs: Set<String>,
347 store: NSPersistentStoreDescription) throws -> (Container, String) {
348 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
349 persistentStoreDescription: store,
350 cuttlefish: cuttlefish)
352 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: accountIsDemo, listDifference: !machineIDs.isEmpty), "Should be able to set machine IDs")
354 print("preparing \(containerID)")
355 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, error) =
356 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
358 XCTAssertNotNil(peerID)
359 XCTAssertNotNil(permanentInfo)
360 XCTAssertNotNil(permanentInfoSig)
361 XCTAssertNotNil(stableInfo)
362 XCTAssertNotNil(stableInfoSig)
365 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
367 print("\(sponsor) vouches for \(containerID)")
368 let (voucherData, voucherSig, vouchError) =
369 sponsor.vouchSync(test: self,
371 permanentInfo: permanentInfo!,
372 permanentInfoSig: permanentInfoSig!,
373 stableInfo: stableInfo!,
374 stableInfoSig: stableInfoSig!,
375 ckksKeys: [self.manateeKeySet])
376 XCTAssertNil(vouchError)
377 XCTAssertNotNil(voucherData)
378 XCTAssertNotNil(voucherSig)
380 // As part of the join, the sponsor should have uploaded a tlk share
381 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
383 print("\(containerID) joins")
384 let (joinedPeerID, _, _, joinError) = c.joinSync(test: self,
385 voucherData: voucherData!,
386 voucherSig: voucherSig!,
389 XCTAssertNil(joinError)
390 XCTAssertEqual(joinedPeerID, peerID!)
396 func testJoin() throws {
397 let description = tmpStoreDescription(name: "container.db")
398 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
399 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
400 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
402 let machineIDs = Set(["aaa", "bbb", "ccc"])
403 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
404 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
405 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
408 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
409 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
410 XCTAssertNotNil(aPolicy, "Should have a syncing policy coming back from a successful prepare")
411 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
412 XCTAssertEqual(aPolicy?.syncUserControllableViews, .UNKNOWN, "Policy coming back from prepare() should not have an opinion on views")
415 let state = containerA.getStateSync(test: self)
416 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
417 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
418 XCTAssertNotNil(secret, "secret should not be nil")
419 XCTAssertNil(error, "error should be nil")
422 XCTAssertNotNil(aPeerID)
423 XCTAssertNotNil(aPermanentInfo)
424 XCTAssertNotNil(aPermanentInfoSig)
426 print("establishing A")
428 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
430 XCTAssertNotNil(peerID)
434 // With no other input, the syncing policy should say to sync user views
435 let (aPolicy, _, aPolicyError) = containerA.fetchCurrentPolicySync(test: self)
436 XCTAssertNil(aPolicyError, "Should be no error fetching aPolicy")
437 XCTAssertNotNil(aPolicy, "Should have a syncing policy")
438 XCTAssertEqual(aPolicy?.syncUserControllableViews, .DISABLED, "Peer should desire to not sync user controllable views (as the client didn't have any input)")
442 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, error2) =
443 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
445 let state = containerB.getStateSync(test: self)
446 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
447 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
448 XCTAssertNotNil(secret, "secret should not be nil")
449 XCTAssertNil(error, "error should be nil")
452 XCTAssertNotNil(bPeerID)
453 XCTAssertNotNil(bPermanentInfo)
454 XCTAssertNotNil(bPermanentInfoSig)
457 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
458 print("A vouches for B, but doesn't provide any TLKShares")
459 let (_, _, errorVouchingWithoutTLKs) =
460 containerA.vouchSync(test: self,
462 permanentInfo: bPermanentInfo!,
463 permanentInfoSig: bPermanentInfoSig!,
464 stableInfo: bStableInfo!,
465 stableInfoSig: bStableInfoSig!,
467 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
468 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
470 print("A vouches for B, but doesn't only has provisional TLKs at the time")
471 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
472 provisionalManateeKeySet.newUpload = true
474 let (_, _, errorVouchingWithProvisionalTLKs) =
475 containerA.vouchSync(test: self,
477 permanentInfo: bPermanentInfo!,
478 permanentInfoSig: bPermanentInfoSig!,
479 stableInfo: bStableInfo!,
480 stableInfoSig: bStableInfoSig!,
481 ckksKeys: [provisionalManateeKeySet])
482 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
483 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
485 print("A vouches for B")
486 let (voucherData, voucherSig, error3) =
487 containerA.vouchSync(test: self,
489 permanentInfo: bPermanentInfo!,
490 permanentInfoSig: bPermanentInfoSig!,
491 stableInfo: bStableInfo!,
492 stableInfoSig: bStableInfoSig!,
493 ckksKeys: [self.manateeKeySet])
495 XCTAssertNotNil(voucherData)
496 XCTAssertNotNil(voucherSig)
498 // As part of the vouch, A should have uploaded a tlkshare for B
499 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
502 let (peerID, _, bPolicy, error) = containerB.joinSync(test: self,
503 voucherData: voucherData!,
504 voucherSig: voucherSig!,
508 XCTAssertEqual(peerID, bPeerID!)
510 XCTAssertNotNil(bPolicy, "Should have a syncing policy")
511 XCTAssertEqual(bPolicy?.syncUserControllableViews, .DISABLED, "Peer should desire to not sync user controllable views (following A's lead)")
514 _ = containerA.dumpSync(test: self)
515 _ = containerB.dumpSync(test: self)
516 _ = containerC.dumpSync(test: self)
519 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, error4) =
520 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
522 let state = containerC.getStateSync(test: self)
523 XCTAssertTrue(state.bottles.contains { $0.peerID == cPeerID }, "should have a bottle for peer")
524 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
525 XCTAssertNotNil(secret, "secret should not be nil")
526 XCTAssertNil(error, "error should be nil")
529 XCTAssertNotNil(cPeerID)
530 XCTAssertNotNil(cPermanentInfo)
531 XCTAssertNotNil(cPermanentInfoSig)
534 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
535 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
536 provisionalEngramKeySet.newUpload = true
538 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
539 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
541 print("B vouches for C")
542 let (voucherData, voucherSig, error) =
543 containerB.vouchSync(test: self,
545 permanentInfo: cPermanentInfo!,
546 permanentInfoSig: cPermanentInfoSig!,
547 stableInfo: cStableInfo!,
548 stableInfoSig: cStableInfoSig!,
549 ckksKeys: [self.manateeKeySet])
551 XCTAssertNotNil(voucherData)
552 XCTAssertNotNil(voucherSig)
554 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
557 let (peerID, _, cPolicy, error2) = containerC.joinSync(test: self,
558 voucherData: voucherData!,
559 voucherSig: voucherSig!,
560 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
563 XCTAssertEqual(peerID, cPeerID!)
565 XCTAssertNotNil(cPolicy, "Should have a syncing policy")
566 XCTAssertEqual(cPolicy?.syncUserControllableViews, .DISABLED, "Peer should desire to not sync user controllable views (following A and B's lead)")
568 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
569 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
570 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
575 let (_, _, error) = containerA.updateSync(test: self)
580 let state = containerA.getStateSync(test: self)
581 let a = state.peers[aPeerID!]!
582 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
585 _ = containerA.dumpSync(test: self)
586 _ = containerB.dumpSync(test: self)
587 _ = containerC.dumpSync(test: self)
590 func testJoinWithEnabledUserControllableViews() throws {
591 let description = tmpStoreDescription(name: "container.db")
592 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
593 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
595 let machineIDs = Set(["aaa", "bbb", "ccc"])
596 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
597 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
600 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
601 containerA.prepareSync(test: self,
604 bottleSalt: "123456789",
605 bottleID: UUID().uuidString,
606 modelID: "iPhone1,1",
607 syncUserControllableViews: .ENABLED)
608 XCTAssertNotNil(aPolicy, "Should have a syncing policy coming back from a successful prepare")
609 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
610 XCTAssertEqual(aPolicy?.syncUserControllableViews, .ENABLED, "Policy coming back from prepare() should already have an opinion of user view syncing")
613 XCTAssertNotNil(aPeerID)
614 XCTAssertNotNil(aPermanentInfo)
615 XCTAssertNotNil(aPermanentInfoSig)
617 print("establishing A")
619 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
621 XCTAssertNotNil(peerID)
625 let (aPolicy, _, aPolicyError) = containerA.fetchCurrentPolicySync(test: self)
626 XCTAssertNil(aPolicyError, "Should be no error fetching aPolicy")
627 XCTAssertNotNil(aPolicy, "Should have a syncing policy")
628 XCTAssertEqual(aPolicy?.syncUserControllableViews, .ENABLED, "Peer should desire to sync user controllable views (as per request)")
632 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, error2) =
633 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
635 let state = containerB.getStateSync(test: self)
636 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
637 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
638 XCTAssertNotNil(secret, "secret should not be nil")
639 XCTAssertNil(error, "error should be nil")
642 XCTAssertNotNil(bPeerID)
643 XCTAssertNotNil(bPermanentInfo)
644 XCTAssertNotNil(bPermanentInfoSig)
647 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
648 print("A vouches for B, but doesn't provide any TLKShares")
649 let (_, _, errorVouchingWithoutTLKs) =
650 containerA.vouchSync(test: self,
652 permanentInfo: bPermanentInfo!,
653 permanentInfoSig: bPermanentInfoSig!,
654 stableInfo: bStableInfo!,
655 stableInfoSig: bStableInfoSig!,
657 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
658 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
660 print("A vouches for B, but doesn't only has provisional TLKs at the time")
661 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
662 provisionalManateeKeySet.newUpload = true
664 let (_, _, errorVouchingWithProvisionalTLKs) =
665 containerA.vouchSync(test: self,
667 permanentInfo: bPermanentInfo!,
668 permanentInfoSig: bPermanentInfoSig!,
669 stableInfo: bStableInfo!,
670 stableInfoSig: bStableInfoSig!,
671 ckksKeys: [provisionalManateeKeySet])
672 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
673 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
675 print("A vouches for B")
676 let (voucherData, voucherSig, error3) =
677 containerA.vouchSync(test: self,
679 permanentInfo: bPermanentInfo!,
680 permanentInfoSig: bPermanentInfoSig!,
681 stableInfo: bStableInfo!,
682 stableInfoSig: bStableInfoSig!,
683 ckksKeys: [self.manateeKeySet])
685 XCTAssertNotNil(voucherData)
686 XCTAssertNotNil(voucherSig)
688 // As part of the vouch, A should have uploaded a tlkshare for B
689 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
692 let (peerID, _, bPolicy, error) = containerB.joinSync(test: self,
693 voucherData: voucherData!,
694 voucherSig: voucherSig!,
698 XCTAssertEqual(peerID, bPeerID!)
700 XCTAssertNotNil(bPolicy, "Should have a syncing policy")
701 XCTAssertEqual(bPolicy?.syncUserControllableViews, .ENABLED, "Peer should desire to sync user controllable views (following A's lead)")
705 func testJoinWithoutAllowListErrors() throws {
706 let description = tmpStoreDescription(name: "container.db")
707 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
708 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
710 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
712 let state = containerA.getStateSync(test: self)
713 XCTAssertTrue(state.bottles.contains { $0.peerID == peerID }, "should have a bottle for peer")
714 let secret = containerA.loadSecretSync(test: self, label: peerID!)
715 XCTAssertNotNil(secret, "secret should not be nil")
716 XCTAssertNil(error, "error should be nil")
718 XCTAssertNil(error, "Should not have an error after preparing A")
719 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
720 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
721 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
723 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
725 let (peerID2, _, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
726 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
727 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
730 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, errorPrepareB) =
731 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
733 let state = containerA.getStateSync(test: self)
734 XCTAssertTrue(state.bottles.contains { $0.peerID == peerID }, "should have a bottle for peer")
735 let secret = containerA.loadSecretSync(test: self, label: peerID!)
736 XCTAssertNotNil(secret, "secret should not be nil")
737 XCTAssertNil(error, "error should be nil")
739 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
740 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
741 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
742 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
744 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs on container B")
747 print("A vouches for B")
748 let (voucherData, voucherSig, error3) =
749 containerA.vouchSync(test: self,
751 permanentInfo: bPermanentInfo!,
752 permanentInfoSig: bPermanentInfoSig!,
753 stableInfo: bStableInfo!,
754 stableInfoSig: bStableInfoSig!,
756 XCTAssertNil(error3, "Should be no error vouching for B")
757 XCTAssertNotNil(voucherData, "Should have a voucher from A")
758 XCTAssertNotNil(voucherSig, "Should have a signature from A")
761 let (peerID, _, _, error) = containerB.joinSync(test: self,
762 voucherData: voucherData!,
763 voucherSig: voucherSig!,
766 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
767 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
771 func testReset() throws {
772 let description = tmpStoreDescription(name: "container.db")
773 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
775 let machineIDs = Set(["aaa"])
776 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
779 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
781 let state = containerA.getStateSync(test: self)
782 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
783 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
784 XCTAssertNotNil(secret, "secret should not be nil")
785 XCTAssertNil(error, "error should be nil")
788 XCTAssertNotNil(aPeerID)
789 XCTAssertNotNil(aPermanentInfo)
790 XCTAssertNotNil(aPermanentInfoSig)
792 print("establishing A")
794 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
795 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
797 XCTAssertNotNil(peerID)
798 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
803 let error = containerA.resetSync(resetReason: .testGenerated, test: self)
807 let (dict, error) = containerA.dumpSync(test: self)
809 XCTAssertNotNil(dict)
810 let peers: [Any] = dict!["peers"] as! [Any]
811 XCTAssertEqual(0, peers.count)
815 func testResetLocal() throws {
816 let description = tmpStoreDescription(name: "container.db")
817 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
819 let machineIDs = Set(["aaa"])
820 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
822 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
824 let state = containerA.getStateSync(test: self)
825 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
826 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
827 XCTAssertNotNil(secret, "secret should not be nil")
828 XCTAssertNil(error, "error should be nil")
830 XCTAssertNil(error, "Should be no error preparing an identity")
831 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
832 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
833 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
836 let (dict, error) = containerA.dumpSync(test: self)
837 XCTAssertNil(error, "Should be no error dumping")
838 XCTAssertNotNil(dict, "Should receive a dump dictionary")
840 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
841 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
843 let selfPeer: String? = selfInfo!["peerID"] as! String?
844 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
848 let error = containerA.localResetSync(test: self)
849 XCTAssertNil(error, "local-reset shouldn't error")
850 let peers = containerA.containerMO.peers as! Set<PeerMO>
851 XCTAssertEqual(peers.count, 0, "peers should be empty ")
854 let (dict, error) = containerA.dumpSync(test: self)
856 XCTAssertNil(error, "Should be no error dumping")
857 XCTAssertNotNil(dict, "Should receive a dump dictionary")
859 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
860 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
862 let selfPeer: String? = selfInfo!["peerID"] as! String?
863 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
867 func testReplayAttack() throws {
868 let description = tmpStoreDescription(name: "container.db")
869 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
870 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
871 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
873 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
874 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
875 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), accountIsDemo: false))
878 let (peerID, _, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
880 let state = containerA.getStateSync(test: self)
881 XCTAssertTrue(state.bottles.contains { $0.peerID == peerID }, "should have a bottle for peer")
882 let secret = containerA.loadSecretSync(test: self, label: peerID!)
883 XCTAssertNotNil(secret, "secret should not be nil")
885 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
887 let state = containerB.getStateSync(test: self)
888 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
889 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
890 XCTAssertNotNil(secret, "secret should not be nil")
892 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
894 let state = containerC.getStateSync(test: self)
895 XCTAssertTrue(state.bottles.contains { $0.peerID == cPeerID }, "should have a bottle for peer")
896 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
897 XCTAssertNotNil(secret, "secret should not be nil")
899 print("establishing A")
900 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
903 print("A vouches for B")
904 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
906 permanentInfo: bPermanentInfo!,
907 permanentInfoSig: bPermanentInfoSig!,
908 stableInfo: bStableInfo!,
909 stableInfoSig: bStableInfoSig!,
913 _ = containerB.joinSync(test: self,
914 voucherData: voucherData!,
915 voucherSig: voucherSig!,
921 _ = containerA.updateSync(test: self)
922 let earlyClock: TPCounter
924 let state = containerA.getStateSync(test: self)
925 let b = state.peers[bPeerID!]!
926 earlyClock = b.dynamicInfo!.clock
930 let snapshot = cuttlefish.state
933 print("B vouches for C")
934 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
935 permanentInfo: cPermanentInfo!,
936 permanentInfoSig: cPermanentInfoSig!,
937 stableInfo: cStableInfo!,
938 stableInfoSig: cStableInfoSig!,
942 _ = containerC.joinSync(test: self,
943 voucherData: voucherData!,
944 voucherSig: voucherSig!,
950 _ = containerB.updateSync(test: self)
953 _ = containerA.updateSync(test: self)
954 let lateClock: TPCounter
956 let state = containerA.getStateSync(test: self)
957 let b = state.peers[bPeerID!]!
958 lateClock = b.dynamicInfo!.clock
959 XCTAssertTrue(earlyClock < lateClock)
962 print("Reverting cuttlefish to the snapshot")
963 cuttlefish.state = snapshot
964 cuttlefish.makeSnapshot()
966 print("A updates, fetching the old snapshot from cuttlefish")
967 _ = containerA.updateSync(test: self)
969 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
970 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
972 let state = containerA.getStateSync(test: self)
973 let b = state.peers[bPeerID!]!
974 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
978 // TODO: need a real configurable mock cuttlefish
979 func testFetchPolicyDocuments() throws {
980 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
982 let missingTuple = TPPolicyVersion(version: 900, hash: "not a hash")
984 let policy1Tuple = TPPolicyVersion(version: 1, hash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=")
985 let policy1Data = Data(base64Encoded: "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
986 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
987 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2")!
989 let policy3Tuple = TPPolicyVersion(version: 3, hash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=")
990 let policy3Data = Data(base64Encoded: "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A=")!
992 let description = tmpStoreDescription(name: "container.db")
993 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
996 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, versions: [])
997 XCTAssertNil(error1, "No error querying for an empty list")
998 XCTAssertEqual(response1, [:], "Received empty dictionary")
1002 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, versions: Set([policy1Tuple]))
1003 XCTAssertNil(error2, "No error getting locally known policy document")
1004 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
1005 XCTAssert(response2?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1")
1006 XCTAssertEqual(response2?[policy1Tuple], policy1Data, "retrieved data matches known data")
1011 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, versions: [policy1Tuple, policy3Tuple])
1012 XCTAssertNil(error3, "No error fetching local + remote policy")
1013 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
1015 XCTAssert(response3?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1")
1016 XCTAssertEqual(response3?[policy1Tuple], policy1Data, "retrieved data matches known data")
1018 XCTAssert(response3?.keys.contains(policy3Tuple) ?? false, "Should have retrieved request for policy3")
1019 XCTAssertEqual(response3?[policy3Tuple], policy3Data, "retrieved data matches known data")
1024 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple]))
1025 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
1026 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
1031 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple,
1034 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
1035 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
1039 func testEscrowKeys() throws {
1040 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
1041 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
1043 let secretString = "i'm a secret!"
1044 XCTAssertNotNil(secretString, "secretString should not be nil")
1046 let secretData: Data? = secretString.data(using: .utf8)
1047 XCTAssertNotNil(secretData, "secretData should not be nil")
1049 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
1050 XCTAssertNotNil(keys, "keys should not be nil")
1052 XCTAssertNotNil(keys.secret, "secret should not be nil")
1053 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
1054 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
1055 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
1056 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
1058 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
1059 XCTAssertNotNil(hash, "hash should not be nil")
1061 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
1062 XCTAssertTrue(result, "result should be true")
1064 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
1065 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
1067 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
1068 XCTAssertNotNil(signingKey, "signingKey should not be nil")
1069 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
1070 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
1073 func testEscrowKeyTestVectors() {
1074 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
1076 let secret = secretString.data(using: .utf8)
1079 let testv1 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
1080 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
1082 let testv2 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
1083 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
1085 let testv3 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
1086 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
1088 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
1089 let newSecret = newSecretString.data(using: .utf8)
1091 let testv4 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
1092 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
1094 let testv5 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
1095 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
1097 let testv6 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
1098 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
1100 XCTFail("error testing escrow key test vectors \(error)")
1104 func testRecoveryKeyTestVectors() {
1105 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
1107 let secret = secretString.data(using: .utf8)
1110 let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID)
1111 XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match")
1113 let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID)
1114 XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match")
1116 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
1117 let newSecret = newSecretString.data(using: .utf8)
1119 let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID)
1120 XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match")
1122 let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID)
1123 XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match")
1125 XCTFail("error testing RecoveryKey test vectors \(error)")
1129 func testJoiningWithBottle() throws {
1130 var bottleA: BottleMO
1132 let description = tmpStoreDescription(name: "container.db")
1133 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1134 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1136 let machineIDs = Set(["aaa", "bbb"])
1137 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1138 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1140 print("preparing A")
1141 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1142 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1144 var state = containerA.getStateSync(test: self)
1145 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1147 bottleA = state.bottles.removeFirst()
1149 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1150 XCTAssertNotNil(secret, "secret should not be nil")
1151 XCTAssertNil(error, "error should be nil")
1153 XCTAssertNotNil(aPeerID)
1154 XCTAssertNotNil(aPermanentInfo)
1155 XCTAssertNotNil(aPermanentInfoSig)
1157 print("establishing A")
1159 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1160 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1162 XCTAssertNotNil(peerID)
1163 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1166 let state = containerA.getStateSync(test: self)
1167 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1168 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1170 XCTAssertNotNil(secret, "secret should not be nil")
1171 XCTAssertNil(error, "error should be nil")
1174 _ = containerB.updateSync(test: self)
1176 print("preparing B")
1177 let (bPeerID, _, _, _, _, _, error2) =
1178 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1180 let state = containerB.getStateSync(test: self)
1181 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1182 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1183 XCTAssertNotNil(secret, "secret should not be nil")
1184 XCTAssertNil(error, "error should be nil")
1186 XCTAssertNil(error2)
1189 print("B prepares to join via bottle")
1191 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1192 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1193 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1194 XCTAssertNotNil(policy, "Should have a policy")
1196 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1198 XCTAssertNil(error3)
1199 XCTAssertNotNil(voucherData)
1200 XCTAssertNotNil(voucherSig)
1202 // Before B joins, there should be no TLKShares for B
1203 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1206 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1208 XCTAssertEqual(peerID, bPeerID!)
1210 // But afterward, it has one!
1211 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1215 func testJoiningWithBottleAndEmptyBottleSalt() throws {
1216 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, accountIsDemo: false))
1224 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1226 print("preparing A")
1227 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1228 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1230 var state = containerA.getStateSync(test: self)
1231 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1233 bottleA = state.bottles.removeFirst()
1235 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1236 XCTAssertNotNil(secret, "secret should not be nil")
1237 XCTAssertNil(error, "error should be nil")
1239 XCTAssertNotNil(aPeerID)
1240 XCTAssertNotNil(aPermanentInfo)
1241 XCTAssertNotNil(aPermanentInfoSig)
1243 print("establishing A")
1245 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1246 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1248 XCTAssertNotNil(peerID)
1249 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1252 let state = containerA.getStateSync(test: self)
1253 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1254 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1256 XCTAssertNotNil(secret, "secret should not be nil")
1257 XCTAssertNil(error, "error should be nil")
1260 _ = containerB.updateSync(test: self)
1262 print("preparing B")
1263 let (bPeerID, _, _, _, _, _, error2) =
1264 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1266 let state = containerB.getStateSync(test: self)
1267 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1268 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1269 XCTAssertNotNil(secret, "secret should not be nil")
1270 XCTAssertNil(error, "error should be nil")
1272 XCTAssertNil(error2)
1275 print("B prepares to join via bottle")
1277 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1278 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1279 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1280 XCTAssertNotNil(policy, "Should have a policy")
1282 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1284 XCTAssertNil(error3)
1285 XCTAssertNotNil(voucherData)
1286 XCTAssertNotNil(voucherSig)
1288 // Before B joins, there should be no TLKShares for B
1289 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1292 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1294 XCTAssertEqual(peerID, bPeerID!)
1296 // But afterward, it has one!
1297 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1301 func testJoiningWithWrongEscrowRecordForBottle() throws {
1303 let description = tmpStoreDescription(name: "container.db")
1304 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1305 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1307 let machineIDs = Set(["aaa", "bbb"])
1308 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1309 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1311 print("preparing A")
1312 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1313 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1315 let state = containerA.getStateSync(test: self)
1316 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1317 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1318 XCTAssertNotNil(secret, "secret should not be nil")
1319 XCTAssertNil(error, "error should be nil")
1322 XCTAssertNotNil(aPeerID)
1323 XCTAssertNotNil(aPermanentInfo)
1324 XCTAssertNotNil(aPermanentInfoSig)
1326 print("establishing A")
1328 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1330 XCTAssertNotNil(peerID)
1333 let state = containerA.getStateSync(test: self)
1334 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1335 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1337 XCTAssertNotNil(secret, "secret should not be nil")
1338 XCTAssertNil(error, "error should be nil")
1341 _ = containerB.updateSync(test: self)
1343 print("preparing B")
1344 let (bPeerID, _, _, _, _, _, error2) =
1345 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1347 let state = containerB.getStateSync(test: self)
1348 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1349 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1350 XCTAssertNotNil(secret, "secret should not be nil")
1351 XCTAssertNil(error, "error should be nil")
1353 XCTAssertNil(error2)
1356 print("B joins via bottle")
1358 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: "wrong escrow record")
1359 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't exist")
1360 XCTAssertNil(bottlePeerID, "peerID should be nil for no bottle")
1361 XCTAssertNil(policy, "Should not have a policy")
1363 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1365 XCTAssertNotNil(error3)
1366 XCTAssertNil(voucherData)
1367 XCTAssertNil(voucherSig)
1371 func testJoiningWithWrongBottle() throws {
1372 var bottleB: BottleMO
1374 let description = tmpStoreDescription(name: "container.db")
1375 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1376 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1378 let machineIDs = Set(["aaa", "bbb"])
1379 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1380 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1382 print("preparing A")
1383 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1384 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1386 let state = containerA.getStateSync(test: self)
1387 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1388 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1389 XCTAssertNotNil(secret, "secret should not be nil")
1390 XCTAssertNil(error, "error should be nil")
1393 XCTAssertNotNil(aPeerID)
1394 XCTAssertNotNil(aPermanentInfo)
1395 XCTAssertNotNil(aPermanentInfoSig)
1397 print("establishing A")
1399 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1401 XCTAssertNotNil(peerID)
1404 let state = containerA.getStateSync(test: self)
1405 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1406 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1408 XCTAssertNotNil(secret, "secret should not be nil")
1409 XCTAssertNil(error, "error should be nil")
1412 print("preparing B")
1413 let (bPeerID, _, _, _, _, _, error2) =
1414 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1416 var state = containerB.getStateSync(test: self)
1417 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1418 bottleB = state.bottles.removeFirst()
1419 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1420 XCTAssertNotNil(secret, "secret should not be nil")
1421 XCTAssertNil(error, "error should be nil")
1423 XCTAssertNil(error2)
1426 print("B joins via bottle")
1428 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleB.bottleID!)
1429 XCTAssertNotNil(errorPreflight, "Should be an error preflighting bottle that doesn't correspond to a peer")
1430 XCTAssertNil(bottlePeerID, "Should have no peer for invalid bottle")
1431 XCTAssertNil(policy, "Should not have a policy")
1433 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1435 XCTAssertNotNil(error3)
1436 XCTAssertNil(voucherData)
1437 XCTAssertNil(voucherSig)
1441 func testJoiningWithBottleAndBadSalt() throws {
1442 var bottleA: BottleMO
1444 let description = tmpStoreDescription(name: "container.db")
1445 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1446 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1448 let machineIDs = Set(["aaa", "bbb"])
1449 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1450 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1452 print("preparing A")
1453 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1454 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1456 var state = containerA.getStateSync(test: self)
1457 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1458 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1459 bottleA = state.bottles.removeFirst()
1460 XCTAssertNotNil(secret, "secret should not be nil")
1461 XCTAssertNil(error, "error should be nil")
1464 XCTAssertNotNil(aPeerID)
1465 XCTAssertNotNil(aPermanentInfo)
1466 XCTAssertNotNil(aPermanentInfoSig)
1468 print("establishing A")
1470 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1472 XCTAssertNotNil(peerID)
1475 let state = containerA.getStateSync(test: self)
1476 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1477 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1479 XCTAssertNotNil(secret, "secret should not be nil")
1480 XCTAssertNil(error, "error should be nil")
1483 _ = containerB.updateSync(test: self)
1485 print("preparing B")
1486 let (bPeerID, _, _, _, _, _, error2) =
1487 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1489 let state = containerB.getStateSync(test: self)
1490 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1491 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1492 XCTAssertNotNil(secret, "secret should not be nil")
1493 XCTAssertNil(error, "error should be nil")
1495 XCTAssertNil(error2)
1498 print("B joins via bottle")
1500 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1501 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1502 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1503 XCTAssertNotNil(policy, "Should have a policy")
1505 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1507 XCTAssertNotNil(error3)
1508 XCTAssertNil(voucherData)
1509 XCTAssertNil(voucherSig)
1513 func testJoiningWithBottleAndBadSecret() throws {
1514 var bottleA: BottleMO
1515 let description = tmpStoreDescription(name: "container.db")
1516 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1517 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1519 let machineIDs = Set(["aaa", "bbb"])
1520 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1521 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1523 print("preparing A")
1524 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1525 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1527 var state = containerA.getStateSync(test: self)
1528 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1529 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1530 bottleA = state.bottles.removeFirst()
1531 XCTAssertNotNil(secret, "secret should not be nil")
1532 XCTAssertNil(error, "error should be nil")
1535 XCTAssertNotNil(aPeerID)
1536 XCTAssertNotNil(aPermanentInfo)
1537 XCTAssertNotNil(aPermanentInfoSig)
1539 print("establishing A")
1541 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1543 XCTAssertNotNil(peerID)
1546 let state = containerA.getStateSync(test: self)
1547 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1548 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1549 XCTAssertNotNil(secret, "secret should not be nil")
1550 XCTAssertNil(error, "error should be nil")
1553 _ = containerB.updateSync(test: self)
1555 print("preparing B")
1556 let (bPeerID, _, _, _, _, _, error2) =
1557 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1559 let state = containerB.getStateSync(test: self)
1560 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1561 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1562 XCTAssertNotNil(secret, "secret should not be nil")
1563 XCTAssertNil(error, "error should be nil")
1565 XCTAssertNil(error2)
1568 print("B joins via bottle")
1570 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1571 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
1572 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
1573 XCTAssertNotNil(policy, "Should have a policy")
1575 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1577 XCTAssertNotNil(error3)
1578 XCTAssertNil(voucherData)
1579 XCTAssertNil(voucherSig)
1583 func testJoiningWithNoFetchAllBottles() throws {
1584 var bottleA: BottleMO
1586 let description = tmpStoreDescription(name: "container.db")
1587 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1588 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1590 let machineIDs = Set(["aaa", "bbb"])
1591 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1592 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1594 print("preparing A")
1595 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1596 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1598 var state = containerA.getStateSync(test: self)
1599 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1600 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1602 bottleA = state.bottles.removeFirst()
1603 XCTAssertNotNil(secret, "secret should not be nil")
1604 XCTAssertNil(error, "error should be nil")
1607 XCTAssertNotNil(aPeerID)
1608 XCTAssertNotNil(aPermanentInfo)
1609 XCTAssertNotNil(aPermanentInfoSig)
1611 print("establishing A")
1613 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1615 XCTAssertNotNil(peerID)
1618 let state = containerA.getStateSync(test: self)
1619 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1620 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1621 XCTAssertNotNil(secret, "secret should not be nil")
1622 XCTAssertNil(error, "error should be nil")
1625 print("preparing B")
1626 let (bPeerID, _, _, _, _, _, error2) =
1627 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1629 let state = containerB.getStateSync(test: self)
1630 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1631 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1632 XCTAssertNotNil(secret, "secret should not be nil")
1633 XCTAssertNil(error, "error should be nil")
1635 XCTAssertNil(error2)
1638 print("B joins via bottle")
1640 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1642 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
1643 XCTAssertNotNil(errorPreflight, "Should be an error preflighting a vouch with bottle with a fetch error")
1644 XCTAssertNil(bottlePeerID, "peerID should be nil")
1645 XCTAssertNil(policy, "Should not have a policy")
1647 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1649 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1651 XCTAssertNotNil(error3)
1652 XCTAssertNil(voucherData)
1653 XCTAssertNil(voucherSig)
1657 func testJoinByPreapproval() throws {
1658 let description = tmpStoreDescription(name: "container.db")
1659 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1660 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1661 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1663 let machineIDs = Set(["aaa", "bbb", "ccc"])
1664 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1665 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1666 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1668 print("preparing A")
1669 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
1670 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1672 XCTAssertNotNil(aPeerID)
1673 XCTAssertNotNil(aPermanentInfo)
1674 XCTAssertNotNil(aPermanentInfoSig)
1676 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
1677 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
1678 XCTAssertEqual(aPolicy?.syncUserControllableViews, .UNKNOWN, "Policy shouldn't yet know whether we want to sync user views")
1680 print("preparing B")
1681 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, error2) =
1682 containerB.prepareSync(test: self,
1685 bottleSalt: "123456789",
1686 bottleID: UUID().uuidString,
1687 modelID: "iPhone1,1",
1688 syncUserControllableViews: .DISABLED)
1690 let state = containerB.getStateSync(test: self)
1691 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
1692 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1693 XCTAssertNotNil(secret, "secret should not be nil")
1694 XCTAssertNil(error, "error should be nil")
1696 XCTAssertNil(error2)
1697 XCTAssertNotNil(bPeerID)
1698 XCTAssertNotNil(bPermanentInfo)
1699 XCTAssertNotNil(bPermanentInfoSig)
1701 print("preparing C")
1702 let (cPeerID, cPermanentInfo, cPermanentInfoSig, _, _, _, cPrepareError) =
1703 containerC.prepareSync(test: self,
1706 bottleSalt: "123456789",
1707 bottleID: UUID().uuidString,
1708 modelID: "iPhone1,1",
1709 syncUserControllableViews: .ENABLED)
1711 let state = containerC.getStateSync(test: self)
1712 XCTAssertTrue(state.bottles.contains { $0.peerID == cPeerID }, "should have a bottle for peer")
1713 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
1714 XCTAssertNotNil(secret, "secret should not be nil")
1715 XCTAssertNil(error, "error should be nil")
1717 XCTAssertNil(cPrepareError)
1718 XCTAssertNotNil(cPeerID)
1719 XCTAssertNotNil(cPermanentInfo)
1720 XCTAssertNotNil(cPermanentInfoSig)
1722 // Now, A establishes preapproving B & C
1723 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1725 let aPermanentInfoParsed = TPPeerPermanentInfo(peerID: aPeerID!, data: aPermanentInfo!, sig: aPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1726 XCTAssertNotNil(aPermanentInfoParsed, "Should have parsed A's permanent info")
1728 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1729 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1731 let cPermanentInfoParsed = TPPeerPermanentInfo(peerID: cPeerID!, data: cPermanentInfo!, sig: cPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1732 XCTAssertNotNil(cPermanentInfoParsed, "Should have parsed C's permanent info")
1734 print(bPermanentInfoParsed!.signingPubKey.spki().base64EncodedString())
1735 print(cPermanentInfoParsed!.signingPubKey.spki().base64EncodedString())
1737 print("establishing A")
1739 let (peerID, _, _, error) = containerA.establishSync(test: self,
1740 ckksKeys: [self.manateeKeySet],
1742 preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki(),
1743 cPermanentInfoParsed!.signingPubKey.spki(), ])
1745 XCTAssertNotNil(peerID)
1747 let (aPolicy, _, aPolicyError) = containerA.fetchCurrentPolicySync(test: self)
1748 XCTAssertNil(aPolicyError, "Should be no error fetching aPolicy")
1749 XCTAssertNotNil(aPolicy, "Should have a syncing policy")
1750 XCTAssertEqual(aPolicy?.syncUserControllableViews, .DISABLED, "A should desire to not ync user controllable views (as the client didn't have any input)")
1754 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1756 print("B joins by preapproval, and uploads all TLKShares that it has")
1757 let (bJoinedPeerID, _, bPolicy, bJoinedError) = containerB.preapprovedJoinSync(test: self,
1758 ckksKeys: [self.manateeKeySet],
1760 preapprovedKeys: [aPermanentInfoParsed!.signingPubKey.spki(),
1761 cPermanentInfoParsed!.signingPubKey.spki(), ])
1762 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1763 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1764 XCTAssertNotNil(bPolicy, "Should have a policy back from preapprovedjoin")
1765 XCTAssertEqual(bPolicy?.syncUserControllableViews, .DISABLED, "Policy should say not to sync user controllable views")
1767 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1771 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1773 print("B joins by preapproval, and uploads all TLKShares that it has")
1774 let (cJoinedPeerID, _, cPolicy, cJoinedError) = containerC.preapprovedJoinSync(test: self,
1775 ckksKeys: [self.manateeKeySet],
1777 preapprovedKeys: [aPermanentInfoParsed!.signingPubKey.spki(),
1778 bPermanentInfoParsed!.signingPubKey.spki(), ])
1779 XCTAssertNil(cJoinedError, "Should be no error joining by preapproval")
1780 XCTAssertNotNil(cJoinedPeerID, "Should have a peer ID out of join")
1781 XCTAssertEqual(cPeerID, cJoinedPeerID, "PeerID after joining should match")
1782 XCTAssertNotNil(cPolicy, "Should have a policy back from preapprovedjoin")
1783 XCTAssertEqual(cPolicy?.syncUserControllableViews, .ENABLED, "Policy should say to sync user controllable views")
1785 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1788 _ = containerA.dumpSync(test: self)
1789 _ = containerB.dumpSync(test: self)
1790 _ = containerC.dumpSync(test: self)
1793 func testDepart() throws {
1794 let description = tmpStoreDescription(name: "container.db")
1795 let (container, peerID) = try establish(reload: false, store: description)
1797 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1798 assertDistrusts(context: container, peerIDs: [peerID])
1801 func testDistrustPeers() throws {
1802 let store = tmpStoreDescription(name: "container.db")
1803 let (c, peerID1) = try establish(reload: false, store: store)
1805 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1806 containerID: "second",
1808 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1811 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1812 containerID: "third",
1814 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1817 let (_, _, cUpdateError) = c.updateSync(test: self)
1818 XCTAssertNil(cUpdateError, "Should be able to update first container")
1819 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1821 // You can't distrust yourself via peerID.
1822 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1823 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1825 // Passing in nonsense should error too
1826 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1828 // Now, distrust both peers.
1829 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1830 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1832 // peers should accept their fates
1833 let (_, _, c2UpdateError) = c2.updateSync(test: self)
1834 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1835 assertDistrusts(context: c2, peerIDs: [peerID2])
1837 let (_, _, c3UpdateError) = c3.updateSync(test: self)
1838 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1839 assertDistrusts(context: c3, peerIDs: [peerID3])
1842 func testFetchWithBadChangeToken() throws {
1843 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1845 // But all that goes away, and a new peer establishes
1846 self.cuttlefish.state = FakeCuttlefishServer.State()
1847 let (_, peerID2) = try establish(reload: false, contextID: "second", accountIsDemo: false, store: tmpStoreDescription(name: "container-peer2.db"))
1849 // And the first container fetches again, which should succeed
1850 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1851 let (_, _, updateError) = c.updateSync(test: self)
1852 XCTAssertNil(updateError, "Update should have succeeded")
1854 // and c's model should only include peerID2
1855 c.moc.performAndWait {
1856 let modelPeers = c.model.allPeerIDs()
1857 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1858 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1859 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1863 func testFetchEscrowContents() throws {
1864 let description = tmpStoreDescription(name: "container.db")
1865 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1866 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1867 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1868 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1869 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1870 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1871 XCTAssertNil(spkiA, "Should not have an SPKI")
1873 let (c, peerID) = try establish(reload: false, store: description)
1874 XCTAssertNotNil(peerID, "establish should return a peer id")
1876 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1877 XCTAssertNil(error, "Should be no error fetching escrow contents")
1878 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1879 XCTAssertNotNil(bottleID, "Should have a bottleID")
1880 XCTAssertNotNil(spki, "Should have an SPKI")
1883 func testBottles() {
1885 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1886 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1887 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1889 let keys = bottle.escrowKeys
1890 XCTAssertNotNil(keys, "keys should not be nil")
1892 XCTAssertNotNil(bottle, "bottle should not be nil")
1894 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1896 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1897 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1899 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1901 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1902 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1904 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1905 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1907 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1908 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1910 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1912 XCTFail("error testing bottles \(error)")
1916 func testFetchBottles() throws {
1917 let store = tmpStoreDescription(name: "container.db")
1918 let (c, _) = try establish(reload: false, store: store)
1920 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1921 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1922 XCTAssertNotNil(bottles, "should have fetched some bottles")
1923 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1926 let state = c.getStateSync(test: self)
1927 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1928 XCTAssertEqual(state.escrowRecords.count, 1, "first container should have an escrow record for peer")
1931 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1933 let state = c2.getStateSync(test: self)
1934 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1935 XCTAssertEqual(state.escrowRecords.count, 0, "before fetch, second container should not have any escrow records")
1938 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1939 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1940 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1941 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1944 let state = c2.getStateSync(test: self)
1945 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1946 XCTAssertEqual(state.escrowRecords.count, 1, "after fetch, second container should have one escrow record")
1950 func testFetchBottlesAfterCacheExpires() throws {
1951 OctagonSetOptimizationEnabled(true)
1952 OctagonSetEscrowRecordFetchEnabled(true)
1954 var bottleA: BottleMO
1956 let description = tmpStoreDescription(name: "container.db")
1957 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1958 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1960 let machineIDs = Set(["aaa", "bbb"])
1961 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1962 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
1964 print("preparing A")
1965 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
1966 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1968 var state = containerA.getStateSync(test: self)
1969 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1971 bottleA = state.bottles.removeFirst()
1973 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1974 XCTAssertNotNil(secret, "secret should not be nil")
1975 XCTAssertNil(error, "error should be nil")
1977 XCTAssertNotNil(aPeerID)
1978 XCTAssertNotNil(aPermanentInfo)
1979 XCTAssertNotNil(aPermanentInfoSig)
1981 print("establishing A")
1983 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1984 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1986 XCTAssertNotNil(peerID)
1987 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1990 let state = containerA.getStateSync(test: self)
1991 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1992 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1994 XCTAssertNotNil(secret, "secret should not be nil")
1995 XCTAssertNil(error, "error should be nil")
1998 let (bottles, _, fetchError) = containerA.fetchViableBottlesSync(test: self)
1999 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
2000 XCTAssertNotNil(bottles, "should have fetched some bottles")
2001 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
2004 let state = containerA.getStateSync(test: self)
2005 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
2006 XCTAssertEqual(state.escrowRecords.count, 1, "first container should have an escrow record for peer")
2009 //have another peer join
2010 _ = containerB.updateSync(test: self)
2012 print("preparing B")
2013 let (bPeerID, _, _, _, _, _, error2) =
2014 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2016 let state = containerB.getStateSync(test: self)
2017 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
2018 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
2019 XCTAssertNotNil(secret, "secret should not be nil")
2020 XCTAssertNil(error, "error should be nil")
2022 XCTAssertNil(error2)
2025 print("B prepares to join via bottle")
2027 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
2028 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
2029 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
2030 XCTAssertNotNil(policy, "Should have a policy")
2032 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
2034 XCTAssertNil(error3)
2035 XCTAssertNotNil(voucherData)
2036 XCTAssertNotNil(voucherSig)
2038 // Before B joins, there should be no TLKShares for B
2039 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2042 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
2044 XCTAssertEqual(peerID, bPeerID!)
2046 // But afterward, it has one!
2047 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2050 //now fetch bottles and we should get the cached version
2051 let (_, _, _) = containerA.fetchViableBottlesSync(test: self)
2053 let state = containerA.getStateSync(test: self)
2054 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
2055 XCTAssertEqual(state.escrowRecords.count, 1, "first container should have an escrow record for peer")
2060 //now fetch bottles again after the cache expired
2061 containerA.escrowCacheTimeout = 2.0
2063 let (_, _, _) = containerA.fetchViableBottlesSync(test: self)
2065 let state = containerA.getStateSync(test: self)
2066 XCTAssertEqual(state.bottles.count, 2, "container A should have 2 bottles")
2067 XCTAssertEqual(state.escrowRecords.count, 2, "container A should have 2 escrow records")
2071 func testTrustStatus() throws {
2072 let store = tmpStoreDescription(name: "container.db")
2074 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
2075 persistentStoreDescription: store,
2076 cuttlefish: self.cuttlefish)
2077 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
2078 XCTAssertNil(precStatusError, "No error fetching status")
2079 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
2080 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
2081 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
2082 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
2084 let (c, _) = try establish(reload: false, store: store)
2085 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
2086 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
2087 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
2088 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
2089 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
2090 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
2092 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
2093 persistentStoreDescription: store,
2094 cuttlefish: self.cuttlefish)
2096 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
2097 XCTAssertNil(statusError, "No error fetching status")
2098 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
2099 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
2100 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
2101 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
2104 func testTrustStatusWhenMissingIdentityKeys() throws {
2105 let store = tmpStoreDescription(name: "container.db")
2106 let (c, _) = try establish(reload: false, store: store)
2107 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
2108 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
2109 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
2110 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
2111 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
2112 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
2114 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
2115 XCTAssertTrue(result, "result should be true")
2117 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
2118 XCTAssertNotNil(distrustedError, "error should not be nil")
2119 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
2120 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
2123 func testSetRecoveryKey() throws {
2124 let store = tmpStoreDescription(name: "container.db")
2126 let c = try Container(name: ContainerName(container: "c", context: "context"),
2127 persistentStoreDescription: store,
2128 cuttlefish: self.cuttlefish)
2130 let machineIDs = Set(["aaa", "bbb", "ccc"])
2131 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2133 print("preparing peer A")
2134 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
2135 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2137 let state = c.getStateSync(test: self)
2138 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2139 let secret = c.loadSecretSync(test: self, label: aPeerID!)
2140 XCTAssertNotNil(secret, "secret should not be nil")
2141 XCTAssertNil(error, "error should be nil")
2144 XCTAssertNotNil(aPeerID)
2145 XCTAssertNotNil(aPermanentInfo)
2146 XCTAssertNotNil(aPermanentInfoSig)
2148 print("establishing A")
2150 let (peerID, _, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2152 XCTAssertNotNil(peerID)
2154 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
2155 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
2157 let (_, setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
2158 XCTAssertNil(setRecoveryError, "error should be nil")
2161 func roundTripThroughSetValueTransformer(set: Set<String>) {
2162 let t = SetValueTransformer()
2164 let transformedSet = t.transformedValue(set) as? Data
2165 XCTAssertNotNil(transformedSet, "SVT should return some data")
2167 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
2168 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
2170 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
2173 func testSetValueTransformer() {
2174 roundTripThroughSetValueTransformer(set: Set<String>([]))
2175 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
2176 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
2179 func testGetRepairSuggestion() throws {
2180 let store = tmpStoreDescription(name: "container.db")
2182 let c = try Container(name: ContainerName(container: "c", context: "context"),
2183 persistentStoreDescription: store,
2184 cuttlefish: self.cuttlefish)
2186 let machineIDs = Set(["aaa", "bbb", "ccc"])
2187 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2189 print("preparing peer A")
2190 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
2191 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2193 let state = c.getStateSync(test: self)
2194 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2195 let secret = c.loadSecretSync(test: self, label: aPeerID!)
2196 XCTAssertNotNil(secret, "secret should not be nil")
2197 XCTAssertNil(error, "error should be nil")
2200 XCTAssertNotNil(aPeerID)
2201 XCTAssertNotNil(aPermanentInfo)
2202 XCTAssertNotNil(aPermanentInfoSig)
2204 print("establishing A")
2206 let (peerID, _, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2208 XCTAssertNotNil(peerID)
2210 let (repairAccount, repairEscrow, resetOctagon, leaveTrust, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
2211 XCTAssertFalse(repairAccount, "")
2212 XCTAssertFalse(repairEscrow, "")
2213 XCTAssertFalse(resetOctagon, "")
2214 XCTAssertFalse(leaveTrust, "")
2215 XCTAssertNil(healthError)
2218 func testFetchChangesFailDuringVouchWithBottle() throws {
2219 var bottleA: BottleMO
2221 let description = tmpStoreDescription(name: "container.db")
2222 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2223 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2225 let machineIDs = Set(["aaa", "bbb"])
2226 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2227 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2229 print("preparing A")
2230 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) =
2231 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2233 var state = containerA.getStateSync(test: self)
2234 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2236 bottleA = state.bottles.removeFirst()
2238 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
2239 XCTAssertNotNil(secret, "secret should not be nil")
2240 XCTAssertNil(error, "error should be nil")
2242 XCTAssertNotNil(aPeerID)
2243 XCTAssertNotNil(aPermanentInfo)
2244 XCTAssertNotNil(aPermanentInfoSig)
2246 print("establishing A")
2248 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2249 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2251 XCTAssertNotNil(peerID)
2252 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2255 let state = containerA.getStateSync(test: self)
2256 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2257 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
2259 XCTAssertNotNil(secret, "secret should not be nil")
2260 XCTAssertNil(error, "error should be nil")
2263 _ = containerB.updateSync(test: self)
2265 print("preparing B")
2266 let (bPeerID, _, _, _, _, _, error2) =
2267 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2269 let state = containerB.getStateSync(test: self)
2270 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
2271 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
2272 XCTAssertNotNil(secret, "secret should not be nil")
2273 XCTAssertNil(error, "error should be nil")
2275 XCTAssertNil(error2)
2278 print("B prepares to join via bottle")
2280 let (bottlePeerID, policy, _, errorPreflight) = containerB.preflightVouchWithBottleSync(test: self, bottleID: bottleA.bottleID!)
2281 XCTAssertNil(errorPreflight, "Should be no error preflighting a vouch with bottle")
2282 XCTAssertEqual(bottlePeerID, aPeerID, "Bottle should be for peer A")
2283 XCTAssertNotNil(policy, "Should have a policy")
2285 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
2287 XCTAssertNil(error3)
2288 XCTAssertNotNil(voucherData)
2289 XCTAssertNotNil(voucherSig)
2291 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
2293 // Before B joins, there should be no TLKShares for B
2294 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2297 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
2298 XCTAssertNil(error, "Should be no error joining with a fetch error")
2299 XCTAssertNotNil(peerID, "Should have a peer ID")
2303 func testDistrustedPeerRecoveryKeyNotSet() throws {
2304 let description = tmpStoreDescription(name: "container.db")
2305 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2306 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2308 let machineIDs = Set(["aaa", "bbb"])
2309 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2310 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2312 print("preparing peer A")
2313 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, _, error) =
2314 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2316 let state = containerA.getStateSync(test: self)
2317 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2318 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
2319 XCTAssertNotNil(secret, "secret should not be nil")
2320 XCTAssertNil(error, "error should be nil")
2323 XCTAssertNotNil(aPeerID)
2324 XCTAssertNotNil(aPermanentInfo)
2325 XCTAssertNotNil(aPermanentInfoSig)
2326 XCTAssertNotNil(aStableInfo)
2327 XCTAssertNotNil(aStableInfoSig)
2329 print("establishing A")
2331 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2333 XCTAssertNotNil(peerID)
2336 print("preparing B")
2337 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, error2) =
2338 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2340 let state = containerB.getStateSync(test: self)
2341 XCTAssertTrue(state.bottles.contains { $0.peerID == bPeerID }, "should have a bottle for peer")
2342 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
2343 XCTAssertNotNil(secret, "secret should not be nil")
2344 XCTAssertNil(error, "error should be nil")
2346 XCTAssertNil(error2)
2347 XCTAssertNotNil(bPeerID)
2348 XCTAssertNotNil(bPermanentInfo)
2349 XCTAssertNotNil(bPermanentInfoSig)
2352 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2353 print("A vouches for B, but doesn't provide any TLKShares")
2354 let (_, _, errorVouchingWithoutTLKs) =
2355 containerA.vouchSync(test: self,
2357 permanentInfo: bPermanentInfo!,
2358 permanentInfoSig: bPermanentInfoSig!,
2359 stableInfo: bStableInfo!,
2360 stableInfoSig: bStableInfoSig!,
2362 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
2363 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2365 print("A vouches for B, but doesn't only has provisional TLKs at the time")
2366 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2367 provisionalManateeKeySet.newUpload = true
2369 let (_, _, errorVouchingWithProvisionalTLKs) =
2370 containerA.vouchSync(test: self,
2372 permanentInfo: bPermanentInfo!,
2373 permanentInfoSig: bPermanentInfoSig!,
2374 stableInfo: bStableInfo!,
2375 stableInfoSig: bStableInfoSig!,
2376 ckksKeys: [provisionalManateeKeySet])
2377 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
2378 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2380 print("A vouches for B")
2381 let (voucherData, voucherSig, error3) =
2382 containerA.vouchSync(test: self,
2384 permanentInfo: bPermanentInfo!,
2385 permanentInfoSig: bPermanentInfoSig!,
2386 stableInfo: bStableInfo!,
2387 stableInfoSig: bStableInfoSig!,
2388 ckksKeys: [self.manateeKeySet])
2389 XCTAssertNil(error3)
2390 XCTAssertNotNil(voucherData)
2391 XCTAssertNotNil(voucherSig)
2393 // As part of the vouch, A should have uploaded a tlkshare for B
2394 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2397 let (peerID, _, _, error) = containerB.joinSync(test: self,
2398 voucherData: voucherData!,
2399 voucherSig: voucherSig!,
2403 XCTAssertEqual(peerID, bPeerID!)
2408 let (_, _, error) = containerA.updateSync(test: self)
2413 let (_, _, error) = containerB.updateSync(test: self)
2417 // Now, A distrusts B.
2418 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
2419 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
2421 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
2422 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
2424 let (_, setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
2425 XCTAssertNil(setRecoveryError, "error should be nil")
2429 let (_, _, error) = containerA.updateSync(test: self)
2434 let (_, _, error) = containerB.updateSync(test: self)
2439 let (dict, error) = containerA.dumpSync(test: self)
2441 XCTAssertNil(error, "Should be no error dumping")
2442 XCTAssertNotNil(dict, "Should receive a dump dictionary")
2444 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
2445 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
2447 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
2448 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
2450 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
2451 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
2453 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
2454 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
2458 func assert(container: Container,
2459 allowedMachineIDs: Set<String>,
2460 disallowedMachineIDs: Set<String>,
2461 unknownMachineIDs: Set<String> = Set(),
2462 persistentStore: NSPersistentStoreDescription,
2463 cuttlefish: FakeCuttlefishServer) throws {
2464 let midList = container.onqueueCurrentMIDList()
2465 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
2466 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
2467 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
2469 let (fetchedAllowList, fetchErr) = container.fetchAllowedMachineIDsSync(test: self)
2470 XCTAssertNil(fetchErr, "Should be no error fetching the allowed list")
2471 XCTAssertEqual(fetchedAllowList, allowedMachineIDs, "A fetched list of allowed machine IDs should match the loaded list")
2473 // if we reload the container, does it still match?
2474 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
2476 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
2477 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
2478 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
2479 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
2482 func testAllowListManipulation() throws {
2483 let description = tmpStoreDescription(name: "container.db")
2484 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2486 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2489 XCTAssertNotNil(peerID)
2490 XCTAssertNotNil(permanentInfo)
2491 XCTAssertNotNil(permanentInfoSig)
2493 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2495 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false), "should be able to set allowed machine IDs")
2496 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2497 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2499 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false), "should be able to set allowed machine IDs")
2500 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2501 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2503 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
2504 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2505 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2507 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2508 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2509 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2510 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2512 // once they're unknown, a full list set will make them disallowed
2513 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false), "should be able to set allowed machine IDs")
2514 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2515 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2517 // Resetting the list to what it is doesn't change the list
2518 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2519 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2520 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2522 // But changing it to something completely new does
2523 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"], accountIsDemo: false), "should be able to set allowed machine IDs")
2524 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2525 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2527 // And, readding a previously disallowed machine ID works too
2528 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
2529 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2530 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2532 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2533 let (_, _, updateError) = container.updateSync(test: self)
2534 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2535 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2537 let (_, _, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2538 XCTAssertNil(establishError, "Should be able to establish() with no error")
2539 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2540 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2542 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2543 let (_, _, updateError2) = container.updateSync(test: self)
2544 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2545 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2546 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2549 func testAllowListManipulationWithAddsAndRemoves() throws {
2550 let description = tmpStoreDescription(name: "container.db")
2551 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2553 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2555 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2556 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2557 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2559 // Now, an 'add' comes in for some peers
2560 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2561 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2562 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2564 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2565 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2566 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2567 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2569 // And a remove comes in for E. It becomes 'unknown'
2570 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2571 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2572 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2574 // and a list set after the remove confirms the removal
2575 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2576 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2577 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2579 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2580 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2581 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2582 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2584 // And another list set no longer includes D, so it should now be disallowed
2585 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2586 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2587 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2589 // And just to check the 48 hour boundary...
2590 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2591 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2592 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2594 container.moc.performAndWait {
2595 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2597 knownMachineMOs.forEach {
2598 if $0.machineID == "xxx" {
2599 $0.modified = Date(timeIntervalSinceNow: -60 * 60 * 72)
2603 try! container.moc.save()
2606 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2608 // Setting the list again should kick out X, since it was 'added' too long ago
2609 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2610 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2613 func testAllowSetUpgrade() throws {
2614 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2616 let description = tmpStoreDescription(name: "container.db")
2617 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2618 let mom = getOrMakeModel(url: url)
2619 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2620 persistentContainer.persistentStoreDescriptions = [description]
2622 persistentContainer.loadPersistentStores { _, error in
2623 XCTAssertNil(error, "Should be no error loading persistent stores")
2626 let moc = persistentContainer.newBackgroundContext()
2627 moc.performAndWait {
2628 let containerMO = ContainerMO(context: moc)
2629 containerMO.name = containerName.asSingleString()
2630 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2632 XCTAssertNoThrow(try! moc.save())
2635 // Now TPH boots up with a preexisting model
2636 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2638 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2641 XCTAssertNotNil(peerID)
2642 XCTAssertNotNil(permanentInfo)
2643 XCTAssertNotNil(permanentInfoSig)
2645 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2647 // Setting a new list should work fine
2648 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs")
2649 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2651 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2654 func testAllowStatusUpgrade() throws {
2655 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2657 let description = tmpStoreDescription(name: "container.db")
2658 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2659 let mom = getOrMakeModel(url: url)
2660 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2661 persistentContainer.persistentStoreDescriptions = [description]
2663 persistentContainer.loadPersistentStores { _, error in
2664 XCTAssertNil(error, "Should be no error loading persistent stores")
2667 let moc = persistentContainer.newBackgroundContext()
2668 moc.performAndWait {
2669 let containerMO = ContainerMO(context: moc)
2670 containerMO.name = containerName.asSingleString()
2672 let machine = MachineMO(context: moc)
2673 machine.allowed = true
2674 machine.modified = Date()
2675 machine.machineID = "aaa"
2676 containerMO.addToMachines(machine)
2678 let machineB = MachineMO(context: moc)
2679 machineB.allowed = false
2680 machineB.modified = Date()
2681 machineB.machineID = "bbb"
2682 containerMO.addToMachines(machineB)
2687 // Now TPH boots up with a preexisting model
2688 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2689 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2691 // Setting a new list should work fine
2692 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"], accountIsDemo: false), "should be able to set allowed machine IDs")
2693 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2695 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2698 func testMachineIDListSetWithUnknownMachineIDs() throws {
2699 let description = tmpStoreDescription(name: "container.db")
2700 let (container, _) = try establish(reload: false, store: description)
2702 container.moc.performAndWait {
2703 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2705 knownMachineMOs.forEach {
2706 container.containerMO.removeFromMachines($0)
2709 try! container.moc.save()
2712 // and set the machine ID list to something that doesn't include 'aaa'
2713 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2714 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2715 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2717 // But check that it exists, and set its modification date to a while ago for an upcoming test
2718 container.moc.performAndWait {
2719 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2721 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2722 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2724 let aaaMO = aaaMOs.first!
2725 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2726 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2728 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2729 try! container.moc.save()
2732 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2733 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2735 // Setting it again is fine...
2736 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2737 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2739 // And doesn't reset the modified date on the record
2740 container.moc.performAndWait {
2741 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2743 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2744 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2746 let aaaMO = aaaMOs.first!
2747 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2748 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2750 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2753 // And can be promoted to 'allowed'
2754 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2755 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2756 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2759 func testDuplicateVouchersWhenRegisteringOnModel() throws {
2760 let store = tmpStoreDescription(name: "container.db")
2761 let (c, peerID1) = try establish(reload: false, store: store)
2763 let (c2, peerID2) = try joinByVoucher(sponsor: c,
2764 containerID: "second",
2766 machineIDs: ["aaa", "bbb"], accountIsDemo: false,
2769 let (_, _, cUpdateError) = c.updateSync(test: self)
2770 XCTAssertNil(cUpdateError, "Should be able to update first container")
2771 assertTrusts(context: c, peerIDs: [peerID1, peerID2])
2773 let (_, _, c2UpdateError) = c2.updateSync(test: self)
2774 XCTAssertNil(c2UpdateError, "Should be able to update second container")
2775 assertTrusts(context: c2, peerIDs: [peerID1, peerID2])
2777 //attempt to register a bunch of vouchers it likely already has
2778 for voucher in c2.model.allVouchers() {
2779 c.model.register(voucher)
2781 XCTAssertEqual(c.model.allVouchers().count, 1, "voucher count should be 1")
2782 XCTAssertEqual(c2.model.allVouchers().count, 1, "voucher count should be 1")
2785 func testDuplicateVouchersOnload() throws {
2786 let description = tmpStoreDescription(name: "container.db")
2788 let store = tmpStoreDescription(name: "container.db")
2789 let (c, peerID1) = try establish(reload: false, store: store)
2791 let (c2, peerID2) = try joinByVoucher(sponsor: c,
2792 containerID: "second",
2794 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
2797 let (c3, peerID3) = try joinByVoucher(sponsor: c,
2798 containerID: "third",
2800 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
2803 let (_, _, cUpdateError) = c.updateSync(test: self)
2804 XCTAssertNil(cUpdateError, "Should be able to update first container")
2806 let (_, _, cUpdateError2) = c2.updateSync(test: self)
2807 XCTAssertNil(cUpdateError2, "Should be able to update first container")
2808 let (_, _, cUpdateError3) = c3.updateSync(test: self)
2809 XCTAssertNil(cUpdateError3, "Should be able to update first container")
2810 let (_, _, _) = c.updateSync(test: self)
2812 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
2814 var vouchers: [VoucherMO] = Array()
2816 let c1Peers = c.containerMO.peers as! Set<PeerMO>
2817 for peer in c1Peers {
2818 for voucher in peer.vouchers! {
2819 let vouch = voucher as! VoucherMO
2820 vouchers.append(vouch)
2823 for peer in c1Peers {
2824 for voucher in peer.vouchers! {
2825 let vouch = voucher as! VoucherMO
2826 vouchers.append(vouch)
2830 for peer in c1Peers {
2831 for voucher in peer.vouchers! {
2832 let vouch = voucher as! VoucherMO
2833 vouchers.append(vouch)
2838 XCTAssertEqual(vouchers.count, 6, "should have 6 vouchers")
2840 c.moc.performAndWait {
2841 let containerMO = ContainerMO(context: c.moc)
2844 for peer in containerMO.peers as! Set<PeerMO> {
2845 for vouch in vouchers {
2846 peer.addToVouchers(vouch)
2850 XCTAssertNoThrow(try! c.moc.save())
2855 let container = try Container(name: c.name, persistentStoreDescription: description, cuttlefish: cuttlefish)
2856 XCTAssertEqual(container.model.allVouchers().count, 2, "voucher count should be 2")
2858 XCTFail("Creating container errored: \(error)")
2862 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2863 let description = tmpStoreDescription(name: "container.db")
2864 var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(["aaa"]), accountIsDemo: false, store: description)
2866 // and set the machine ID list to something that doesn't include 'ddd'
2867 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2868 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2869 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2870 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2872 let unknownMachineID = "ddd"
2873 let (_, peerID3) = try self.joinByVoucher(sponsor: container,
2874 containerID: "second",
2875 machineID: unknownMachineID,
2876 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false,
2879 // And the first container accepts the join...
2880 let (_, _, cUpdateError) = container.updateSync(test: self)
2881 XCTAssertNil(cUpdateError, "Should be able to update first container")
2882 assertTrusts(context: container, peerIDs: [peerID1, peerID3])
2884 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2886 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2887 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2889 // But an entry for "ddd" should exist, as a peer in the model claims it as their MID
2890 container.moc.performAndWait {
2891 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2893 let unknownMOs = knownMachineMOs.filter { $0.machineID == unknownMachineID }
2894 XCTAssertEqual(unknownMOs.count, 1, "Should have one machine MO for ddd")
2896 let dddMO = unknownMOs.first!
2897 XCTAssertEqual(dddMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of ddd MO should be 'unknown'")
2898 XCTAssertFalse(dddMO.allowed, "allowed should no longer be a used field")
2900 // Pretend that ddd was added 49 hours ago
2901 dddMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49)
2902 try! container.moc.save()
2907 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2908 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2910 XCTFail("Creating container errored: \(error)")
2912 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2914 // And, setting the list again should disallow ddd, since it is so old
2915 // Note that this _should_ return a list difference, since D is promoted to disallowed
2916 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2917 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish)
2918 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2920 // Setting ths list again has no change
2921 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false, listDifference: false), "should be able to set allowed machine IDs")
2922 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [unknownMachineID], persistentStore: description, cuttlefish: self.cuttlefish)
2923 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2925 // But D can appear again, no problem.
2926 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc", "ddd"], accountIsDemo: false, listDifference: true), "should be able to set allowed machine IDs")
2927 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2928 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2929 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2932 func testMachineIDListHandlingWithPeers() throws {
2933 let description = tmpStoreDescription(name: "container.db")
2934 let (container, peerID1) = try establish(reload: false, store: description)
2936 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2938 let unknownMachineID = "not-on-list"
2939 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2940 containerID: "second",
2941 machineID: unknownMachineID,
2942 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]), accountIsDemo: false,
2945 // And the first container accepts the join...
2946 let (_, _, cUpdateError) = container.updateSync(test: self)
2947 XCTAssertNil(cUpdateError, "Should be able to update first container")
2948 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2950 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2953 func testMachineIDListHandlingInDemoAccounts() throws {
2954 // Demo accounts have no machine IDs in their lists
2955 let description = tmpStoreDescription(name: "container.db")
2956 var (container, peerID1) = try establish(reload: false, contextID: OTDefaultContext, allowedMachineIDs: Set(), accountIsDemo: true, store: description)
2958 // And so we just don't write down any MIDs
2959 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2961 // Even when joining...
2962 let unknownMachineID = "not-on-list"
2963 let (c2, peerID2) = try self.joinByVoucher(sponsor: container,
2964 containerID: "second",
2965 machineID: unknownMachineID,
2967 accountIsDemo: true,
2969 try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2971 c2.containerMO.honorIDMSListChanges = "NO"
2973 // And the first container accepts the join...
2974 let (_, _, cUpdateError) = container.updateSync(test: self)
2975 XCTAssertNil(cUpdateError, "Should be able to update first container")
2976 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2978 // And still has nothing in its list...
2979 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2983 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2984 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO")
2986 XCTFail("Creating container errored: \(error)")
2989 // Even after a full list set
2990 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: [], accountIsDemo: true, listDifference: false), "should be able to set allowed machine IDs")
2991 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2993 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2994 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO")
2997 func testContainerAndModelConsistency() throws {
2998 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2999 let description = tmpStoreDescription(name: "container.db")
3000 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
3001 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, _, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3003 XCTAssertNotNil(peerID)
3004 XCTAssertNotNil(permanentInfo)
3005 XCTAssertNotNil(permanentInfoSig)
3006 XCTAssertNotNil(stableInfo)
3007 XCTAssertNotNil(stableInfoSig)
3009 let containerName = ContainerName(container: "test", context: OTDefaultContext)
3010 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
3011 let mom = getOrMakeModel(url: url)
3012 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
3013 persistentContainer.persistentStoreDescriptions = [description]
3015 persistentContainer.loadPersistentStores { _, error in
3016 XCTAssertNil(error, "Should be no error loading persistent stores")
3019 let moc = persistentContainer.newBackgroundContext()
3020 moc.performAndWait {
3021 let containerMO = ContainerMO(context: moc)
3022 containerMO.name = containerName.asSingleString()
3023 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
3024 containerMO.egoPeerID = peerID
3025 containerMO.egoPeerPermanentInfo = permanentInfo
3026 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
3027 containerMO.egoPeerStableInfoSig = stableInfoSig
3028 containerMO.egoPeerStableInfo = stableInfo
3029 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
3031 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
3032 let newClock = containerEgoStableInfo!.clock + 2
3033 let info3 = try TPPeerStableInfo(clock: newClock,
3034 frozenPolicyVersion: containerEgoStableInfo!.frozenPolicyVersion,
3035 flexiblePolicyVersion: containerEgoStableInfo!.flexiblePolicyVersion!,
3036 policySecrets: containerEgoStableInfo!.policySecrets,
3037 syncUserControllableViews: containerEgoStableInfo!.syncUserControllableViews,
3038 deviceName: containerEgoStableInfo!.deviceName,
3039 serialNumber: containerEgoStableInfo!.serialNumber,
3040 osVersion: containerEgoStableInfo!.osVersion,
3041 signing: peerKeys.signingKey,
3042 recoverySigningPubKey: containerEgoStableInfo!.recoverySigningPublicKey,
3043 recoveryEncryptionPubKey: containerEgoStableInfo!.recoveryEncryptionPublicKey)
3045 //setting the containerMO's ego stable info to an old clock
3046 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
3047 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
3049 //now we are adding the ego stable info with a clock of 3 to the list of peers
3050 let peer = PeerMO(context: moc)
3051 peer.peerID = peerID
3052 peer.permanentInfo = permanentInfo
3053 peer.permanentInfoSig = permanentInfoSig
3054 peer.stableInfo = info3.data
3055 peer.stableInfoSig = info3.sig
3056 peer.isEgoPeer = true
3057 peer.container = containerMO
3059 containerMO.addToPeers(peer)
3061 //at this point the containerMO's egoStableInfo should have a clock of 1
3062 //the saved ego peer in the peer list has a clock of 3
3065 XCTFail("load ego keys failed: \(error)")
3067 XCTAssertNoThrow(try! moc.save())
3070 // Now TPH boots up with a preexisting model
3071 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
3073 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
3074 XCTAssertNotNil(stableInfoAfterBoot)
3076 //after boot the clock should be updated to the one that was saved in the model
3077 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")
3080 func testRetryableError() throws {
3081 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorTimedOut, userInfo: nil)))
3082 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: NSURLErrorDomain, code: NSURLErrorUnknown, userInfo: nil)))
3083 XCTAssertTrue(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.networkFailure.rawValue, userInfo: nil)))
3084 XCTAssertFalse(RetryingInvocable.retryableError(error: NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: nil)))
3086 let sub0 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalServerInternalError.rawValue, userInfo: nil)
3087 let e0 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub0])
3088 XCTAssertTrue(RetryingInvocable.retryableError(error: e0))
3090 let sub1 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalGenericError.rawValue, userInfo: nil)
3091 let e1 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: sub1])
3092 XCTAssertFalse(RetryingInvocable.retryableError(error: e1))
3094 let cf2 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
3095 let int2 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf2])
3096 let e2 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int2])
3097 XCTAssertFalse(RetryingInvocable.retryableError(error: e2))
3099 let cf3 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.retryableServerFailure.rawValue, userInfo: nil)
3100 let int3 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf3])
3101 let e3 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int3])
3102 XCTAssertTrue(RetryingInvocable.retryableError(error: e3))
3104 let cf4 = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.transactionalFailure.rawValue, userInfo: nil)
3105 let int4 = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cf4])
3106 let e4 = NSError(domain: CKErrorDomain, code: CKError.serverRejectedRequest.rawValue, userInfo: [NSUnderlyingErrorKey: int4])
3107 XCTAssertTrue(RetryingInvocable.retryableError(error: e4))
3110 func testEstablishWithEnforceIDMSListNotSetBehavior() throws {
3111 let contextID = "OTDefaultContext"
3112 let description = tmpStoreDescription(name: "container.db")
3114 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish)
3115 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3117 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3119 let state = container.getStateSync(test: self)
3120 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
3121 let secret = container.loadSecretSync(test: self, label: peerID!)
3122 XCTAssertNotNil(secret, "secret should not be nil")
3123 XCTAssertNil(error, "error should be nil")
3125 XCTAssertNotNil(peerID)
3126 XCTAssertNotNil(permanentInfo)
3127 XCTAssertNotNil(permanentInfoSig)
3130 _ = container.dumpSync(test: self)
3133 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish)
3135 XCTFail("Creating container errored: \(error)")
3138 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
3139 XCTAssertNil(error2)
3140 XCTAssertNotNil(peerID2)
3142 _ = container.dumpSync(test: self)
3143 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3146 func testJoinWithEnforceIDMSListNotSetBehavior() throws {
3147 let description = tmpStoreDescription(name: "container.db")
3148 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
3149 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
3150 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
3152 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3153 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3154 XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3156 print("preparing A")
3157 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
3158 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3159 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
3160 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
3163 let state = containerA.getStateSync(test: self)
3164 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
3165 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
3166 XCTAssertNotNil(secret, "secret should not be nil")
3167 XCTAssertNil(error, "error should be nil")
3170 XCTAssertNotNil(aPeerID)
3171 XCTAssertNotNil(aPermanentInfo)
3172 XCTAssertNotNil(aPermanentInfoSig)
3174 print("establishing A")
3176 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
3178 XCTAssertNotNil(peerID)
3181 print("preparing B")
3182 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, error2) =
3183 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3185 let state = containerB.getStateSync(test: self)
3186 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
3187 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
3188 XCTAssertNotNil(secret, "secret should not be nil")
3189 XCTAssertNil(error, "error should be nil")
3191 XCTAssertNil(error2)
3192 XCTAssertNotNil(bPeerID)
3193 XCTAssertNotNil(bPermanentInfo)
3194 XCTAssertNotNil(bPermanentInfoSig)
3197 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3198 print("A vouches for B, but doesn't provide any TLKShares")
3199 let (_, _, errorVouchingWithoutTLKs) =
3200 containerA.vouchSync(test: self,
3202 permanentInfo: bPermanentInfo!,
3203 permanentInfoSig: bPermanentInfoSig!,
3204 stableInfo: bStableInfo!,
3205 stableInfoSig: bStableInfoSig!,
3207 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
3208 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3210 print("A vouches for B, but doesn't only has provisional TLKs at the time")
3211 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3212 provisionalManateeKeySet.newUpload = true
3214 let (_, _, errorVouchingWithProvisionalTLKs) =
3215 containerA.vouchSync(test: self,
3217 permanentInfo: bPermanentInfo!,
3218 permanentInfoSig: bPermanentInfoSig!,
3219 stableInfo: bStableInfo!,
3220 stableInfoSig: bStableInfoSig!,
3221 ckksKeys: [provisionalManateeKeySet])
3222 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
3223 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3225 print("A vouches for B")
3226 let (voucherData, voucherSig, error3) =
3227 containerA.vouchSync(test: self,
3229 permanentInfo: bPermanentInfo!,
3230 permanentInfoSig: bPermanentInfoSig!,
3231 stableInfo: bStableInfo!,
3232 stableInfoSig: bStableInfoSig!,
3233 ckksKeys: [self.manateeKeySet])
3234 XCTAssertNil(error3)
3235 XCTAssertNotNil(voucherData)
3236 XCTAssertNotNil(voucherSig)
3238 // As part of the vouch, A should have uploaded a tlkshare for B
3239 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3242 let (peerID, _, _, error) = containerB.joinSync(test: self,
3243 voucherData: voucherData!,
3244 voucherSig: voucherSig!,
3248 XCTAssertEqual(peerID, bPeerID!)
3251 _ = containerA.dumpSync(test: self)
3252 _ = containerB.dumpSync(test: self)
3253 _ = containerC.dumpSync(test: self)
3255 print("preparing C")
3256 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, error4) =
3257 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3259 let state = containerC.getStateSync(test: self)
3260 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
3261 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
3262 XCTAssertNotNil(secret, "secret should not be nil")
3263 XCTAssertNil(error, "error should be nil")
3265 XCTAssertNil(error4)
3266 XCTAssertNotNil(cPeerID)
3267 XCTAssertNotNil(cPermanentInfo)
3268 XCTAssertNotNil(cPermanentInfoSig)
3271 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
3272 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
3273 provisionalEngramKeySet.newUpload = true
3275 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3276 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
3278 print("B vouches for C")
3279 let (voucherData, voucherSig, error) =
3280 containerB.vouchSync(test: self,
3282 permanentInfo: cPermanentInfo!,
3283 permanentInfoSig: cPermanentInfoSig!,
3284 stableInfo: cStableInfo!,
3285 stableInfoSig: cStableInfoSig!,
3286 ckksKeys: [self.manateeKeySet])
3288 XCTAssertNotNil(voucherData)
3289 XCTAssertNotNil(voucherSig)
3291 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3294 let (peerID, _, _, error2) = containerC.joinSync(test: self,
3295 voucherData: voucherData!,
3296 voucherSig: voucherSig!,
3297 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
3299 XCTAssertNil(error2)
3300 XCTAssertEqual(peerID, cPeerID!)
3302 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
3303 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
3304 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
3309 let (_, _, error) = containerA.updateSync(test: self)
3314 let state = containerA.getStateSync(test: self)
3315 let a = state.peers[aPeerID!]!
3316 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
3319 _ = containerA.dumpSync(test: self)
3320 _ = containerB.dumpSync(test: self)
3321 _ = containerC.dumpSync(test: self)
3323 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3324 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3325 XCTAssertEqual(containerC.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3328 func testPreApprovedJoinWithEnforceIDMSListNotSetBehavior() throws {
3329 let description = tmpStoreDescription(name: "container.db")
3330 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
3331 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
3333 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3334 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3336 print("preparing A")
3337 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
3338 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3340 XCTAssertNotNil(aPeerID)
3341 XCTAssertNotNil(aPermanentInfo)
3342 XCTAssertNotNil(aPermanentInfoSig)
3344 XCTAssertNotNil(aPolicy, "Should have a policy coming back from a successful prepare")
3345 XCTAssertEqual(aPolicy?.version, prevailingPolicyVersion, "Policy coming back from prepare() should be prevailing policy version")
3347 print("preparing B")
3348 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, error2) =
3349 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3351 let state = containerB.getStateSync(test: self)
3352 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
3353 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
3354 XCTAssertNotNil(secret, "secret should not be nil")
3355 XCTAssertNil(error, "error should be nil")
3357 XCTAssertNil(error2)
3358 XCTAssertNotNil(bPeerID)
3359 XCTAssertNotNil(bPermanentInfo)
3360 XCTAssertNotNil(bPermanentInfoSig)
3362 // Now, A establishes preapproving B
3363 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
3365 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
3366 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
3368 print("establishing A")
3370 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
3372 XCTAssertNotNil(peerID)
3376 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3378 print("B joins by preapproval, and uploads all TLKShares that it has")
3379 let (bJoinedPeerID, _, policy, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
3380 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
3381 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
3382 XCTAssertNotNil(policy, "Should have a policy back from preapprovedjoin")
3384 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3387 _ = containerA.dumpSync(test: self)
3388 _ = containerB.dumpSync(test: self)
3390 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3391 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3394 func testReloadingContainerIDMSListVariable() throws {
3395 let store = tmpStoreDescription(name: "container.db")
3396 let contextID = "contextID"
3397 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
3399 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3401 let state = container.getStateSync(test: self)
3402 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
3403 let secret = container.loadSecretSync(test: self, label: peerID!)
3404 XCTAssertNotNil(secret, "secret should not be nil")
3405 XCTAssertNil(error, "error should be nil")
3407 XCTAssertNotNil(peerID)
3408 XCTAssertNotNil(permanentInfo)
3409 XCTAssertNotNil(permanentInfoSig)
3412 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3414 _ = container.dumpSync(test: self)
3417 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
3418 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3420 XCTFail("Creating container errored: \(error)")
3423 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
3424 XCTAssertNil(error2)
3425 XCTAssertNotNil(peerID2)
3427 _ = container.dumpSync(test: self)