]> git.saurik.com Git - apple/security.git/blob - keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / TrustedPeersHelperUnitTests / TrustedPeersHelperUnitTests.swift
1 //
2 // TrustedPeersHelperUnitTests.swift
3 // TrustedPeersHelperUnitTests
4 //
5 // Created by Ben Williamson on 5/1/18.
6 //
7
8 import CloudKitCodeProtobuf
9 import CoreData
10 import XCTest
11
12 let testDSID = "123456789"
13
14 let signingKey_384 = Data(base64Encoded: "BOQbPoiBnzuA0Cgc2QegjKGJqDtpkRenHwAxkYKJH1xELdaoIh8ifSch8sl18tpBYVUpEfdxz2ZSKif+dx7UPfu8WeTtpHkqm3M+9PTjr/KNNJCSR1PQNB5Jh+sRiQ+cpJnoTzm+IZSIukylamAcL3eA0nMUM0Zc2u4TijrbTgVND22WzSirUkwSK3mA/prk9A==")
15
16 let encryptionKey_384 = Data(base64Encoded: "BE1RuazBWmSEx0XVGhobbrdSE6fRQOrUrYEQnBkGl4zJq9GCeRoYvbuWNYFcOH0ijCRz9pYILsTn3ajT1OknlvcKmuQ7SeoGWzk9cBZzT5bBEwozn2gZxn80DQoDkmejywlH3D0/cuV6Bxexu5KMAFGqg6eN6th4sQABL5EuI9zKPuxHStM/b9B1LyqcnRKQHA==")
17
18 let symmetricKey_384 = Data(base64Encoded: "MfHje3Y/mWV0q+grjwZ4VxuqB7OreYHLxYkeeCiNjjY=")
19
20 let recovery_signingKey_384 = Data(base64Encoded: "BK5nrmP6oitJHtGV2Josk5cUKnG3pqxgEP8uzyPtNXgAMNHZoDKwCKFXpUzQSgbYiR4G2XZY2Q0+qSCKN7YSY2KNKE0hM9p4GvABBmAWKW/O9eFd5ugKQWisn25a/7nieIw8CQ81hBDR7R/vBpfLVtzE8ieRA8JPGqulQ5RdLcClFrD3B8BPJAZpLv4OP1CLDA==")
21
22 let recovery_encryptionKey_384 = Data(base64Encoded: "BKkZpYHTbMi2yrWFo+ErM3HbcYJCngPuWDYoVUD7egKkmiHFvv1Bsk0j/Dcj3xTR12vj5QOpZQV3GzE5estf75BV+EZz1cjUUSi/MysfpKsqEbwYrhIEkmeyMGr7CVWQWRLR2LnoihnQajvWi1LmO0AoDl3+LzVgTJBjjDQ5ANyw0Yv1EgOgBvZsLA9UTN4oAg==")
23
24 class TrustedPeersHelperUnitTests: XCTestCase {
25 var tmpPath: String!
26 var tmpURL: URL!
27 var cuttlefish: FakeCuttlefishServer!
28
29 var manateeKeySet: CKKSKeychainBackedKeySet!
30
31 override static func setUp() {
32 super.setUp()
33
34 SecTapToRadar.disableTTRsEntirely()
35
36 // Turn on NO_SERVER stuff
37 securityd_init_local_spi()
38
39 SecCKKSDisable()
40 }
41
42 override func setUp() {
43 super.setUp()
44
45 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
46 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
47
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)
51 do {
52 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
53 SetCustomHomeURLString(tmpPath as CFString)
54 SecKeychainDbReset(nil)
55 } catch {
56 XCTFail("setUp failed: \(error)")
57 }
58
59 // Actually load the database.
60 kc_with_dbt(true, nil) { _ in
61 false
62 }
63
64 // Now that the keychain is alive, perform test setup
65 do {
66 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
67 } catch {
68 XCTFail("Creation of fake key hierarchies failed: \(error)")
69 }
70 }
71
72 override func tearDown() {
73 // Put teardown code here. This method is called after the invocation of each test method in the class.
74 cuttlefish = nil
75
76 if let nskeychainDir : NSURL = SecCopyHomeURL(), let keychainDir : URL = nskeychainDir as URL? {
77 SecItemDataSourceFactoryReleaseAll()
78 SecKeychainDbForceClose()
79 SecKeychainDbReset(nil)
80
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) {
84 do {
85 try FileManager.default.removeItem(at: keychainDir)
86 } catch {
87 print("Failed to remove keychain directory: \(error)")
88 }
89 }
90 }
91
92 super.tearDown()
93 }
94
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)
100
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")
104
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)
108
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
112
113 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
114 }
115
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
120 }
121
122 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
123 }
124
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
129 }
130
131 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
132 }
133
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")
138 return
139 }
140
141 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
142 XCTFail("No dynamicInfo for ego peer")
143 return
144 }
145
146 _ = peerIDs.map {
147 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
148 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
149 }
150 }
151
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")
156 return
157 }
158
159 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
160 XCTFail("No dynamicInfo for ego peer")
161 return
162 }
163
164 _ = peerIDs.map {
165 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
166 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
167 }
168 }
169
170 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
171 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
172 return NSPersistentStoreDescription(url: tmpStoreURL)
173 }
174
175 func establish(reload: Bool,
176 store: NSPersistentStoreDescription) throws -> (Container, String) {
177 return try self.establish(reload: reload, contextID: OTDefaultContext, accountIsDemo: false, store: store)
178 }
179
180 func establish(reload: Bool,
181 contextID: String,
182 allowedMachineIDs: Set<String> = Set(["aaa", "bbb", "ccc"]),
183 accountIsDemo: Bool,
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)
188
189 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: allowedMachineIDs, accountIsDemo: accountIsDemo, listDifference: !allowedMachineIDs.isEmpty), "should be able to set allowed machine IDs")
190
191 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self,
192 epoch: 1,
193 machineID: "aaa",
194 bottleSalt: "123456789",
195 bottleID: UUID().uuidString,
196 modelID: modelID,
197 syncUserControllableViews: syncUserControllableViews)
198
199 do {
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")
205 }
206 XCTAssertNotNil(peerID)
207 XCTAssertNotNil(permanentInfo)
208 XCTAssertNotNil(permanentInfoSig)
209 XCTAssertNil(error)
210
211 _ = container.dumpSync(test: self)
212
213 if reload {
214 do {
215 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
216 } catch {
217 XCTFail("Creating container errored: \(error)")
218 }
219 }
220
221 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
222 XCTAssertNil(error2)
223 XCTAssertNotNil(peerID2)
224
225 _ = container.dumpSync(test: self)
226
227 return (container, peerID!)
228 }
229
230 func testEstablishWithReload() throws {
231 let description = tmpStoreDescription(name: "container.db")
232 let (container, peerID) = try establish(reload: true, store: description)
233
234 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
235
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)")
241 }
242
243 func testEstablishNoReload() throws {
244 let description = tmpStoreDescription(name: "container.db")
245 let (container, peerID) = try establish(reload: false, store: description)
246
247 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
248
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)")
254 }
255
256 func testEstablishWithUserSyncableViews() throws {
257 let description = tmpStoreDescription(name: "container.db")
258
259 let (container, peerID) = try self.establish(reload: false,
260 contextID: OTDefaultContext,
261 accountIsDemo: false,
262 syncUserControllableViews: .ENABLED,
263 store: description)
264
265 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
266
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")
271
272 XCTAssertEqual(policy?.syncUserControllableViews, .ENABLED, "Peer should desire to sync user controllable views (per request)")
273 }
274
275 func testEstablishWithoutUserSyncableViews() throws {
276 let description = tmpStoreDescription(name: "container.db")
277
278 let (container, peerID) = try self.establish(reload: false,
279 contextID: OTDefaultContext,
280 accountIsDemo: false,
281 syncUserControllableViews: .DISABLED,
282 store: description)
283
284 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
285
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")
290
291 XCTAssertEqual(policy?.syncUserControllableViews, .DISABLED, "Peer should not desire to sync user controllable views (per request)")
292 }
293
294 func testEstablishWithoutUserSyncableViewsOnWatch() throws {
295 let description = tmpStoreDescription(name: "container.db")
296
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,
301 modelID: "Watch1,1",
302 syncUserControllableViews: .FOLLOWING,
303 store: description)
304
305 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
306
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")
311
312 XCTAssertEqual(policy?.syncUserControllableViews, .FOLLOWING, "Peer should desire to sync user controllable views (ignoring the request)")
313 }
314
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)
318
319 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
320 do {
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")
326 }
327 XCTAssertNotNil(peerID)
328 XCTAssertNotNil(permanentInfo)
329 XCTAssertNotNil(permanentInfoSig)
330 XCTAssertNil(error)
331
332 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
333
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")
336
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")
340 }
341
342 func joinByVoucher(sponsor: Container,
343 containerID: String,
344 machineID: String,
345 machineIDs: Set<String>,
346 accountIsDemo: Bool,
347 store: NSPersistentStoreDescription) throws -> (Container, String) {
348 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
349 persistentStoreDescription: store,
350 cuttlefish: cuttlefish)
351
352 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: accountIsDemo, listDifference: !machineIDs.isEmpty), "Should be able to set machine IDs")
353
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")
357 XCTAssertNil(error)
358 XCTAssertNotNil(peerID)
359 XCTAssertNotNil(permanentInfo)
360 XCTAssertNotNil(permanentInfoSig)
361 XCTAssertNotNil(stableInfo)
362 XCTAssertNotNil(stableInfoSig)
363
364 do {
365 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
366
367 print("\(sponsor) vouches for \(containerID)")
368 let (voucherData, voucherSig, vouchError) =
369 sponsor.vouchSync(test: self,
370 peerID: peerID!,
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)
379
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"))
382
383 print("\(containerID) joins")
384 let (joinedPeerID, _, _, joinError) = c.joinSync(test: self,
385 voucherData: voucherData!,
386 voucherSig: voucherSig!,
387 ckksKeys: [],
388 tlkShares: [])
389 XCTAssertNil(joinError)
390 XCTAssertEqual(joinedPeerID, peerID!)
391 }
392
393 return (c, peerID!)
394 }
395
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)
401
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))
406
407 print("preparing A")
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")
413
414 do {
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")
420 }
421 XCTAssertNil(error)
422 XCTAssertNotNil(aPeerID)
423 XCTAssertNotNil(aPermanentInfo)
424 XCTAssertNotNil(aPermanentInfoSig)
425
426 print("establishing A")
427 do {
428 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
429 XCTAssertNil(error)
430 XCTAssertNotNil(peerID)
431 }
432
433 do {
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)")
439 }
440
441 print("preparing B")
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")
444 do {
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")
450 }
451 XCTAssertNil(error2)
452 XCTAssertNotNil(bPeerID)
453 XCTAssertNotNil(bPermanentInfo)
454 XCTAssertNotNil(bPermanentInfoSig)
455
456 do {
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,
461 peerID: bPeerID!,
462 permanentInfo: bPermanentInfo!,
463 permanentInfoSig: bPermanentInfoSig!,
464 stableInfo: bStableInfo!,
465 stableInfoSig: bStableInfoSig!,
466 ckksKeys: [])
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"))
469
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
473
474 let (_, _, errorVouchingWithProvisionalTLKs) =
475 containerA.vouchSync(test: self,
476 peerID: bPeerID!,
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"))
484
485 print("A vouches for B")
486 let (voucherData, voucherSig, error3) =
487 containerA.vouchSync(test: self,
488 peerID: bPeerID!,
489 permanentInfo: bPermanentInfo!,
490 permanentInfoSig: bPermanentInfoSig!,
491 stableInfo: bStableInfo!,
492 stableInfoSig: bStableInfoSig!,
493 ckksKeys: [self.manateeKeySet])
494 XCTAssertNil(error3)
495 XCTAssertNotNil(voucherData)
496 XCTAssertNotNil(voucherSig)
497
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"))
500
501 print("B joins")
502 let (peerID, _, bPolicy, error) = containerB.joinSync(test: self,
503 voucherData: voucherData!,
504 voucherSig: voucherSig!,
505 ckksKeys: [],
506 tlkShares: [])
507 XCTAssertNil(error)
508 XCTAssertEqual(peerID, bPeerID!)
509
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)")
512 }
513
514 _ = containerA.dumpSync(test: self)
515 _ = containerB.dumpSync(test: self)
516 _ = containerC.dumpSync(test: self)
517
518 print("preparing C")
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")
521 do {
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")
527 }
528 XCTAssertNil(error4)
529 XCTAssertNotNil(cPeerID)
530 XCTAssertNotNil(cPermanentInfo)
531 XCTAssertNotNil(cPermanentInfoSig)
532
533 do {
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
537
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"))
540
541 print("B vouches for C")
542 let (voucherData, voucherSig, error) =
543 containerB.vouchSync(test: self,
544 peerID: cPeerID!,
545 permanentInfo: cPermanentInfo!,
546 permanentInfoSig: cPermanentInfoSig!,
547 stableInfo: cStableInfo!,
548 stableInfoSig: cStableInfoSig!,
549 ckksKeys: [self.manateeKeySet])
550 XCTAssertNil(error)
551 XCTAssertNotNil(voucherData)
552 XCTAssertNotNil(voucherSig)
553
554 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
555
556 print("C joins")
557 let (peerID, _, cPolicy, error2) = containerC.joinSync(test: self,
558 voucherData: voucherData!,
559 voucherSig: voucherSig!,
560 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
561 tlkShares: [])
562 XCTAssertNil(error2)
563 XCTAssertEqual(peerID, cPeerID!)
564
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)")
567
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"))
571 }
572
573 print("A updates")
574 do {
575 let (_, _, error) = containerA.updateSync(test: self)
576 XCTAssertNil(error)
577 }
578
579 do {
580 let state = containerA.getStateSync(test: self)
581 let a = state.peers[aPeerID!]!
582 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
583 }
584
585 _ = containerA.dumpSync(test: self)
586 _ = containerB.dumpSync(test: self)
587 _ = containerC.dumpSync(test: self)
588 }
589
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)
594
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))
598
599 print("preparing A")
600 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, aPolicy, error) =
601 containerA.prepareSync(test: self,
602 epoch: 1,
603 machineID: "aaa",
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")
611
612 XCTAssertNil(error)
613 XCTAssertNotNil(aPeerID)
614 XCTAssertNotNil(aPermanentInfo)
615 XCTAssertNotNil(aPermanentInfoSig)
616
617 print("establishing A")
618 do {
619 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
620 XCTAssertNil(error)
621 XCTAssertNotNil(peerID)
622 }
623
624 do {
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)")
629 }
630
631 print("preparing B")
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")
634 do {
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")
640 }
641 XCTAssertNil(error2)
642 XCTAssertNotNil(bPeerID)
643 XCTAssertNotNil(bPermanentInfo)
644 XCTAssertNotNil(bPermanentInfoSig)
645
646 do {
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,
651 peerID: bPeerID!,
652 permanentInfo: bPermanentInfo!,
653 permanentInfoSig: bPermanentInfoSig!,
654 stableInfo: bStableInfo!,
655 stableInfoSig: bStableInfoSig!,
656 ckksKeys: [])
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"))
659
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
663
664 let (_, _, errorVouchingWithProvisionalTLKs) =
665 containerA.vouchSync(test: self,
666 peerID: bPeerID!,
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"))
674
675 print("A vouches for B")
676 let (voucherData, voucherSig, error3) =
677 containerA.vouchSync(test: self,
678 peerID: bPeerID!,
679 permanentInfo: bPermanentInfo!,
680 permanentInfoSig: bPermanentInfoSig!,
681 stableInfo: bStableInfo!,
682 stableInfoSig: bStableInfoSig!,
683 ckksKeys: [self.manateeKeySet])
684 XCTAssertNil(error3)
685 XCTAssertNotNil(voucherData)
686 XCTAssertNotNil(voucherSig)
687
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"))
690
691 print("B joins")
692 let (peerID, _, bPolicy, error) = containerB.joinSync(test: self,
693 voucherData: voucherData!,
694 voucherSig: voucherSig!,
695 ckksKeys: [],
696 tlkShares: [])
697 XCTAssertNil(error)
698 XCTAssertEqual(peerID, bPeerID!)
699
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)")
702 }
703 }
704
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)
709
710 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
711 do {
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")
717 }
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")
722
723 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs")
724
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")
728
729 print("preparing B")
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")
732 do {
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")
738 }
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")
743
744 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"], accountIsDemo: false), "should be able to set allowed machine IDs on container B")
745
746 do {
747 print("A vouches for B")
748 let (voucherData, voucherSig, error3) =
749 containerA.vouchSync(test: self,
750 peerID: bPeerID!,
751 permanentInfo: bPermanentInfo!,
752 permanentInfoSig: bPermanentInfoSig!,
753 stableInfo: bStableInfo!,
754 stableInfoSig: bStableInfoSig!,
755 ckksKeys: [])
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")
759
760 print("B joins")
761 let (peerID, _, _, error) = containerB.joinSync(test: self,
762 voucherData: voucherData!,
763 voucherSig: voucherSig!,
764 ckksKeys: [],
765 tlkShares: [])
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")
768 }
769 }
770
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)
774
775 let machineIDs = Set(["aaa"])
776 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
777
778 print("preparing A")
779 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
780 do {
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")
786 }
787 XCTAssertNil(error)
788 XCTAssertNotNil(aPeerID)
789 XCTAssertNotNil(aPermanentInfo)
790 XCTAssertNotNil(aPermanentInfoSig)
791
792 print("establishing A")
793 do {
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: [])
796 XCTAssertNil(error)
797 XCTAssertNotNil(peerID)
798 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
799 }
800
801 print("reset A")
802 do {
803 let error = containerA.resetSync(resetReason: .testGenerated, test: self)
804 XCTAssertNil(error)
805 }
806 do {
807 let (dict, error) = containerA.dumpSync(test: self)
808 XCTAssertNil(error)
809 XCTAssertNotNil(dict)
810 let peers: [Any] = dict!["peers"] as! [Any]
811 XCTAssertEqual(0, peers.count)
812 }
813 }
814
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)
818
819 let machineIDs = Set(["aaa"])
820 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
821
822 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
823 do {
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")
829 }
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")
834
835 do {
836 let (dict, error) = containerA.dumpSync(test: self)
837 XCTAssertNil(error, "Should be no error dumping")
838 XCTAssertNotNil(dict, "Should receive a dump dictionary")
839
840 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
841 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
842
843 let selfPeer: String? = selfInfo!["peerID"] as! String?
844 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
845 }
846
847 do {
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 ")
852 }
853 do {
854 let (dict, error) = containerA.dumpSync(test: self)
855
856 XCTAssertNil(error, "Should be no error dumping")
857 XCTAssertNotNil(dict, "Should receive a dump dictionary")
858
859 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
860 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
861
862 let selfPeer: String? = selfInfo!["peerID"] as! String?
863 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
864 }
865 }
866
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)
872
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))
876
877 print("preparing")
878 let (peerID, _, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
879 do {
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")
884 }
885 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
886 do {
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")
891 }
892 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
893 do {
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")
898 }
899 print("establishing A")
900 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
901
902 do {
903 print("A vouches for B")
904 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
905 peerID: bPeerID!,
906 permanentInfo: bPermanentInfo!,
907 permanentInfoSig: bPermanentInfoSig!,
908 stableInfo: bStableInfo!,
909 stableInfoSig: bStableInfoSig!,
910 ckksKeys: [])
911
912 print("B joins")
913 _ = containerB.joinSync(test: self,
914 voucherData: voucherData!,
915 voucherSig: voucherSig!,
916 ckksKeys: [],
917 tlkShares: [])
918 }
919
920 print("A updates")
921 _ = containerA.updateSync(test: self)
922 let earlyClock: TPCounter
923 do {
924 let state = containerA.getStateSync(test: self)
925 let b = state.peers[bPeerID!]!
926 earlyClock = b.dynamicInfo!.clock
927 }
928
929 // Take a snapshot
930 let snapshot = cuttlefish.state
931
932 do {
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!,
939 ckksKeys: [])
940
941 print("C joins")
942 _ = containerC.joinSync(test: self,
943 voucherData: voucherData!,
944 voucherSig: voucherSig!,
945 ckksKeys: [],
946 tlkShares: [])
947 }
948
949 print("B updates")
950 _ = containerB.updateSync(test: self)
951
952 print("A updates")
953 _ = containerA.updateSync(test: self)
954 let lateClock: TPCounter
955 do {
956 let state = containerA.getStateSync(test: self)
957 let b = state.peers[bPeerID!]!
958 lateClock = b.dynamicInfo!.clock
959 XCTAssertTrue(earlyClock < lateClock)
960 }
961
962 print("Reverting cuttlefish to the snapshot")
963 cuttlefish.state = snapshot
964 cuttlefish.makeSnapshot()
965
966 print("A updates, fetching the old snapshot from cuttlefish")
967 _ = containerA.updateSync(test: self)
968
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)
971 do {
972 let state = containerA.getStateSync(test: self)
973 let b = state.peers[bPeerID!]!
974 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
975 }
976 }
977
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
981
982 let missingTuple = TPPolicyVersion(version: 900, hash: "not a hash")
983
984 let policy1Tuple = TPPolicyVersion(version: 1, hash: "SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=")
985 let policy1Data = Data(base64Encoded: "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
986 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
987 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2")!
988
989 let policy3Tuple = TPPolicyVersion(version: 3, hash: "SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=")
990 let policy3Data = Data(base64Encoded: "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A=")!
991
992 let description = tmpStoreDescription(name: "container.db")
993 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
994
995 // nothing
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")
999
1000 // local stuff
1001 do {
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")
1007 }
1008
1009 // fetch remote
1010 do {
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")
1014
1015 XCTAssert(response3?.keys.contains(policy1Tuple) ?? false, "Should have retrieved request for policy1")
1016 XCTAssertEqual(response3?[policy1Tuple], policy1Data, "retrieved data matches known data")
1017
1018 XCTAssert(response3?.keys.contains(policy3Tuple) ?? false, "Should have retrieved request for policy3")
1019 XCTAssertEqual(response3?[policy3Tuple], policy3Data, "retrieved data matches known data")
1020 }
1021
1022 // invalid version
1023 do {
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")
1027 }
1028
1029 // valid + invalid
1030 do {
1031 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, versions: Set([missingTuple,
1032 policy1Tuple,
1033 policy3Tuple, ]))
1034 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
1035 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
1036 }
1037 }
1038
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")
1042
1043 let secretString = "i'm a secret!"
1044 XCTAssertNotNil(secretString, "secretString should not be nil")
1045
1046 let secretData: Data? = secretString.data(using: .utf8)
1047 XCTAssertNotNil(secretData, "secretData should not be nil")
1048
1049 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
1050 XCTAssertNotNil(keys, "keys should not be nil")
1051
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")
1057
1058 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
1059 XCTAssertNotNil(hash, "hash should not be nil")
1060
1061 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
1062 XCTAssertTrue(result, "result should be true")
1063
1064 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
1065 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
1066
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")
1071 }
1072
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"
1075
1076 let secret = secretString.data(using: .utf8)
1077
1078 do {
1079 let testv1 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
1080 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
1081
1082 let testv2 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
1083 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
1084
1085 let testv3 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
1086 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
1087
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)
1090
1091 let testv4 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
1092 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
1093
1094 let testv5 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
1095 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
1096
1097 let testv6 = try EscrowKeys.generateEscrowKey(keyType: EscrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
1098 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
1099 } catch {
1100 XCTFail("error testing escrow key test vectors \(error)")
1101 }
1102 }
1103
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"
1106
1107 let secret = secretString.data(using: .utf8)
1108
1109 do {
1110 let testv1 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: secret!, recoverySalt: testDSID)
1111 XCTAssertEqual(testv1, recovery_signingKey_384, "signing keys should match")
1112
1113 let testv2 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: secret!, recoverySalt: testDSID)
1114 XCTAssertEqual(testv2, recovery_encryptionKey_384, "encryption keys should match")
1115
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)
1118
1119 let testv4 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeySigning, masterSecret: newSecret!, recoverySalt: testDSID)
1120 XCTAssertNotEqual(testv4, recovery_signingKey_384, "signing keys should not match")
1121
1122 let testv5 = try RecoveryKeySet.generateRecoveryKey(keyType: RecoveryKeyType.kOTRecoveryKeyEncryption, masterSecret: newSecret!, recoverySalt: testDSID)
1123 XCTAssertNotEqual(testv5, recovery_encryptionKey_384, "encryption keys should not match")
1124 } catch {
1125 XCTFail("error testing RecoveryKey test vectors \(error)")
1126 }
1127 }
1128
1129 func testJoiningWithBottle() throws {
1130 var bottleA: BottleMO
1131 var entropy: Data
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)
1135
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))
1139
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")
1143 do {
1144 var state = containerA.getStateSync(test: self)
1145 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1146
1147 bottleA = state.bottles.removeFirst()
1148
1149 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1150 XCTAssertNotNil(secret, "secret should not be nil")
1151 XCTAssertNil(error, "error should be nil")
1152 }
1153 XCTAssertNotNil(aPeerID)
1154 XCTAssertNotNil(aPermanentInfo)
1155 XCTAssertNotNil(aPermanentInfoSig)
1156
1157 print("establishing A")
1158 do {
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: [])
1161 XCTAssertNil(error)
1162 XCTAssertNotNil(peerID)
1163 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1164 }
1165 do {
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!)
1169 entropy = secret!
1170 XCTAssertNotNil(secret, "secret should not be nil")
1171 XCTAssertNil(error, "error should be nil")
1172 }
1173
1174 _ = containerB.updateSync(test: self)
1175
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")
1179 do {
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")
1185 }
1186 XCTAssertNil(error2)
1187
1188 do {
1189 print("B prepares to join via bottle")
1190
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")
1195
1196 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1197
1198 XCTAssertNil(error3)
1199 XCTAssertNotNil(voucherData)
1200 XCTAssertNotNil(voucherSig)
1201
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"))
1204
1205 print("B joins")
1206 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1207 XCTAssertNil(error)
1208 XCTAssertEqual(peerID, bPeerID!)
1209
1210 // But afterward, it has one!
1211 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1212 }
1213 }
1214
1215 func testJoiningWithBottleAndEmptyBottleSalt() throws {
1216 var bottleA: BottleMO
1217 var entropy: Data
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)
1221
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))
1225
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")
1229 do {
1230 var state = containerA.getStateSync(test: self)
1231 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1232
1233 bottleA = state.bottles.removeFirst()
1234
1235 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1236 XCTAssertNotNil(secret, "secret should not be nil")
1237 XCTAssertNil(error, "error should be nil")
1238 }
1239 XCTAssertNotNil(aPeerID)
1240 XCTAssertNotNil(aPermanentInfo)
1241 XCTAssertNotNil(aPermanentInfoSig)
1242
1243 print("establishing A")
1244 do {
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: [])
1247 XCTAssertNil(error)
1248 XCTAssertNotNil(peerID)
1249 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1250 }
1251 do {
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!)
1255 entropy = secret!
1256 XCTAssertNotNil(secret, "secret should not be nil")
1257 XCTAssertNil(error, "error should be nil")
1258 }
1259
1260 _ = containerB.updateSync(test: self)
1261
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")
1265 do {
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")
1271 }
1272 XCTAssertNil(error2)
1273
1274 do {
1275 print("B prepares to join via bottle")
1276
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")
1281
1282 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1283
1284 XCTAssertNil(error3)
1285 XCTAssertNotNil(voucherData)
1286 XCTAssertNotNil(voucherSig)
1287
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"))
1290
1291 print("B joins")
1292 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1293 XCTAssertNil(error)
1294 XCTAssertEqual(peerID, bPeerID!)
1295
1296 // But afterward, it has one!
1297 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1298 }
1299 }
1300
1301 func testJoiningWithWrongEscrowRecordForBottle() throws {
1302 var entropy: Data
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)
1306
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))
1310
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")
1314 do {
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")
1320 }
1321 XCTAssertNil(error)
1322 XCTAssertNotNil(aPeerID)
1323 XCTAssertNotNil(aPermanentInfo)
1324 XCTAssertNotNil(aPermanentInfoSig)
1325
1326 print("establishing A")
1327 do {
1328 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1329 XCTAssertNil(error)
1330 XCTAssertNotNil(peerID)
1331 }
1332 do {
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!)
1336 entropy = secret!
1337 XCTAssertNotNil(secret, "secret should not be nil")
1338 XCTAssertNil(error, "error should be nil")
1339 }
1340
1341 _ = containerB.updateSync(test: self)
1342
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")
1346 do {
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")
1352 }
1353 XCTAssertNil(error2)
1354
1355 do {
1356 print("B joins via bottle")
1357
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")
1362
1363 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1364
1365 XCTAssertNotNil(error3)
1366 XCTAssertNil(voucherData)
1367 XCTAssertNil(voucherSig)
1368 }
1369 }
1370
1371 func testJoiningWithWrongBottle() throws {
1372 var bottleB: BottleMO
1373 var entropy: Data
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)
1377
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))
1381
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")
1385 do {
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")
1391 }
1392 XCTAssertNil(error)
1393 XCTAssertNotNil(aPeerID)
1394 XCTAssertNotNil(aPermanentInfo)
1395 XCTAssertNotNil(aPermanentInfoSig)
1396
1397 print("establishing A")
1398 do {
1399 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1400 XCTAssertNil(error)
1401 XCTAssertNotNil(peerID)
1402 }
1403 do {
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!)
1407 entropy = secret!
1408 XCTAssertNotNil(secret, "secret should not be nil")
1409 XCTAssertNil(error, "error should be nil")
1410 }
1411
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")
1415 do {
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")
1422 }
1423 XCTAssertNil(error2)
1424
1425 do {
1426 print("B joins via bottle")
1427
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")
1432
1433 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1434
1435 XCTAssertNotNil(error3)
1436 XCTAssertNil(voucherData)
1437 XCTAssertNil(voucherSig)
1438 }
1439 }
1440
1441 func testJoiningWithBottleAndBadSalt() throws {
1442 var bottleA: BottleMO
1443 var entropy: Data
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)
1447
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))
1451
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")
1455 do {
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")
1462 }
1463 XCTAssertNil(error)
1464 XCTAssertNotNil(aPeerID)
1465 XCTAssertNotNil(aPermanentInfo)
1466 XCTAssertNotNil(aPermanentInfoSig)
1467
1468 print("establishing A")
1469 do {
1470 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1471 XCTAssertNil(error)
1472 XCTAssertNotNil(peerID)
1473 }
1474 do {
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!)
1478 entropy = secret!
1479 XCTAssertNotNil(secret, "secret should not be nil")
1480 XCTAssertNil(error, "error should be nil")
1481 }
1482
1483 _ = containerB.updateSync(test: self)
1484
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")
1488 do {
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")
1494 }
1495 XCTAssertNil(error2)
1496
1497 do {
1498 print("B joins via bottle")
1499
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")
1504
1505 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1506
1507 XCTAssertNotNil(error3)
1508 XCTAssertNil(voucherData)
1509 XCTAssertNil(voucherSig)
1510 }
1511 }
1512
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)
1518
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))
1522
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")
1526 do {
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")
1533 }
1534 XCTAssertNil(error)
1535 XCTAssertNotNil(aPeerID)
1536 XCTAssertNotNil(aPermanentInfo)
1537 XCTAssertNotNil(aPermanentInfoSig)
1538
1539 print("establishing A")
1540 do {
1541 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1542 XCTAssertNil(error)
1543 XCTAssertNotNil(peerID)
1544 }
1545 do {
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")
1551 }
1552
1553 _ = containerB.updateSync(test: self)
1554
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")
1558 do {
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")
1564 }
1565 XCTAssertNil(error2)
1566
1567 do {
1568 print("B joins via bottle")
1569
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")
1574
1575 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1576
1577 XCTAssertNotNil(error3)
1578 XCTAssertNil(voucherData)
1579 XCTAssertNil(voucherSig)
1580 }
1581 }
1582
1583 func testJoiningWithNoFetchAllBottles() throws {
1584 var bottleA: BottleMO
1585 var entropy: Data
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)
1589
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))
1593
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")
1597 do {
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!)
1601 entropy = secret!
1602 bottleA = state.bottles.removeFirst()
1603 XCTAssertNotNil(secret, "secret should not be nil")
1604 XCTAssertNil(error, "error should be nil")
1605 }
1606 XCTAssertNil(error)
1607 XCTAssertNotNil(aPeerID)
1608 XCTAssertNotNil(aPermanentInfo)
1609 XCTAssertNotNil(aPermanentInfoSig)
1610
1611 print("establishing A")
1612 do {
1613 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1614 XCTAssertNil(error)
1615 XCTAssertNotNil(peerID)
1616 }
1617 do {
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")
1623 }
1624
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")
1628 do {
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")
1634 }
1635 XCTAssertNil(error2)
1636
1637 do {
1638 print("B joins via bottle")
1639
1640 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1641
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")
1646
1647 self.cuttlefish.fetchViableBottlesError.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
1648
1649 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1650
1651 XCTAssertNotNil(error3)
1652 XCTAssertNil(voucherData)
1653 XCTAssertNil(voucherSig)
1654 }
1655 }
1656
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)
1662
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))
1667
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")
1671 XCTAssertNil(error)
1672 XCTAssertNotNil(aPeerID)
1673 XCTAssertNotNil(aPermanentInfo)
1674 XCTAssertNotNil(aPermanentInfoSig)
1675
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")
1679
1680 print("preparing B")
1681 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, _, error2) =
1682 containerB.prepareSync(test: self,
1683 epoch: 1,
1684 machineID: "bbb",
1685 bottleSalt: "123456789",
1686 bottleID: UUID().uuidString,
1687 modelID: "iPhone1,1",
1688 syncUserControllableViews: .DISABLED)
1689 do {
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")
1695 }
1696 XCTAssertNil(error2)
1697 XCTAssertNotNil(bPeerID)
1698 XCTAssertNotNil(bPermanentInfo)
1699 XCTAssertNotNil(bPermanentInfoSig)
1700
1701 print("preparing C")
1702 let (cPeerID, cPermanentInfo, cPermanentInfoSig, _, _, _, cPrepareError) =
1703 containerC.prepareSync(test: self,
1704 epoch: 1,
1705 machineID: "ccc",
1706 bottleSalt: "123456789",
1707 bottleID: UUID().uuidString,
1708 modelID: "iPhone1,1",
1709 syncUserControllableViews: .ENABLED)
1710 do {
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")
1716 }
1717 XCTAssertNil(cPrepareError)
1718 XCTAssertNotNil(cPeerID)
1719 XCTAssertNotNil(cPermanentInfo)
1720 XCTAssertNotNil(cPermanentInfoSig)
1721
1722 // Now, A establishes preapproving B & C
1723 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1724
1725 let aPermanentInfoParsed = TPPeerPermanentInfo(peerID: aPeerID!, data: aPermanentInfo!, sig: aPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1726 XCTAssertNotNil(aPermanentInfoParsed, "Should have parsed A's permanent info")
1727
1728 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1729 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1730
1731 let cPermanentInfoParsed = TPPeerPermanentInfo(peerID: cPeerID!, data: cPermanentInfo!, sig: cPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1732 XCTAssertNotNil(cPermanentInfoParsed, "Should have parsed C's permanent info")
1733
1734 print(bPermanentInfoParsed!.signingPubKey.spki().base64EncodedString())
1735 print(cPermanentInfoParsed!.signingPubKey.spki().base64EncodedString())
1736
1737 print("establishing A")
1738 do {
1739 let (peerID, _, _, error) = containerA.establishSync(test: self,
1740 ckksKeys: [self.manateeKeySet],
1741 tlkShares: [],
1742 preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki(),
1743 cPermanentInfoParsed!.signingPubKey.spki(), ])
1744 XCTAssertNil(error)
1745 XCTAssertNotNil(peerID)
1746
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)")
1751 }
1752
1753 do {
1754 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1755
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],
1759 tlkShares: [],
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")
1766
1767 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1768 }
1769
1770 do {
1771 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1772
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],
1776 tlkShares: [],
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")
1784
1785 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1786 }
1787
1788 _ = containerA.dumpSync(test: self)
1789 _ = containerB.dumpSync(test: self)
1790 _ = containerC.dumpSync(test: self)
1791 }
1792
1793 func testDepart() throws {
1794 let description = tmpStoreDescription(name: "container.db")
1795 let (container, peerID) = try establish(reload: false, store: description)
1796
1797 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1798 assertDistrusts(context: container, peerIDs: [peerID])
1799 }
1800
1801 func testDistrustPeers() throws {
1802 let store = tmpStoreDescription(name: "container.db")
1803 let (c, peerID1) = try establish(reload: false, store: store)
1804
1805 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1806 containerID: "second",
1807 machineID: "bbb",
1808 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1809 store: store)
1810
1811 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1812 containerID: "third",
1813 machineID: "ccc",
1814 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
1815 store: store)
1816
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])
1820
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])
1824
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")
1827
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])
1831
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])
1836
1837 let (_, _, c3UpdateError) = c3.updateSync(test: self)
1838 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1839 assertDistrusts(context: c3, peerIDs: [peerID3])
1840 }
1841
1842 func testFetchWithBadChangeToken() throws {
1843 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1844
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"))
1848
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")
1853
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)")
1860 }
1861 }
1862
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")
1872
1873 let (c, peerID) = try establish(reload: false, store: description)
1874 XCTAssertNotNil(peerID, "establish should return a peer id")
1875
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")
1881 }
1882
1883 func testBottles() {
1884 do {
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")
1888
1889 let keys = bottle.escrowKeys
1890 XCTAssertNotNil(keys, "keys should not be nil")
1891
1892 XCTAssertNotNil(bottle, "bottle should not be nil")
1893
1894 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1895
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))
1898
1899 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1900
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")
1903
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")
1906
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")
1909
1910 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1911 } catch {
1912 XCTFail("error testing bottles \(error)")
1913 }
1914 }
1915
1916 func testFetchBottles() throws {
1917 let store = tmpStoreDescription(name: "container.db")
1918 let (c, _) = try establish(reload: false, store: store)
1919
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")
1924
1925 do {
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")
1929 }
1930
1931 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1932 do {
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")
1936 }
1937
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")
1942
1943 do {
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")
1947 }
1948 }
1949
1950 func testFetchBottlesAfterCacheExpires() throws {
1951 OctagonSetOptimizationEnabled(true)
1952 OctagonSetEscrowRecordFetchEnabled(true)
1953
1954 var bottleA: BottleMO
1955 var entropy: Data
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)
1959
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))
1963
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")
1967 do {
1968 var state = containerA.getStateSync(test: self)
1969 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
1970
1971 bottleA = state.bottles.removeFirst()
1972
1973 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1974 XCTAssertNotNil(secret, "secret should not be nil")
1975 XCTAssertNil(error, "error should be nil")
1976 }
1977 XCTAssertNotNil(aPeerID)
1978 XCTAssertNotNil(aPermanentInfo)
1979 XCTAssertNotNil(aPermanentInfoSig)
1980
1981 print("establishing A")
1982 do {
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: [])
1985 XCTAssertNil(error)
1986 XCTAssertNotNil(peerID)
1987 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1988 }
1989 do {
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!)
1993 entropy = secret!
1994 XCTAssertNotNil(secret, "secret should not be nil")
1995 XCTAssertNil(error, "error should be nil")
1996 }
1997
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")
2002
2003 do {
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")
2007 }
2008
2009 //have another peer join
2010 _ = containerB.updateSync(test: self)
2011
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")
2015 do {
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")
2021 }
2022 XCTAssertNil(error2)
2023
2024 do {
2025 print("B prepares to join via bottle")
2026
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")
2031
2032 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
2033
2034 XCTAssertNil(error3)
2035 XCTAssertNotNil(voucherData)
2036 XCTAssertNotNil(voucherSig)
2037
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"))
2040
2041 print("B joins")
2042 let (peerID, _, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
2043 XCTAssertNil(error)
2044 XCTAssertEqual(peerID, bPeerID!)
2045
2046 // But afterward, it has one!
2047 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2048 }
2049
2050 //now fetch bottles and we should get the cached version
2051 let (_, _, _) = containerA.fetchViableBottlesSync(test: self)
2052 do {
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")
2056 }
2057
2058 sleep(2)
2059
2060 //now fetch bottles again after the cache expired
2061 containerA.escrowCacheTimeout = 2.0
2062
2063 let (_, _, _) = containerA.fetchViableBottlesSync(test: self)
2064 do {
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")
2068 }
2069 }
2070
2071 func testTrustStatus() throws {
2072 let store = tmpStoreDescription(name: "container.db")
2073
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")
2083
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")
2091
2092 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
2093 persistentStoreDescription: store,
2094 cuttlefish: self.cuttlefish)
2095
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")
2102 }
2103
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")
2113
2114 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
2115 XCTAssertTrue(result, "result should be true")
2116
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")
2121 }
2122
2123 func testSetRecoveryKey() throws {
2124 let store = tmpStoreDescription(name: "container.db")
2125
2126 let c = try Container(name: ContainerName(container: "c", context: "context"),
2127 persistentStoreDescription: store,
2128 cuttlefish: self.cuttlefish)
2129
2130 let machineIDs = Set(["aaa", "bbb", "ccc"])
2131 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2132
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")
2136 do {
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")
2142 }
2143 XCTAssertNil(error)
2144 XCTAssertNotNil(aPeerID)
2145 XCTAssertNotNil(aPermanentInfo)
2146 XCTAssertNotNil(aPermanentInfoSig)
2147
2148 print("establishing A")
2149 do {
2150 let (peerID, _, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2151 XCTAssertNil(error)
2152 XCTAssertNotNil(peerID)
2153 }
2154 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
2155 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
2156
2157 let (_, setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
2158 XCTAssertNil(setRecoveryError, "error should be nil")
2159 }
2160
2161 func roundTripThroughSetValueTransformer(set: Set<String>) {
2162 let t = SetValueTransformer()
2163
2164 let transformedSet = t.transformedValue(set) as? Data
2165 XCTAssertNotNil(transformedSet, "SVT should return some data")
2166
2167 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
2168 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
2169
2170 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
2171 }
2172
2173 func testSetValueTransformer() {
2174 roundTripThroughSetValueTransformer(set: Set<String>([]))
2175 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
2176 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
2177 }
2178
2179 func testGetRepairSuggestion() throws {
2180 let store = tmpStoreDescription(name: "container.db")
2181
2182 let c = try Container(name: ContainerName(container: "c", context: "context"),
2183 persistentStoreDescription: store,
2184 cuttlefish: self.cuttlefish)
2185
2186 let machineIDs = Set(["aaa", "bbb", "ccc"])
2187 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs, accountIsDemo: false))
2188
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")
2192 do {
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")
2198 }
2199 XCTAssertNil(error)
2200 XCTAssertNotNil(aPeerID)
2201 XCTAssertNotNil(aPermanentInfo)
2202 XCTAssertNotNil(aPermanentInfoSig)
2203
2204 print("establishing A")
2205 do {
2206 let (peerID, _, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2207 XCTAssertNil(error)
2208 XCTAssertNotNil(peerID)
2209 }
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)
2216 }
2217
2218 func testFetchChangesFailDuringVouchWithBottle() throws {
2219 var bottleA: BottleMO
2220 var entropy: Data
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)
2224
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))
2228
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")
2232 do {
2233 var state = containerA.getStateSync(test: self)
2234 XCTAssertTrue(state.bottles.contains { $0.peerID == aPeerID }, "should have a bottle for peer")
2235
2236 bottleA = state.bottles.removeFirst()
2237
2238 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
2239 XCTAssertNotNil(secret, "secret should not be nil")
2240 XCTAssertNil(error, "error should be nil")
2241 }
2242 XCTAssertNotNil(aPeerID)
2243 XCTAssertNotNil(aPermanentInfo)
2244 XCTAssertNotNil(aPermanentInfoSig)
2245
2246 print("establishing A")
2247 do {
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: [])
2250 XCTAssertNil(error)
2251 XCTAssertNotNil(peerID)
2252 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
2253 }
2254 do {
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!)
2258 entropy = secret!
2259 XCTAssertNotNil(secret, "secret should not be nil")
2260 XCTAssertNil(error, "error should be nil")
2261 }
2262
2263 _ = containerB.updateSync(test: self)
2264
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")
2268 do {
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")
2274 }
2275 XCTAssertNil(error2)
2276
2277 do {
2278 print("B prepares to join via bottle")
2279
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")
2284
2285 let (voucherData, voucherSig, _, _, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
2286
2287 XCTAssertNil(error3)
2288 XCTAssertNotNil(voucherData)
2289 XCTAssertNotNil(voucherSig)
2290
2291 self.cuttlefish.nextFetchErrors.append(FakeCuttlefishServer.makeCloudKitCuttlefishError(code: .changeTokenExpired))
2292
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"))
2295
2296 print("B joins")
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")
2300 }
2301 }
2302
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)
2307
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))
2311
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")
2315 do {
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")
2321 }
2322 XCTAssertNil(error)
2323 XCTAssertNotNil(aPeerID)
2324 XCTAssertNotNil(aPermanentInfo)
2325 XCTAssertNotNil(aPermanentInfoSig)
2326 XCTAssertNotNil(aStableInfo)
2327 XCTAssertNotNil(aStableInfoSig)
2328
2329 print("establishing A")
2330 do {
2331 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
2332 XCTAssertNil(error)
2333 XCTAssertNotNil(peerID)
2334 }
2335
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")
2339 do {
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")
2345 }
2346 XCTAssertNil(error2)
2347 XCTAssertNotNil(bPeerID)
2348 XCTAssertNotNil(bPermanentInfo)
2349 XCTAssertNotNil(bPermanentInfoSig)
2350
2351 do {
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,
2356 peerID: bPeerID!,
2357 permanentInfo: bPermanentInfo!,
2358 permanentInfoSig: bPermanentInfoSig!,
2359 stableInfo: bStableInfo!,
2360 stableInfoSig: bStableInfoSig!,
2361 ckksKeys: [])
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"))
2364
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
2368
2369 let (_, _, errorVouchingWithProvisionalTLKs) =
2370 containerA.vouchSync(test: self,
2371 peerID: bPeerID!,
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"))
2379
2380 print("A vouches for B")
2381 let (voucherData, voucherSig, error3) =
2382 containerA.vouchSync(test: self,
2383 peerID: bPeerID!,
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)
2392
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"))
2395
2396 print("B joins")
2397 let (peerID, _, _, error) = containerB.joinSync(test: self,
2398 voucherData: voucherData!,
2399 voucherSig: voucherSig!,
2400 ckksKeys: [],
2401 tlkShares: [])
2402 XCTAssertNil(error)
2403 XCTAssertEqual(peerID, bPeerID!)
2404 }
2405
2406 print("A updates")
2407 do {
2408 let (_, _, error) = containerA.updateSync(test: self)
2409 XCTAssertNil(error)
2410 }
2411 print("B updates")
2412 do {
2413 let (_, _, error) = containerB.updateSync(test: self)
2414 XCTAssertNil(error)
2415 }
2416
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!])
2420
2421 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
2422 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
2423
2424 let (_, setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
2425 XCTAssertNil(setRecoveryError, "error should be nil")
2426
2427 print("A updates")
2428 do {
2429 let (_, _, error) = containerA.updateSync(test: self)
2430 XCTAssertNil(error)
2431 }
2432 print("B updates")
2433 do {
2434 let (_, _, error) = containerB.updateSync(test: self)
2435 XCTAssertNil(error)
2436 }
2437
2438 do {
2439 let (dict, error) = containerA.dumpSync(test: self)
2440
2441 XCTAssertNil(error, "Should be no error dumping")
2442 XCTAssertNotNil(dict, "Should receive a dump dictionary")
2443
2444 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
2445 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
2446
2447 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
2448 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
2449
2450 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
2451 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
2452
2453 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
2454 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
2455 }
2456 }
2457
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")
2468
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")
2472
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)
2475
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")
2480 }
2481
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)
2485
2486 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2487
2488 XCTAssertNil(error)
2489 XCTAssertNotNil(peerID)
2490 XCTAssertNotNil(permanentInfo)
2491 XCTAssertNotNil(permanentInfoSig)
2492
2493 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2494
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")
2498
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")
2502
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")
2506
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")
2511
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")
2516
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")
2521
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")
2526
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")
2531
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)
2536
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")
2541
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")
2547 }
2548
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)
2552
2553 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2554
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")
2558
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")
2563
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")
2568
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")
2573
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")
2578
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")
2583
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")
2588
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)
2593
2594 container.moc.performAndWait {
2595 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2596
2597 knownMachineMOs.forEach {
2598 if $0.machineID == "xxx" {
2599 $0.modified = Date(timeIntervalSinceNow: -60 * 60 * 72)
2600 }
2601 }
2602
2603 try! container.moc.save()
2604 }
2605
2606 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2607
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)
2611 }
2612
2613 func testAllowSetUpgrade() throws {
2614 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2615
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]
2621
2622 persistentContainer.loadPersistentStores { _, error in
2623 XCTAssertNil(error, "Should be no error loading persistent stores")
2624 }
2625
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
2631
2632 XCTAssertNoThrow(try! moc.save())
2633 }
2634
2635 // Now TPH boots up with a preexisting model
2636 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2637
2638 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2639
2640 XCTAssertNil(error)
2641 XCTAssertNotNil(peerID)
2642 XCTAssertNotNil(permanentInfo)
2643 XCTAssertNotNil(permanentInfoSig)
2644
2645 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2646
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)
2650
2651 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2652 }
2653
2654 func testAllowStatusUpgrade() throws {
2655 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2656
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]
2662
2663 persistentContainer.loadPersistentStores { _, error in
2664 XCTAssertNil(error, "Should be no error loading persistent stores")
2665 }
2666
2667 let moc = persistentContainer.newBackgroundContext()
2668 moc.performAndWait {
2669 let containerMO = ContainerMO(context: moc)
2670 containerMO.name = containerName.asSingleString()
2671
2672 let machine = MachineMO(context: moc)
2673 machine.allowed = true
2674 machine.modified = Date()
2675 machine.machineID = "aaa"
2676 containerMO.addToMachines(machine)
2677
2678 let machineB = MachineMO(context: moc)
2679 machineB.allowed = false
2680 machineB.modified = Date()
2681 machineB.machineID = "bbb"
2682 containerMO.addToMachines(machineB)
2683
2684 try! moc.save()
2685 }
2686
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)
2690
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)
2694
2695 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2696 }
2697
2698 func testMachineIDListSetWithUnknownMachineIDs() throws {
2699 let description = tmpStoreDescription(name: "container.db")
2700 let (container, _) = try establish(reload: false, store: description)
2701
2702 container.moc.performAndWait {
2703 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2704
2705 knownMachineMOs.forEach {
2706 container.containerMO.removeFromMachines($0)
2707 }
2708
2709 try! container.moc.save()
2710 }
2711
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")
2716
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()
2720
2721 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2722 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2723
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")
2727
2728 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2729 try! container.moc.save()
2730 }
2731
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")
2734
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)
2738
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()
2742
2743 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2744 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2745
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")
2749
2750 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2751 }
2752
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")
2757 }
2758
2759 func testDuplicateVouchersWhenRegisteringOnModel() throws {
2760 let store = tmpStoreDescription(name: "container.db")
2761 let (c, peerID1) = try establish(reload: false, store: store)
2762
2763 let (c2, peerID2) = try joinByVoucher(sponsor: c,
2764 containerID: "second",
2765 machineID: "bbb",
2766 machineIDs: ["aaa", "bbb"], accountIsDemo: false,
2767 store: store)
2768
2769 let (_, _, cUpdateError) = c.updateSync(test: self)
2770 XCTAssertNil(cUpdateError, "Should be able to update first container")
2771 assertTrusts(context: c, peerIDs: [peerID1, peerID2])
2772
2773 let (_, _, c2UpdateError) = c2.updateSync(test: self)
2774 XCTAssertNil(c2UpdateError, "Should be able to update second container")
2775 assertTrusts(context: c2, peerIDs: [peerID1, peerID2])
2776
2777 //attempt to register a bunch of vouchers it likely already has
2778 for voucher in c2.model.allVouchers() {
2779 c.model.register(voucher)
2780 }
2781 XCTAssertEqual(c.model.allVouchers().count, 1, "voucher count should be 1")
2782 XCTAssertEqual(c2.model.allVouchers().count, 1, "voucher count should be 1")
2783 }
2784
2785 func testDuplicateVouchersOnload() throws {
2786 let description = tmpStoreDescription(name: "container.db")
2787
2788 let store = tmpStoreDescription(name: "container.db")
2789 let (c, peerID1) = try establish(reload: false, store: store)
2790
2791 let (c2, peerID2) = try joinByVoucher(sponsor: c,
2792 containerID: "second",
2793 machineID: "bbb",
2794 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
2795 store: store)
2796
2797 let (c3, peerID3) = try joinByVoucher(sponsor: c,
2798 containerID: "third",
2799 machineID: "ccc",
2800 machineIDs: ["aaa", "bbb", "ccc"], accountIsDemo: false,
2801 store: store)
2802
2803 let (_, _, cUpdateError) = c.updateSync(test: self)
2804 XCTAssertNil(cUpdateError, "Should be able to update first container")
2805
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)
2811
2812 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
2813
2814 var vouchers: [VoucherMO] = Array()
2815
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)
2821 }
2822 }
2823 for peer in c1Peers {
2824 for voucher in peer.vouchers! {
2825 let vouch = voucher as! VoucherMO
2826 vouchers.append(vouch)
2827 }
2828 }
2829
2830 for peer in c1Peers {
2831 for voucher in peer.vouchers! {
2832 let vouch = voucher as! VoucherMO
2833 vouchers.append(vouch)
2834 }
2835 }
2836
2837 //reload container
2838 XCTAssertEqual(vouchers.count, 6, "should have 6 vouchers")
2839
2840 c.moc.performAndWait {
2841 let containerMO = ContainerMO(context: c.moc)
2842 do {
2843
2844 for peer in containerMO.peers as! Set<PeerMO> {
2845 for vouch in vouchers {
2846 peer.addToVouchers(vouch)
2847 }
2848 }
2849 }
2850 XCTAssertNoThrow(try! c.moc.save())
2851 }
2852
2853 //reload container
2854 do {
2855 let container = try Container(name: c.name, persistentStoreDescription: description, cuttlefish: cuttlefish)
2856 XCTAssertEqual(container.model.allVouchers().count, 2, "voucher count should be 2")
2857 } catch {
2858 XCTFail("Creating container errored: \(error)")
2859 }
2860 }
2861
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)
2865
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")
2871
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,
2877 store: description)
2878
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])
2883
2884 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2885
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")
2888
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()
2892
2893 let unknownMOs = knownMachineMOs.filter { $0.machineID == unknownMachineID }
2894 XCTAssertEqual(unknownMOs.count, 1, "Should have one machine MO for ddd")
2895
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")
2899
2900 // Pretend that ddd was added 49 hours ago
2901 dddMO.modified = Date(timeIntervalSinceNow: -60 * 60 * 49)
2902 try! container.moc.save()
2903 }
2904
2905 //reload container
2906 do {
2907 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2908 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "YES", "honorIDMSListChanges should be YES")
2909 } catch {
2910 XCTFail("Creating container errored: \(error)")
2911 }
2912 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2913
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")
2919
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")
2924
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")
2930 }
2931
2932 func testMachineIDListHandlingWithPeers() throws {
2933 let description = tmpStoreDescription(name: "container.db")
2934 let (container, peerID1) = try establish(reload: false, store: description)
2935
2936 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2937
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,
2943 store: description)
2944
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])
2949
2950 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2951 }
2952
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)
2957
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)
2960
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,
2966 machineIDs: Set(),
2967 accountIsDemo: true,
2968 store: description)
2969 try self.assert(container: c2, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2970
2971 c2.containerMO.honorIDMSListChanges = "NO"
2972
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])
2977
2978 // And still has nothing in its list...
2979 try self.assert(container: container, allowedMachineIDs: Set([]), disallowedMachineIDs: [], unknownMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2980
2981 //reload container
2982 do {
2983 container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2984 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "NO", "honorIDMSListChanges should be NO")
2985 } catch {
2986 XCTFail("Creating container errored: \(error)")
2987 }
2988
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)
2992
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")
2995 }
2996
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")
3002 XCTAssertNil(error)
3003 XCTAssertNotNil(peerID)
3004 XCTAssertNotNil(permanentInfo)
3005 XCTAssertNotNil(permanentInfoSig)
3006 XCTAssertNotNil(stableInfo)
3007 XCTAssertNotNil(stableInfoSig)
3008
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]
3014
3015 persistentContainer.loadPersistentStores { _, error in
3016 XCTAssertNil(error, "Should be no error loading persistent stores")
3017 }
3018
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!)
3030 do {
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)
3044
3045 //setting the containerMO's ego stable info to an old clock
3046 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
3047 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
3048
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
3058
3059 containerMO.addToPeers(peer)
3060
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
3063
3064 } catch {
3065 XCTFail("load ego keys failed: \(error)")
3066 }
3067 XCTAssertNoThrow(try! moc.save())
3068 }
3069
3070 // Now TPH boots up with a preexisting model
3071 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
3072
3073 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
3074 XCTAssertNotNil(stableInfoAfterBoot)
3075
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")
3078 }
3079
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)))
3085
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))
3089
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))
3093
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))
3098
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))
3103
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))
3108 }
3109
3110 func testEstablishWithEnforceIDMSListNotSetBehavior() throws {
3111 let contextID = "OTDefaultContext"
3112 let description = tmpStoreDescription(name: "container.db")
3113
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")
3116
3117 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3118 do {
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")
3124 }
3125 XCTAssertNotNil(peerID)
3126 XCTAssertNotNil(permanentInfo)
3127 XCTAssertNotNil(permanentInfoSig)
3128 XCTAssertNil(error)
3129
3130 _ = container.dumpSync(test: self)
3131
3132 do {
3133 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: description, cuttlefish: cuttlefish)
3134 } catch {
3135 XCTFail("Creating container errored: \(error)")
3136 }
3137
3138 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
3139 XCTAssertNil(error2)
3140 XCTAssertNotNil(peerID2)
3141
3142 _ = container.dumpSync(test: self)
3143 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3144 }
3145
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)
3151
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")
3155
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")
3161
3162 do {
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")
3168 }
3169 XCTAssertNil(error)
3170 XCTAssertNotNil(aPeerID)
3171 XCTAssertNotNil(aPermanentInfo)
3172 XCTAssertNotNil(aPermanentInfoSig)
3173
3174 print("establishing A")
3175 do {
3176 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
3177 XCTAssertNil(error)
3178 XCTAssertNotNil(peerID)
3179 }
3180
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")
3184 do {
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")
3190 }
3191 XCTAssertNil(error2)
3192 XCTAssertNotNil(bPeerID)
3193 XCTAssertNotNil(bPermanentInfo)
3194 XCTAssertNotNil(bPermanentInfoSig)
3195
3196 do {
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,
3201 peerID: bPeerID!,
3202 permanentInfo: bPermanentInfo!,
3203 permanentInfoSig: bPermanentInfoSig!,
3204 stableInfo: bStableInfo!,
3205 stableInfoSig: bStableInfoSig!,
3206 ckksKeys: [])
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"))
3209
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
3213
3214 let (_, _, errorVouchingWithProvisionalTLKs) =
3215 containerA.vouchSync(test: self,
3216 peerID: bPeerID!,
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"))
3224
3225 print("A vouches for B")
3226 let (voucherData, voucherSig, error3) =
3227 containerA.vouchSync(test: self,
3228 peerID: bPeerID!,
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)
3237
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"))
3240
3241 print("B joins")
3242 let (peerID, _, _, error) = containerB.joinSync(test: self,
3243 voucherData: voucherData!,
3244 voucherSig: voucherSig!,
3245 ckksKeys: [],
3246 tlkShares: [])
3247 XCTAssertNil(error)
3248 XCTAssertEqual(peerID, bPeerID!)
3249 }
3250
3251 _ = containerA.dumpSync(test: self)
3252 _ = containerB.dumpSync(test: self)
3253 _ = containerC.dumpSync(test: self)
3254
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")
3258 do {
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")
3264 }
3265 XCTAssertNil(error4)
3266 XCTAssertNotNil(cPeerID)
3267 XCTAssertNotNil(cPermanentInfo)
3268 XCTAssertNotNil(cPermanentInfoSig)
3269
3270 do {
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
3274
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"))
3277
3278 print("B vouches for C")
3279 let (voucherData, voucherSig, error) =
3280 containerB.vouchSync(test: self,
3281 peerID: cPeerID!,
3282 permanentInfo: cPermanentInfo!,
3283 permanentInfoSig: cPermanentInfoSig!,
3284 stableInfo: cStableInfo!,
3285 stableInfoSig: cStableInfoSig!,
3286 ckksKeys: [self.manateeKeySet])
3287 XCTAssertNil(error)
3288 XCTAssertNotNil(voucherData)
3289 XCTAssertNotNil(voucherSig)
3290
3291 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3292
3293 print("C joins")
3294 let (peerID, _, _, error2) = containerC.joinSync(test: self,
3295 voucherData: voucherData!,
3296 voucherSig: voucherSig!,
3297 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
3298 tlkShares: [])
3299 XCTAssertNil(error2)
3300 XCTAssertEqual(peerID, cPeerID!)
3301
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"))
3305 }
3306
3307 print("A updates")
3308 do {
3309 let (_, _, error) = containerA.updateSync(test: self)
3310 XCTAssertNil(error)
3311 }
3312
3313 do {
3314 let state = containerA.getStateSync(test: self)
3315 let a = state.peers[aPeerID!]!
3316 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
3317 }
3318
3319 _ = containerA.dumpSync(test: self)
3320 _ = containerB.dumpSync(test: self)
3321 _ = containerC.dumpSync(test: self)
3322
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")
3326 }
3327
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)
3332
3333 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3334 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3335
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")
3339 XCTAssertNil(error)
3340 XCTAssertNotNil(aPeerID)
3341 XCTAssertNotNil(aPermanentInfo)
3342 XCTAssertNotNil(aPermanentInfoSig)
3343
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")
3346
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")
3350 do {
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")
3356 }
3357 XCTAssertNil(error2)
3358 XCTAssertNotNil(bPeerID)
3359 XCTAssertNotNil(bPermanentInfo)
3360 XCTAssertNotNil(bPermanentInfoSig)
3361
3362 // Now, A establishes preapproving B
3363 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
3364
3365 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
3366 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
3367
3368 print("establishing A")
3369 do {
3370 let (peerID, _, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
3371 XCTAssertNil(error)
3372 XCTAssertNotNil(peerID)
3373 }
3374
3375 do {
3376 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3377
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")
3383
3384 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
3385 }
3386
3387 _ = containerA.dumpSync(test: self)
3388 _ = containerB.dumpSync(test: self)
3389
3390 XCTAssertEqual(containerA.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3391 XCTAssertEqual(containerB.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3392 }
3393
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)
3398
3399 let (peerID, permanentInfo, permanentInfoSig, _, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
3400 do {
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")
3406 }
3407 XCTAssertNotNil(peerID)
3408 XCTAssertNotNil(permanentInfo)
3409 XCTAssertNotNil(permanentInfoSig)
3410 XCTAssertNil(error)
3411
3412 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3413
3414 _ = container.dumpSync(test: self)
3415
3416 do {
3417 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
3418 XCTAssertEqual(container.containerMO.honorIDMSListChanges, "UNKNOWN", "honorIDMSListChanges should be unknown")
3419 } catch {
3420 XCTFail("Creating container errored: \(error)")
3421 }
3422
3423 let (peerID2, _, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
3424 XCTAssertNil(error2)
3425 XCTAssertNotNil(peerID2)
3426
3427 _ = container.dumpSync(test: self)
3428 }
3429 }