]> git.saurik.com Git - apple/security.git/blob - keychain/TrustedPeersHelperUnitTests/TrustedPeersHelperUnitTests.swift
Security-59306.11.20.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 CoreData
9 import XCTest
10
11 let testDSID = "123456789"
12
13 let signingKey_384 = Data(base64Encoded: "BOQbPoiBnzuA0Cgc2QegjKGJqDtpkRenHwAxkYKJH1xELdaoIh8ifSch8sl18tpBYVUpEfdxz2ZSKif+dx7UPfu8WeTtpHkqm3M+9PTjr/KNNJCSR1PQNB5Jh+sRiQ+cpJnoTzm+IZSIukylamAcL3eA0nMUM0Zc2u4TijrbTgVND22WzSirUkwSK3mA/prk9A==")
14
15 let encryptionKey_384 = Data(base64Encoded: "BE1RuazBWmSEx0XVGhobbrdSE6fRQOrUrYEQnBkGl4zJq9GCeRoYvbuWNYFcOH0ijCRz9pYILsTn3ajT1OknlvcKmuQ7SeoGWzk9cBZzT5bBEwozn2gZxn80DQoDkmejywlH3D0/cuV6Bxexu5KMAFGqg6eN6th4sQABL5EuI9zKPuxHStM/b9B1LyqcnRKQHA==")
16
17 let symmetricKey_384 = Data(base64Encoded: "MfHje3Y/mWV0q+grjwZ4VxuqB7OreYHLxYkeeCiNjjY=")
18
19 class TrustedPeersHelperUnitTests: XCTestCase {
20
21 var tmpPath: String!
22 var tmpURL: URL!
23 var cuttlefish: FakeCuttlefishServer!
24
25 var manateeKeySet: CKKSKeychainBackedKeySet!
26
27 override static func setUp() {
28 super.setUp()
29
30 SecTapToRadar.disableTTRsEntirely()
31
32 // Turn on NO_SERVER stuff
33 securityd_init_local_spi()
34
35 SecCKKSDisable()
36 }
37
38 override func setUp() {
39 super.setUp()
40
41 let testName = self.name.components(separatedBy: CharacterSet(charactersIn: " ]"))[1]
42 cuttlefish = FakeCuttlefishServer(nil, ckZones: [:], ckksZoneKeys: [:])
43
44 // Make a new fake keychain
45 tmpPath = String(format: "/tmp/%@-%X", testName, arc4random())
46 tmpURL = URL(fileURLWithPath: tmpPath, isDirectory: true)
47 do {
48 try FileManager.default.createDirectory(atPath: String(format: "%@/Library/Keychains", tmpPath), withIntermediateDirectories: true, attributes: nil)
49 SetCustomHomeURLString(tmpPath as CFString)
50 SecKeychainDbReset(nil)
51 } catch {
52 XCTFail("setUp failed: \(error)")
53 }
54
55 // Actually load the database.
56 kc_with_dbt(true, nil) { _ in
57 false
58 }
59
60 // Now that the keychain is alive, perform test setup
61 do {
62 self.manateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
63 } catch {
64 XCTFail("Creation of fake key hierarchies failed: \(error)")
65 }
66 }
67
68 override func tearDown() {
69 // Put teardown code here. This method is called after the invocation of each test method in the class.
70 cuttlefish = nil
71 super.tearDown()
72 }
73
74 func makeFakeKeyHierarchy(zoneID: CKRecordZone.ID) throws -> CKKSKeychainBackedKeySet {
75 // Remember, these keys come into TPH having round-tripped through an NSEncoding
76 let tlk = try CKKSKeychainBackedKey.randomKeyWrapped(bySelf: zoneID)
77 let classA = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassA)
78 let classC = try CKKSKeychainBackedKey.randomKeyWrapped(byParent: tlk, keyclass: SecCKKSKeyClassC)
79
80 XCTAssertNoThrow(try tlk.saveMaterialToKeychain(), "Should be able to save TLK to keychain")
81 XCTAssertNoThrow(try classA.saveMaterialToKeychain(), "Should be able to save classA key to keychain")
82 XCTAssertNoThrow(try classC.saveMaterialToKeychain(), "Should be able to save classC key to keychain")
83
84 let tlkData = try NSKeyedArchiver.archivedData(withRootObject: tlk, requiringSecureCoding: true)
85 let classAData = try NSKeyedArchiver.archivedData(withRootObject: classA, requiringSecureCoding: true)
86 let classCData = try NSKeyedArchiver.archivedData(withRootObject: classC, requiringSecureCoding: true)
87
88 let decodedTLK = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: tlkData) as! CKKSKeychainBackedKey
89 let decodedClassA = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classAData) as! CKKSKeychainBackedKey
90 let decodedClassC = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [CKKSKeychainBackedKey.classForKeyedUnarchiver()], from: classCData) as! CKKSKeychainBackedKey
91
92 return CKKSKeychainBackedKeySet(tlk: decodedTLK, classA: decodedClassA, classC: decodedClassC, newUpload: false)
93 }
94
95 func assertTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
96 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
97 tlkShare.receiver == peerID &&
98 tlkShare.keyUuid == keyUUID
99 }
100
101 XCTAssertEqual(matches?.count ?? 0, 1, "Should have one tlk share matching \(peerID) and \(keyUUID)")
102 }
103
104 func assertNoTLKShareFor(peerID: String, keyUUID: String, zoneID: CKRecordZone.ID) {
105 let matches = self.cuttlefish.state.tlkShares[zoneID]?.filter { tlkShare in
106 tlkShare.receiver == peerID &&
107 tlkShare.keyUuid == keyUUID
108 }
109
110 XCTAssertEqual(matches?.count ?? 0, 0, "Should have no tlk share matching \(peerID) and \(keyUUID)")
111 }
112
113 func assertTrusts(context: Container, peerIDs: [String]) {
114 let state = context.getStateSync(test: self)
115 guard let egoPeerID = state.egoPeerID else {
116 XCTFail("context should have an ego peer ID")
117 return
118 }
119
120 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
121 XCTFail("No dynamicInfo for ego peer")
122 return
123 }
124
125 _ = peerIDs.map {
126 XCTAssertTrue(dynamicInfo.includedPeerIDs.contains($0), "Peer should trust \($0)")
127 XCTAssertFalse(dynamicInfo.excludedPeerIDs.contains($0), "Peer should not distrust \($0)")
128 }
129 }
130
131 func assertDistrusts(context: Container, peerIDs: [String]) {
132 let state = context.getStateSync(test: self)
133 guard let egoPeerID = state.egoPeerID else {
134 XCTFail("context should have an ego peer ID")
135 return
136 }
137
138 guard let dynamicInfo = state.peers[egoPeerID]?.dynamicInfo else {
139 XCTFail("No dynamicInfo for ego peer")
140 return
141 }
142
143 _ = peerIDs.map {
144 XCTAssertFalse(dynamicInfo.includedPeerIDs.contains($0), "Peer should not trust \($0)")
145 XCTAssertTrue(dynamicInfo.excludedPeerIDs.contains($0), "Peer should distrust \($0)")
146 }
147 }
148
149 func tmpStoreDescription(name: String) -> NSPersistentStoreDescription {
150 let tmpStoreURL = URL(fileURLWithPath: name, relativeTo: tmpURL)
151 return NSPersistentStoreDescription(url: tmpStoreURL)
152 }
153
154 func establish(reload: Bool,
155 store: NSPersistentStoreDescription) throws -> (Container, String) {
156 return try self.establish(reload: reload, contextID: OTDefaultContext, store: store)
157 }
158
159 func establish(reload: Bool,
160 contextID: String,
161 store: NSPersistentStoreDescription) throws -> (Container, String) {
162 var container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
163
164 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
165
166 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
167 do {
168 let state = container.getStateSync(test: self)
169 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
170 let secret = container.loadSecretSync(test: self, label: peerID!)
171 XCTAssertNotNil(secret, "secret should not be nil")
172 XCTAssertNil(error, "error should be nil")
173 }
174 XCTAssertNotNil(peerID)
175 XCTAssertNotNil(permanentInfo)
176 XCTAssertNotNil(permanentInfoSig)
177 XCTAssertNil(error)
178
179 _ = container.dumpSync(test: self)
180
181 if (reload) {
182 do {
183 container = try Container(name: ContainerName(container: "test", context: contextID), persistentStoreDescription: store, cuttlefish: cuttlefish)
184 } catch {
185 XCTFail()
186 }
187 }
188
189 let (peerID2, _, error2) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
190 XCTAssertNil(error2)
191 XCTAssertNotNil(peerID2)
192
193 _ = container.dumpSync(test: self)
194
195 return (container, peerID!)
196 }
197
198 func testEstablishWithReload() throws {
199 let description = tmpStoreDescription(name: "container.db")
200 let (_, peerID) = try establish(reload: true, store: description)
201
202 assertTLKShareFor(peerID: peerID, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
203 }
204
205 func testEstablishNoReload() throws {
206 let description = tmpStoreDescription(name: "container.db")
207 _ = try establish(reload: false, store: description)
208 }
209
210 func testEstablishNotOnAllowListErrors() throws {
211 let description = tmpStoreDescription(name: "container.db")
212 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
213
214 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
215 do {
216 let state = container.getStateSync(test: self)
217 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
218 let secret = container.loadSecretSync(test: self, label: peerID!)
219 XCTAssertNotNil(secret, "secret should not be nil")
220 XCTAssertNil(error, "error should be nil")
221 }
222 XCTAssertNotNil(peerID)
223 XCTAssertNotNil(permanentInfo)
224 XCTAssertNotNil(permanentInfoSig)
225 XCTAssertNil(error)
226
227 // Note that an empty machine ID list means "all are allowed", so an establish now will succeed
228
229 // Now set up a machine ID list that positively does not have our peer
230 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
231
232 let (peerID3, _, error3) = container.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
233 XCTAssertNotNil(peerID3, "Should get a peer when you establish a now allow-listed peer")
234 XCTAssertNil(error3, "Should not get an error when you establish a now allow-listed peer")
235 }
236
237 func joinByVoucher(sponsor: Container,
238 containerID: String,
239 machineID: String,
240 machineIDs: Set<String>,
241 store: NSPersistentStoreDescription) throws -> (Container, String) {
242 let c = try Container(name: ContainerName(container: containerID, context: OTDefaultContext),
243 persistentStoreDescription: store,
244 cuttlefish: cuttlefish)
245
246 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs), "Should be able to set machine IDs")
247
248 print("preparing \(containerID)")
249 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) =
250 c.prepareSync(test: self, epoch: 1, machineID: machineID, bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
251 XCTAssertNil(error)
252 XCTAssertNotNil(peerID)
253 XCTAssertNotNil(permanentInfo)
254 XCTAssertNotNil(permanentInfoSig)
255 XCTAssertNotNil(stableInfo)
256 XCTAssertNotNil(stableInfoSig)
257
258 do {
259 assertNoTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
260
261 print("\(sponsor) vouches for \(containerID)")
262 let (voucherData, voucherSig, vouchError) =
263 sponsor.vouchSync(test: self,
264 peerID: peerID!,
265 permanentInfo: permanentInfo!,
266 permanentInfoSig: permanentInfoSig!,
267 stableInfo: stableInfo!,
268 stableInfoSig: stableInfoSig!,
269 ckksKeys: [self.manateeKeySet])
270 XCTAssertNil(vouchError)
271 XCTAssertNotNil(voucherData)
272 XCTAssertNotNil(voucherSig)
273
274 // As part of the join, the sponsor should have uploaded a tlk share
275 assertTLKShareFor(peerID: peerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
276
277 print("\(containerID) joins")
278 let (joinedPeerID, _, joinError) = c.joinSync(test: self,
279 voucherData: voucherData!,
280 voucherSig: voucherSig!,
281 ckksKeys: [],
282 tlkShares: [])
283 XCTAssertNil(joinError)
284 XCTAssertEqual(joinedPeerID, peerID!)
285 }
286
287 return (c, peerID!)
288 }
289
290 func testJoin() throws {
291 let description = tmpStoreDescription(name: "container.db")
292 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
293 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
294 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
295
296 let machineIDs = Set(["aaa", "bbb", "ccc"])
297 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
298 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
299 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
300
301 print("preparing A")
302 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
303 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
304 do {
305 let state = containerA.getStateSync(test: self)
306 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
307 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
308 XCTAssertNotNil(secret, "secret should not be nil")
309 XCTAssertNil(error, "error should be nil")
310 }
311 XCTAssertNil(error)
312 XCTAssertNotNil(aPeerID)
313 XCTAssertNotNil(aPermanentInfo)
314 XCTAssertNotNil(aPermanentInfoSig)
315
316 print("establishing A")
317 do {
318 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
319 XCTAssertNil(error)
320 XCTAssertNotNil(peerID)
321 }
322
323 print("preparing B")
324 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
325 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
326 do {
327 let state = containerB.getStateSync(test: self)
328 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
329 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
330 XCTAssertNotNil(secret, "secret should not be nil")
331 XCTAssertNil(error, "error should be nil")
332 }
333 XCTAssertNil(error2)
334 XCTAssertNotNil(bPeerID)
335 XCTAssertNotNil(bPermanentInfo)
336 XCTAssertNotNil(bPermanentInfoSig)
337
338 do {
339 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
340 print("A vouches for B, but doesn't provide any TLKShares")
341 let (_, _, errorVouchingWithoutTLKs) =
342 containerA.vouchSync(test: self,
343 peerID: bPeerID!,
344 permanentInfo: bPermanentInfo!,
345 permanentInfoSig: bPermanentInfoSig!,
346 stableInfo: bStableInfo!,
347 stableInfoSig: bStableInfoSig!,
348 ckksKeys: [])
349 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
350 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
351
352 print("A vouches for B, but doesn't only has provisional TLKs at the time")
353 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
354 provisionalManateeKeySet.newUpload = true
355
356 let (_, _, errorVouchingWithProvisionalTLKs) =
357 containerA.vouchSync(test: self,
358 peerID: bPeerID!,
359 permanentInfo: bPermanentInfo!,
360 permanentInfoSig: bPermanentInfoSig!,
361 stableInfo: bStableInfo!,
362 stableInfoSig: bStableInfoSig!,
363 ckksKeys: [provisionalManateeKeySet])
364 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
365 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
366
367 print("A vouches for B")
368 let (voucherData, voucherSig, error3) =
369 containerA.vouchSync(test: self,
370 peerID: bPeerID!,
371 permanentInfo: bPermanentInfo!,
372 permanentInfoSig: bPermanentInfoSig!,
373 stableInfo: bStableInfo!,
374 stableInfoSig: bStableInfoSig!,
375 ckksKeys: [self.manateeKeySet])
376 XCTAssertNil(error3)
377 XCTAssertNotNil(voucherData)
378 XCTAssertNotNil(voucherSig)
379
380 // As part of the vouch, A should have uploaded a tlkshare for B
381 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
382
383 print("B joins")
384 let (peerID, _, error) = containerB.joinSync(test: self,
385 voucherData: voucherData!,
386 voucherSig: voucherSig!,
387 ckksKeys: [],
388 tlkShares: [])
389 XCTAssertNil(error)
390 XCTAssertEqual(peerID, bPeerID!)
391 }
392
393 _ = containerA.dumpSync(test: self)
394 _ = containerB.dumpSync(test: self)
395 _ = containerC.dumpSync(test: self)
396
397 print("preparing C")
398 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, error4) =
399 containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
400 do {
401 let state = containerC.getStateSync(test: self)
402 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
403 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
404 XCTAssertNotNil(secret, "secret should not be nil")
405 XCTAssertNil(error, "error should be nil")
406 }
407 XCTAssertNil(error4)
408 XCTAssertNotNil(cPeerID)
409 XCTAssertNotNil(cPermanentInfo)
410 XCTAssertNotNil(cPermanentInfoSig)
411
412 do {
413 // C, when it joins, will create a new CKKS zone. It should also upload TLKShares for A and B.
414 let provisionalEngramKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Engram"))
415 provisionalEngramKeySet.newUpload = true
416
417 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
418 assertNoTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
419
420 print("B vouches for C")
421 let (voucherData, voucherSig, error) =
422 containerB.vouchSync(test: self,
423 peerID: cPeerID!,
424 permanentInfo: cPermanentInfo!,
425 permanentInfoSig: cPermanentInfoSig!,
426 stableInfo: cStableInfo!,
427 stableInfoSig: cStableInfoSig!,
428 ckksKeys: [self.manateeKeySet])
429 XCTAssertNil(error)
430 XCTAssertNotNil(voucherData)
431 XCTAssertNotNil(voucherSig)
432
433 assertTLKShareFor(peerID: cPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
434
435 print("C joins")
436 let (peerID, _, error2) = containerC.joinSync(test: self,
437 voucherData: voucherData!,
438 voucherSig: voucherSig!,
439 ckksKeys: [self.manateeKeySet, provisionalEngramKeySet],
440 tlkShares: [])
441 XCTAssertNil(error2)
442 XCTAssertEqual(peerID, cPeerID!)
443
444 assertTLKShareFor(peerID: cPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
445 assertTLKShareFor(peerID: aPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
446 assertTLKShareFor(peerID: bPeerID!, keyUUID: provisionalEngramKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Engram"))
447 }
448
449 print("A updates")
450 do {
451 let (_, error) = containerA.updateSync(test: self)
452 XCTAssertNil(error)
453 }
454
455 do {
456 let state = containerA.getStateSync(test: self)
457 let a = state.peers[aPeerID!]!
458 XCTAssertTrue(a.dynamicInfo!.includedPeerIDs.contains(cPeerID!))
459 }
460
461 _ = containerA.dumpSync(test: self)
462 _ = containerB.dumpSync(test: self)
463 _ = containerC.dumpSync(test: self)
464 }
465
466 func testJoinWithoutAllowListErrors() throws {
467 let description = tmpStoreDescription(name: "container.db")
468 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
469 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
470
471 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
472 do {
473 let state = containerA.getStateSync(test: self)
474 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
475 let secret = containerA.loadSecretSync(test: self, label: peerID!)
476 XCTAssertNotNil(secret, "secret should not be nil")
477 XCTAssertNil(error, "error should be nil")
478 }
479 XCTAssertNil(error, "Should not have an error after preparing A")
480 XCTAssertNotNil(peerID, "Should have a peer ID after preparing A")
481 XCTAssertNotNil(permanentInfo, "Should have a permanent info after preparing A")
482 XCTAssertNotNil(permanentInfoSig, "Should have a signature after preparing A")
483
484 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs")
485
486 let (peerID2, _, error2) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
487 XCTAssertNotNil(peerID2, "Should get a peer when you establish a now allow-listed peer")
488 XCTAssertNil(error2, "Should not get an error when you establish a now allow-listed peer")
489
490 print("preparing B")
491 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, errorPrepareB) =
492 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
493 do {
494 let state = containerA.getStateSync(test: self)
495 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
496 let secret = containerA.loadSecretSync(test: self, label: peerID!)
497 XCTAssertNotNil(secret, "secret should not be nil")
498 XCTAssertNil(error, "error should be nil")
499 }
500 XCTAssertNil(errorPrepareB, "Should not have an error after preparing B")
501 XCTAssertNotNil(bPeerID, "Should have a peer ID after preparing B")
502 XCTAssertNotNil(bPermanentInfo, "Should have a permanent info after preparing B")
503 XCTAssertNotNil(bPermanentInfoSig, "Should have a signature after preparing B")
504
505 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa"]), "should be able to set allowed machine IDs on container B")
506
507 do {
508 print("A vouches for B")
509 let (voucherData, voucherSig, error3) =
510 containerA.vouchSync(test: self,
511 peerID: bPeerID!,
512 permanentInfo: bPermanentInfo!,
513 permanentInfoSig: bPermanentInfoSig!,
514 stableInfo: bStableInfo!,
515 stableInfoSig: bStableInfoSig!,
516 ckksKeys: [])
517 XCTAssertNil(error3, "Should be no error vouching for B")
518 XCTAssertNotNil(voucherData, "Should have a voucher from A")
519 XCTAssertNotNil(voucherSig, "Should have a signature from A")
520
521 print("B joins")
522 let (peerID, _, error) = containerB.joinSync(test: self,
523 voucherData: voucherData!,
524 voucherSig: voucherSig!,
525 ckksKeys: [],
526 tlkShares: [])
527 XCTAssertNotNil(error, "Should have an error joining with an unapproved machine ID")
528 XCTAssertNil(peerID, "Should not receive a peer ID joining with an unapproved machine ID")
529 }
530 }
531
532 func testReset() throws {
533 let description = tmpStoreDescription(name: "container.db")
534 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
535
536 let machineIDs = Set(["aaa"])
537 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
538
539 print("preparing A")
540 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
541 do {
542 let state = containerA.getStateSync(test: self)
543 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
544 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
545 XCTAssertNotNil(secret, "secret should not be nil")
546 XCTAssertNil(error, "error should be nil")
547 }
548 XCTAssertNil(error)
549 XCTAssertNotNil(aPeerID)
550 XCTAssertNotNil(aPermanentInfo)
551 XCTAssertNotNil(aPermanentInfoSig)
552
553 print("establishing A")
554 do {
555 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
556 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
557 XCTAssertNil(error)
558 XCTAssertNotNil(peerID)
559 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
560 }
561
562 print("reset A")
563 do {
564 let error = containerA.resetSync(test: self)
565 XCTAssertNil(error)
566 }
567 do {
568 let (dict, error) = containerA.dumpSync(test: self)
569 XCTAssertNil(error)
570 XCTAssertNotNil(dict)
571 let peers: Array<Any> = dict!["peers"] as! Array<Any>
572 XCTAssertEqual(0, peers.count)
573 }
574 }
575
576 func testResetLocal() throws {
577 let description = tmpStoreDescription(name: "container.db")
578 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
579
580 let machineIDs = Set(["aaa"])
581 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
582
583 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
584 do {
585 let state = containerA.getStateSync(test: self)
586 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
587 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
588 XCTAssertNotNil(secret, "secret should not be nil")
589 XCTAssertNil(error, "error should be nil")
590 }
591 XCTAssertNil(error, "Should be no error preparing an identity")
592 XCTAssertNotNil(aPeerID, "Should have a peer ID after preparing")
593 XCTAssertNotNil(aPermanentInfo, "Should have a permanentInfo after preparing")
594 XCTAssertNotNil(aPermanentInfoSig, "Should have a permanentInfoSign after preparing")
595
596 do {
597 let (dict, error) = containerA.dumpSync(test: self)
598 XCTAssertNil(error, "Should be no error dumping")
599 XCTAssertNotNil(dict, "Should receive a dump dictionary")
600
601 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
602 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
603
604 let selfPeer: String? = selfInfo!["peerID"] as! String?
605 XCTAssertNotNil(selfPeer, "self peer should be part of the dump")
606 }
607
608 do {
609 let error = containerA.localResetSync(test: self)
610 XCTAssertNil(error, "local-reset shouldn't error")
611 }
612 do {
613 let (dict, error) = containerA.dumpSync(test: self)
614
615 XCTAssertNil(error, "Should be no error dumping")
616 XCTAssertNotNil(dict, "Should receive a dump dictionary")
617
618 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
619 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
620
621 let selfPeer: String? = selfInfo!["peerID"] as! String?
622 XCTAssertNil(selfPeer, "self peer should not be part of the dump")
623 }
624 }
625
626 func testReplayAttack() throws {
627 let description = tmpStoreDescription(name: "container.db")
628 var containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
629 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
630 let containerC = try Container(name: ContainerName(container: "c", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
631
632 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
633 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
634 XCTAssertNil(containerC.setAllowedMachineIDsSync(test: self, allowedMachineIDs: Set(["aaa", "bbb", "ccc"])))
635
636 print("preparing")
637 let (peerID, _, _, _, _, _) = containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
638 do {
639 let state = containerA.getStateSync(test: self)
640 XCTAssertFalse( state.bottles.filter { $0.peerID == peerID } .isEmpty, "should have a bottle for peer")
641 let secret = containerA.loadSecretSync(test: self, label: peerID!)
642 XCTAssertNotNil(secret, "secret should not be nil")
643 }
644 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, _) = containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
645 do {
646 let state = containerB.getStateSync(test: self)
647 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
648 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
649 XCTAssertNotNil(secret, "secret should not be nil")
650 }
651 let (cPeerID, cPermanentInfo, cPermanentInfoSig, cStableInfo, cStableInfoSig, _) = containerC.prepareSync(test: self, epoch: 1, machineID: "ccc", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
652 do {
653 let state = containerC.getStateSync(test: self)
654 XCTAssertFalse( state.bottles.filter { $0.peerID == cPeerID } .isEmpty, "should have a bottle for peer")
655 let secret = containerC.loadSecretSync(test: self, label: cPeerID!)
656 XCTAssertNotNil(secret, "secret should not be nil")
657 }
658 print("establishing A")
659 _ = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
660
661 do {
662 print("A vouches for B")
663 let (voucherData, voucherSig, _) = containerA.vouchSync(test: self,
664 peerID: bPeerID!,
665 permanentInfo: bPermanentInfo!,
666 permanentInfoSig: bPermanentInfoSig!,
667 stableInfo: bStableInfo!,
668 stableInfoSig: bStableInfoSig!,
669 ckksKeys: [])
670
671 print("B joins")
672 _ = containerB.joinSync(test: self,
673 voucherData: voucherData!,
674 voucherSig: voucherSig!,
675 ckksKeys: [],
676 tlkShares: [])
677 }
678
679 print("A updates")
680 _ = containerA.updateSync(test: self)
681 let earlyClock: TPCounter
682 do {
683 let state = containerA.getStateSync(test: self)
684 let b = state.peers[bPeerID!]!
685 earlyClock = b.dynamicInfo!.clock
686 }
687
688 // Take a snapshot
689 let snapshot = cuttlefish.state
690
691 do {
692 print("B vouches for C")
693 let (voucherData, voucherSig, _) = containerB.vouchSync(test: self, peerID: cPeerID!,
694 permanentInfo: cPermanentInfo!,
695 permanentInfoSig: cPermanentInfoSig!,
696 stableInfo: cStableInfo!,
697 stableInfoSig: cStableInfoSig!,
698 ckksKeys: [])
699
700 print("C joins")
701 _ = containerC.joinSync(test: self,
702 voucherData: voucherData!,
703 voucherSig: voucherSig!,
704 ckksKeys: [],
705 tlkShares: [])
706 }
707
708 print("B updates")
709 _ = containerB.updateSync(test: self)
710
711 print("A updates")
712 _ = containerA.updateSync(test: self)
713 let lateClock: TPCounter
714 do {
715 let state = containerA.getStateSync(test: self)
716 let b = state.peers[bPeerID!]!
717 lateClock = b.dynamicInfo!.clock
718 XCTAssertTrue(earlyClock < lateClock)
719 }
720
721 print("Reverting cuttlefish to the snapshot")
722 cuttlefish.state = snapshot
723 cuttlefish.makeSnapshot()
724
725 print("A updates, fetching the old snapshot from cuttlefish")
726 _ = containerA.updateSync(test: self)
727
728 print("Reload A. Now we see whether it persisted the replayed snapshot in the previous step.")
729 containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
730 do {
731 let state = containerA.getStateSync(test: self)
732 let b = state.peers[bPeerID!]!
733 XCTAssertEqual(lateClock, b.dynamicInfo!.clock)
734 }
735 }
736
737 // TODO: need a real configurable mock cuttlefish
738 func testFetchPolicyDocuments() throws {
739
740 // 1 is known locally via builtin, 3 is not but is known to cuttlefish
741 let policies =
742 [
743 1: ("SHA256:TLXrcQmY4ue3oP5pCX1pwsi9BF8cKfohlJBilCroeBs=",
744 "CAESDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYS" +
745 "DgoFV2F0Y2gSBXdhdGNoGhEKCVBDU0VzY3JvdxIEZnVsbBoXCgRXaUZpEgRmdWxsEgJ0dhIFd2F0Y2gaGQoRU2FmYXJpQ3JlZGl0" +
746 "Q2FyZHMSBGZ1bGwiDAoEZnVsbBIEZnVsbCIUCgV3YXRjaBIEZnVsbBIFd2F0Y2giDgoCdHYSBGZ1bGwSAnR2"),
747 3: ("SHA256:JZzazSuHXrUhiOfSgElsg6vYKpnvvEPVpciR8FewRWg=",
748 "CAMSDgoGaVBob25lEgRmdWxsEgwKBGlQYWQSBGZ1bGwSCwoDTWFjEgRmdWxsEgwKBGlNYWMSBGZ1bGwSDQoHQXBwbGVUVhICdHYSDgoFV2F0Y2gSBXdhdGNoEhcKDkF1ZGlvQWNjZXNzb3J5EgVhdWRpbxocCg1EZXZpY2VQYWlyaW5nEgRmdWxsEgV3YXRjaBoXCghBcHBsZVBheRIEZnVsbBIFd2F0Y2gaJAoVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlEgRmdWxsEgV3YXRjaBoXCghCYWNrc3RvcBIEZnVsbBIFd2F0Y2gaGQoKQXV0b1VubG9jaxIEZnVsbBIFd2F0Y2gaHwoQU2VjdXJlT2JqZWN0U3luYxIEZnVsbBIFd2F0Y2gaIAoRU2FmYXJpQ3JlZGl0Q2FyZHMSBGZ1bGwSBXdhdGNoGhMKBEhvbWUSBGZ1bGwSBXdhdGNoGh4KD1NhZmFyaVBhc3N3b3JkcxIEZnVsbBIFd2F0Y2gaGwoMQXBwbGljYXRpb25zEgRmdWxsEgV3YXRjaBoVCgZFbmdyYW0SBGZ1bGwSBXdhdGNoGi0KE0xpbWl0ZWRQZWVyc0FsbG93ZWQSBGZ1bGwSBXdhdGNoEgJ0dhIFYXVkaW8aFgoHTWFuYXRlZRIEZnVsbBIFd2F0Y2gaHgoEV2lGaRIEZnVsbBIFd2F0Y2gSAnR2EgVhdWRpbxoVCgZIZWFsdGgSBGZ1bGwSBXdhdGNoIhMKBGZ1bGwSBGZ1bGwSBXdhdGNoIhsKBWF1ZGlvEgRmdWxsEgV3YXRjaBIFYXVkaW8iFAoFd2F0Y2gSBGZ1bGwSBXdhdGNoIhUKAnR2EgRmdWxsEgV3YXRjaBICdHYyIgoWAAQiEgIEdndodAoKXkFwcGxlUGF5JBIIQXBwbGVQYXkyJgoYAAQiFAIEdndodAoMXkF1dG9VbmxvY2skEgpBdXRvVW5sb2NrMh4KFAAEIhACBHZ3aHQKCF5FbmdyYW0kEgZFbmdyYW0yHgoUAAQiEAIEdndodAoIXkhlYWx0aCQSBkhlYWx0aDIaChIABCIOAgR2d2h0CgZeSG9tZSQSBEhvbWUyIAoVAAQiEQIEdndodAoJXk1hbmF0ZWUkEgdNYW5hdGVlMjgKIQAEIh0CBHZ3aHQKFV5MaW1pdGVkUGVlcnNBbGxvd2VkJBITTGltaXRlZFBlZXJzQWxsb3dlZDJdClAAAhIeAAQiGgIEdndodAoSXkNvbnRpbnVpdHlVbmxvY2skEhUABCIRAgR2d2h0CgleSG9tZUtpdCQSFQAEIhECBHZ3aHQKCV5BcHBsZVRWJBIJTm90U3luY2VkMisKGwAEIhcCBGFncnAKD15bMC05QS1aXXsxMH1cLhIMQXBwbGljYXRpb25zMsUBCrABAAISNAABChMABCIPAgVjbGFzcwoGXmdlbnAkChsABCIXAgRhZ3JwCg9eY29tLmFwcGxlLnNiZCQSPQABChMABCIPAgVjbGFzcwoGXmtleXMkCiQABCIgAgRhZ3JwChheY29tLmFwcGxlLnNlY3VyaXR5LnNvcyQSGQAEIhUCBHZ3aHQKDV5CYWNrdXBCYWdWMCQSHAAEIhgCBHZ3aHQKEF5pQ2xvdWRJZGVudGl0eSQSEFNlY3VyZU9iamVjdFN5bmMyYwpbAAISEgAEIg4CBHZ3aHQKBl5XaUZpJBJDAAEKEwAEIg8CBWNsYXNzCgZeZ2VucCQKEwAEIg8CBGFncnAKB15hcHBsZSQKFQAEIhECBHN2Y2UKCV5BaXJQb3J0JBIEV2lGaTLbAgrBAgACEhkABCIVAgR2d2h0Cg1eUENTQ2xvdWRLaXQkEhcABCITAgR2d2h0CgteUENTRXNjcm93JBIUAAQiEAIEdndodAoIXlBDU0ZERSQSGQAEIhUCBHZ3aHQKDV5QQ1NGZWxkc3BhciQSGQAEIhUCBHZ3aHQKDV5QQ1NNYWlsRHJvcCQSGgAEIhYCBHZ3aHQKDl5QQ1NNYXN0ZXJLZXkkEhYABCISAgR2d2h0CgpeUENTTm90ZXMkEhcABCITAgR2d2h0CgteUENTUGhvdG9zJBIYAAQiFAIEdndodAoMXlBDU1NoYXJpbmckEh0ABCIZAgR2d2h0ChFeUENTaUNsb3VkQmFja3VwJBIcAAQiGAIEdndodAoQXlBDU2lDbG91ZERyaXZlJBIZAAQiFQIEdndodAoNXlBDU2lNZXNzYWdlJBIVUHJvdGVjdGVkQ2xvdWRTdG9yYWdlMkAKKwAEIicCBGFncnAKH15jb20uYXBwbGUuc2FmYXJpLmNyZWRpdC1jYXJkcyQSEVNhZmFyaUNyZWRpdENhcmRzMjQKIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIPU2FmYXJpUGFzc3dvcmRzMm0KXAACEh4ABCIaAgR2d2h0ChJeQWNjZXNzb3J5UGFpcmluZyQSGgAEIhYCBHZ3aHQKDl5OYW5vUmVnaXN0cnkkEhwABCIYAgR2d2h0ChBeV2F0Y2hNaWdyYXRpb24kEg1EZXZpY2VQYWlyaW5nMi0KIQAEIh0CBGFncnAKFV5jb20uYXBwbGUuY2ZuZXR3b3JrJBIIQmFja3N0b3A="),
749 ]
750 let (request1, data1) = policies[1]!
751 let (request3, data3) = policies[3]!
752
753 let description = tmpStoreDescription(name: "container.db")
754 let container = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
755
756 // nothing
757 let (response1, error1) = container.fetchPolicyDocumentsSync(test: self, keys: [:])
758 XCTAssertNil(error1, "No error querying for an empty list")
759 XCTAssertEqual(response1, [:], "Received empty dictionary")
760
761 // local stuff
762 let (response2, error2) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1])
763 XCTAssertNil(error2, "No error getting locally known policy document")
764 XCTAssertEqual(response2?.count, 1, "Got one response for request for one locally known policy")
765 XCTAssertEqual(response2?[1]?[0], request1, "retrieved hash matches request hash")
766 XCTAssertEqual(response2?[1]?[1], data1, "retrieved data matches known data")
767
768 // fetch remote
769 let (response3, error3) = container.fetchPolicyDocumentsSync(test: self, keys: [1: request1, 3: request3])
770 XCTAssertNil(error3, "No error fetching local + remote policy")
771 XCTAssertEqual(response3?.count, 2, "Got two responses for local+remote policy request")
772 XCTAssertEqual(response3?[1]?[0], request1, "retrieved hash matches local request hash")
773 XCTAssertEqual(response3?[1]?[1], data1, "retrieved data matches local known data")
774 XCTAssertEqual(response3?[3]?[0], request3, "retrieved hash matches remote request hash")
775 XCTAssertEqual(response3?[3]?[1], data3, "retrieved data matches remote known data")
776
777 // invalid version
778 let (response4, error4) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash"])
779 XCTAssertNil(response4, "No response for wrong [version: hash] combination")
780 XCTAssertNotNil(error4, "Expected error fetching invalid policy version")
781
782 // valid + invalid
783 let (response5, error5) = container.fetchPolicyDocumentsSync(test: self, keys: [9000: "not a hash",
784 1: request1,
785 3: request3, ])
786 XCTAssertNil(response5, "No response for valid + unknown [version: hash] combination")
787 XCTAssertNotNil(error5, "Expected error fetching valid + invalid policy version")
788 }
789
790 func testEscrowKeys() throws {
791
792 XCTAssertThrowsError(try EscrowKeys.retrieveEscrowKeysFromKeychain(label: "hash"), "retrieveEscrowKeysFromKeychain should throw error")
793 XCTAssertThrowsError(try EscrowKeys.findEscrowKeysForLabel(label: "hash"), "findEscrowKeysForLabel should throw error")
794
795 let secretString = "i'm a secret!"
796 XCTAssertNotNil(secretString, "secretString should not be nil")
797
798 let secretData: Data? = secretString.data(using: .utf8)
799 XCTAssertNotNil(secretData, "secretData should not be nil")
800
801 let keys = try EscrowKeys(secret: secretData!, bottleSalt: "123456789")
802 XCTAssertNotNil(keys, "keys should not be nil")
803
804 XCTAssertNotNil(keys.secret, "secret should not be nil")
805 XCTAssertNotNil(keys.bottleSalt, "bottleSalt should not be nil")
806 XCTAssertNotNil(keys.encryptionKey, "encryptionKey should not be nil")
807 XCTAssertNotNil(keys.signingKey, "signingKey should not be nil")
808 XCTAssertNotNil(keys.symmetricKey, "symmetricKey should not be nil")
809
810 let hash = try EscrowKeys.hashEscrowedSigningPublicKey(keyData: keys.signingKey.publicKey().spki())
811 XCTAssertNotNil(hash, "hash should not be nil")
812
813 let result = try EscrowKeys.storeEscrowedSigningKeyPair(keyData: keys.signingKey.keyData, label: "Signing Key")
814 XCTAssertTrue(result, "result should be true")
815
816 let escrowKey = try EscrowKeys.retrieveEscrowKeysFromKeychain(label: hash)
817 XCTAssertNotNil(escrowKey, "escrowKey should not be nil")
818
819 let (signingKey, encryptionKey, symmetricKey) = try EscrowKeys.findEscrowKeysForLabel(label: hash)
820 XCTAssertNotNil(signingKey, "signingKey should not be nil")
821 XCTAssertNotNil(encryptionKey, "encryptionKey should not be nil")
822 XCTAssertNotNil(symmetricKey, "symmetricKey should not be nil")
823 }
824
825 func testEscrowKeyTestVectors() {
826
827 let secretString = "I'm a secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
828
829 let secret = secretString.data(using: .utf8)
830
831 do {
832 let testv1 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: secret!, bottleSalt: testDSID)
833 XCTAssertEqual(testv1, signingKey_384, "signing keys should match")
834
835 let testv2 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: secret!, bottleSalt: testDSID)
836 XCTAssertEqual(testv2, encryptionKey_384, "encryption keys should match")
837
838 let testv3 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: secret!, bottleSalt: testDSID)
839 XCTAssertEqual(testv3, symmetricKey_384, "symmetric keys should match")
840
841 let newSecretString = "I'm f secretI'm a secretI'm a secretI'm a secretI'm a secretI'm a secret"
842 let newSecret = newSecretString.data(using: .utf8)
843
844 let testv4 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySigning, masterSecret: newSecret!, bottleSalt: testDSID)
845 XCTAssertNotEqual(testv4, signingKey_384, "signing keys should not match")
846
847 let testv5 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeyEncryption, masterSecret: newSecret!, bottleSalt: testDSID)
848 XCTAssertNotEqual(testv5, encryptionKey_384, "encryption keys should not match")
849
850 let testv6 = try EscrowKeys.generateEscrowKey(keyType: escrowKeyType.kOTEscrowKeySymmetric, masterSecret: newSecret!, bottleSalt: testDSID)
851 XCTAssertNotEqual(testv6, symmetricKey_384, "symmetric keys should not match")
852 } catch {
853 XCTFail("error testing escrow key test vectors \(error)")
854 }
855 }
856
857 func testJoiningWithBottle() throws {
858 var bottleA: BottleMO
859 var entropy: Data
860 let description = tmpStoreDescription(name: "container.db")
861 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
862 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
863
864 let machineIDs = Set(["aaa", "bbb"])
865 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
866 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
867
868 print("preparing A")
869 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
870 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
871 do {
872 var state = containerA.getStateSync(test: self)
873 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
874
875 bottleA = state.bottles.removeFirst()
876
877 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
878 XCTAssertNotNil(secret, "secret should not be nil")
879 XCTAssertNil(error, "error should be nil")
880 }
881 XCTAssertNotNil(aPeerID)
882 XCTAssertNotNil(aPermanentInfo)
883 XCTAssertNotNil(aPermanentInfoSig)
884
885 print("establishing A")
886 do {
887 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
888 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
889 XCTAssertNil(error)
890 XCTAssertNotNil(peerID)
891 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
892 }
893 do {
894 let state = containerA.getStateSync(test: self)
895 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
896 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
897 entropy = secret!
898 XCTAssertNotNil(secret, "secret should not be nil")
899 XCTAssertNil(error, "error should be nil")
900 }
901
902 _ = containerB.updateSync(test: self)
903
904 print("preparing B")
905 let (bPeerID, _, _, _, _, error2) =
906 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
907 do {
908 let state = containerB.getStateSync(test: self)
909 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
910 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
911 XCTAssertNotNil(secret, "secret should not be nil")
912 XCTAssertNil(error, "error should be nil")
913 }
914 XCTAssertNil(error2)
915
916 do {
917 print("B prepares to join via bottle")
918
919 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
920
921 XCTAssertNil(error3)
922 XCTAssertNotNil(voucherData)
923 XCTAssertNotNil(voucherSig)
924
925 // Before B joins, there should be no TLKShares for B
926 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
927
928 print("B joins")
929 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
930 XCTAssertNil(error)
931 XCTAssertEqual(peerID, bPeerID!)
932
933 // But afterward, it has one!
934 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
935 }
936 }
937
938 func testJoiningWithBottleAndEmptyBottleSalt() throws {
939 var bottleA: BottleMO
940 var entropy: Data
941 let description = tmpStoreDescription(name: "container.db")
942 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
943 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
944
945 let machineIDs = Set(["aaa", "bbb"])
946 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
947 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
948
949 print("preparing A")
950 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
951 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "", bottleID: UUID().uuidString, modelID: "iPhone1,1")
952 do {
953 var state = containerA.getStateSync(test: self)
954 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
955
956 bottleA = state.bottles.removeFirst()
957
958 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
959 XCTAssertNotNil(secret, "secret should not be nil")
960 XCTAssertNil(error, "error should be nil")
961 }
962 XCTAssertNotNil(aPeerID)
963 XCTAssertNotNil(aPermanentInfo)
964 XCTAssertNotNil(aPermanentInfoSig)
965
966 print("establishing A")
967 do {
968 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
969 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
970 XCTAssertNil(error)
971 XCTAssertNotNil(peerID)
972 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
973 }
974 do {
975 let state = containerA.getStateSync(test: self)
976 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
977 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
978 entropy = secret!
979 XCTAssertNotNil(secret, "secret should not be nil")
980 XCTAssertNil(error, "error should be nil")
981 }
982
983 _ = containerB.updateSync(test: self)
984
985 print("preparing B")
986 let (bPeerID, _, _, _, _, error2) =
987 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
988 do {
989 let state = containerB.getStateSync(test: self)
990 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
991 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
992 XCTAssertNotNil(secret, "secret should not be nil")
993 XCTAssertNil(error, "error should be nil")
994 }
995 XCTAssertNil(error2)
996
997 do {
998 print("B prepares to join via bottle")
999
1000 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1001
1002 XCTAssertNil(error3)
1003 XCTAssertNotNil(voucherData)
1004 XCTAssertNotNil(voucherSig)
1005
1006 // Before B joins, there should be no TLKShares for B
1007 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1008
1009 print("B joins")
1010 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1011 XCTAssertNil(error)
1012 XCTAssertEqual(peerID, bPeerID!)
1013
1014 // But afterward, it has one!
1015 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1016 }
1017 }
1018
1019 func testJoiningWithWrongEscrowRecordForBottle() throws {
1020 var entropy: Data
1021 let description = tmpStoreDescription(name: "container.db")
1022 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1023 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1024
1025 let machineIDs = Set(["aaa", "bbb"])
1026 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1027 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1028
1029 print("preparing A")
1030 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1031 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1032 do {
1033 let state = containerA.getStateSync(test: self)
1034 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1035 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1036 XCTAssertNotNil(secret, "secret should not be nil")
1037 XCTAssertNil(error, "error should be nil")
1038 }
1039 XCTAssertNil(error)
1040 XCTAssertNotNil(aPeerID)
1041 XCTAssertNotNil(aPermanentInfo)
1042 XCTAssertNotNil(aPermanentInfoSig)
1043
1044 print("establishing A")
1045 do {
1046 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1047 XCTAssertNil(error)
1048 XCTAssertNotNil(peerID)
1049 }
1050 do {
1051 let state = containerA.getStateSync(test: self)
1052 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1053 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1054 entropy = secret!
1055 XCTAssertNotNil(secret, "secret should not be nil")
1056 XCTAssertNil(error, "error should be nil")
1057 }
1058
1059 _ = containerB.updateSync(test: self)
1060
1061 print("preparing B")
1062 let (bPeerID, _, _, _, _, error2) =
1063 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1064 do {
1065 let state = containerB.getStateSync(test: self)
1066 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1067 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1068 XCTAssertNotNil(secret, "secret should not be nil")
1069 XCTAssertNil(error, "error should be nil")
1070 }
1071 XCTAssertNil(error2)
1072
1073 do {
1074 print("B joins via bottle")
1075
1076 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: "wrong escrow record", entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1077
1078 XCTAssertNotNil(error3)
1079 XCTAssertNil(voucherData)
1080 XCTAssertNil(voucherSig)
1081 }
1082 }
1083
1084 func testJoiningWithWrongBottle() throws {
1085 var bottleB: BottleMO
1086 var entropy: Data
1087 let description = tmpStoreDescription(name: "container.db")
1088 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1089 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1090
1091 let machineIDs = Set(["aaa", "bbb"])
1092 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1093 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1094
1095 print("preparing A")
1096 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1097 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1098 do {
1099 let state = containerA.getStateSync(test: self)
1100 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1101 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1102 XCTAssertNotNil(secret, "secret should not be nil")
1103 XCTAssertNil(error, "error should be nil")
1104 }
1105 XCTAssertNil(error)
1106 XCTAssertNotNil(aPeerID)
1107 XCTAssertNotNil(aPermanentInfo)
1108 XCTAssertNotNil(aPermanentInfoSig)
1109
1110 print("establishing A")
1111 do {
1112 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1113 XCTAssertNil(error)
1114 XCTAssertNotNil(peerID)
1115 }
1116 do {
1117 let state = containerA.getStateSync(test: self)
1118 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1119 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1120 entropy = secret!
1121 XCTAssertNotNil(secret, "secret should not be nil")
1122 XCTAssertNil(error, "error should be nil")
1123 }
1124
1125 print("preparing B")
1126 let (bPeerID, _, _, _, _, error2) =
1127 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1128 do {
1129 var state = containerB.getStateSync(test: self)
1130 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1131 bottleB = state.bottles.removeFirst()
1132 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1133 XCTAssertNotNil(secret, "secret should not be nil")
1134 XCTAssertNil(error, "error should be nil")
1135 }
1136 XCTAssertNil(error2)
1137
1138 do {
1139 print("B joins via bottle")
1140
1141 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleB.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1142
1143 XCTAssertNotNil(error3)
1144 XCTAssertNil(voucherData)
1145 XCTAssertNil(voucherSig)
1146 }
1147 }
1148
1149 func testJoiningWithBottleAndBadSalt() throws {
1150 var bottleA: BottleMO
1151 var entropy: Data
1152 let description = tmpStoreDescription(name: "container.db")
1153 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1154 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1155
1156 let machineIDs = Set(["aaa", "bbb"])
1157 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1158 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1159
1160 print("preparing A")
1161 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1162 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1163 do {
1164 var state = containerA.getStateSync(test: self)
1165 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1166 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1167 bottleA = state.bottles.removeFirst()
1168 XCTAssertNotNil(secret, "secret should not be nil")
1169 XCTAssertNil(error, "error should be nil")
1170 }
1171 XCTAssertNil(error)
1172 XCTAssertNotNil(aPeerID)
1173 XCTAssertNotNil(aPermanentInfo)
1174 XCTAssertNotNil(aPermanentInfoSig)
1175
1176 print("establishing A")
1177 do {
1178 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1179 XCTAssertNil(error)
1180 XCTAssertNotNil(peerID)
1181 }
1182 do {
1183 let state = containerA.getStateSync(test: self)
1184 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1185 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1186 entropy = secret!
1187 XCTAssertNotNil(secret, "secret should not be nil")
1188 XCTAssertNil(error, "error should be nil")
1189 }
1190
1191 _ = containerB.updateSync(test: self)
1192
1193 print("preparing B")
1194 let (bPeerID, _, _, _, _, error2) =
1195 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1196 do {
1197 let state = containerB.getStateSync(test: self)
1198 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1199 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1200 XCTAssertNotNil(secret, "secret should not be nil")
1201 XCTAssertNil(error, "error should be nil")
1202 }
1203 XCTAssertNil(error2)
1204
1205 do {
1206 print("B joins via bottle")
1207
1208 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "987654321", tlkShares: [])
1209
1210 XCTAssertNotNil(error3)
1211 XCTAssertNil(voucherData)
1212 XCTAssertNil(voucherSig)
1213 }
1214 }
1215
1216 func testJoiningWithBottleAndBadSecret() throws {
1217 var bottleA: BottleMO
1218 let description = tmpStoreDescription(name: "container.db")
1219 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1220 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1221
1222 let machineIDs = Set(["aaa", "bbb"])
1223 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1224 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1225
1226 print("preparing A")
1227 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1228 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1229 do {
1230 var state = containerA.getStateSync(test: self)
1231 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1232 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1233 bottleA = state.bottles.removeFirst()
1234 XCTAssertNotNil(secret, "secret should not be nil")
1235 XCTAssertNil(error, "error should be nil")
1236 }
1237 XCTAssertNil(error)
1238 XCTAssertNotNil(aPeerID)
1239 XCTAssertNotNil(aPermanentInfo)
1240 XCTAssertNotNil(aPermanentInfoSig)
1241
1242 print("establishing A")
1243 do {
1244 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1245 XCTAssertNil(error)
1246 XCTAssertNotNil(peerID)
1247 }
1248 do {
1249 let state = containerA.getStateSync(test: self)
1250 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1251 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1252 XCTAssertNotNil(secret, "secret should not be nil")
1253 XCTAssertNil(error, "error should be nil")
1254 }
1255
1256 _ = containerB.updateSync(test: self)
1257
1258 print("preparing B")
1259 let (bPeerID, _, _, _, _, error2) =
1260 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1261 do {
1262 let state = containerB.getStateSync(test: self)
1263 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1264 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1265 XCTAssertNotNil(secret, "secret should not be nil")
1266 XCTAssertNil(error, "error should be nil")
1267 }
1268 XCTAssertNil(error2)
1269
1270 do {
1271 print("B joins via bottle")
1272
1273 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: Data(count: Int(OTMasterSecretLength)), bottleSalt: "123456789", tlkShares: [])
1274
1275 XCTAssertNotNil(error3)
1276 XCTAssertNil(voucherData)
1277 XCTAssertNil(voucherSig)
1278 }
1279 }
1280
1281 func testJoiningWithNoFetchAllBottles() throws {
1282 var bottleA: BottleMO
1283 var entropy: Data
1284 let description = tmpStoreDescription(name: "container.db")
1285 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1286 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1287
1288 let machineIDs = Set(["aaa", "bbb"])
1289 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1290 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1291
1292 print("preparing A")
1293 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1294 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1295 do {
1296 var state = containerA.getStateSync(test: self)
1297 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1298 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1299 entropy = secret!
1300 bottleA = state.bottles.removeFirst()
1301 XCTAssertNotNil(secret, "secret should not be nil")
1302 XCTAssertNil(error, "error should be nil")
1303 }
1304 XCTAssertNil(error)
1305 XCTAssertNotNil(aPeerID)
1306 XCTAssertNotNil(aPermanentInfo)
1307 XCTAssertNotNil(aPermanentInfoSig)
1308
1309 print("establishing A")
1310 do {
1311 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: [])
1312 XCTAssertNil(error)
1313 XCTAssertNotNil(peerID)
1314 }
1315 do {
1316 let state = containerA.getStateSync(test: self)
1317 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1318 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1319 XCTAssertNotNil(secret, "secret should not be nil")
1320 XCTAssertNil(error, "error should be nil")
1321 }
1322
1323 print("preparing B")
1324 let (bPeerID, _, _, _, _, error2) =
1325 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1326 do {
1327 let state = containerB.getStateSync(test: self)
1328 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1329 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1330 XCTAssertNotNil(secret, "secret should not be nil")
1331 XCTAssertNil(error, "error should be nil")
1332 }
1333 XCTAssertNil(error2)
1334
1335 do {
1336 print("B joins via bottle")
1337
1338 // And the first container fetches again, which should succeed
1339 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1340 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1341 self.cuttlefish.fetchViableBottlesError.append(ckError)
1342
1343 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1344
1345 XCTAssertNotNil(error3)
1346 XCTAssertNil(voucherData)
1347 XCTAssertNil(voucherSig)
1348 }
1349 }
1350
1351 func testJoinByPreapproval() throws {
1352 let description = tmpStoreDescription(name: "container.db")
1353 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1354 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1355
1356 let machineIDs = Set(["aaa", "bbb"])
1357 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1358 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1359
1360 print("preparing A")
1361 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1362 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1363 XCTAssertNil(error)
1364 XCTAssertNotNil(aPeerID)
1365 XCTAssertNotNil(aPermanentInfo)
1366 XCTAssertNotNil(aPermanentInfoSig)
1367
1368 print("preparing B")
1369 let (bPeerID, bPermanentInfo, bPermanentInfoSig, _, _, error2) =
1370 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1371 do {
1372 let state = containerB.getStateSync(test: self)
1373 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1374 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1375 XCTAssertNotNil(secret, "secret should not be nil")
1376 XCTAssertNil(error, "error should be nil")
1377 }
1378 XCTAssertNil(error2)
1379 XCTAssertNotNil(bPeerID)
1380 XCTAssertNotNil(bPermanentInfo)
1381 XCTAssertNotNil(bPermanentInfoSig)
1382
1383 // Now, A establishes preapproving B
1384 // Note: secd is responsible for passing in TLKShares to these preapproved keys in sosTLKShares
1385
1386 let bPermanentInfoParsed = TPPeerPermanentInfo(peerID: bPeerID!, data: bPermanentInfo!, sig: bPermanentInfoSig!, keyFactory: TPECPublicKeyFactory())
1387 XCTAssertNotNil(bPermanentInfoParsed, "Should have parsed B's permanent info")
1388
1389 print("establishing A")
1390 do {
1391 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [bPermanentInfoParsed!.signingPubKey.spki()])
1392 XCTAssertNil(error)
1393 XCTAssertNotNil(peerID)
1394 }
1395
1396 do {
1397 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1398
1399 print("B joins by preapproval, and uploads all TLKShares that it has")
1400 let (bJoinedPeerID, _, bJoinedError) = containerB.preapprovedJoinSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [])
1401 XCTAssertNil(bJoinedError, "Should be no error joining by preapproval")
1402 XCTAssertNotNil(bJoinedPeerID, "Should have a peer ID out of join")
1403
1404 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1405 }
1406
1407 _ = containerA.dumpSync(test: self)
1408 _ = containerB.dumpSync(test: self)
1409 }
1410
1411 func testDepart() throws {
1412 let description = tmpStoreDescription(name: "container.db")
1413 let (container, peerID) = try establish(reload: false, store: description)
1414
1415 XCTAssertNil(container.departByDistrustingSelfSync(test: self), "Should be no error distrusting self")
1416 assertDistrusts(context: container, peerIDs: [peerID])
1417 }
1418
1419 func testDistrustPeers() throws {
1420 let store = tmpStoreDescription(name: "container.db")
1421 let (c, peerID1) = try establish(reload: false, store: store)
1422
1423 let (c2, peerID2) = try joinByVoucher(sponsor: c,
1424 containerID: "second",
1425 machineID: "bbb",
1426 machineIDs: ["aaa", "bbb", "ccc"],
1427 store: store)
1428
1429 let (c3, peerID3) = try joinByVoucher(sponsor: c,
1430 containerID: "third",
1431 machineID: "ccc",
1432 machineIDs: ["aaa", "bbb", "ccc"],
1433 store: store)
1434
1435 let (_, cUpdateError) = c.updateSync(test: self)
1436 XCTAssertNil(cUpdateError, "Should be able to update first container")
1437 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1438
1439 // You can't distrust yourself via peerID.
1440 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set([peerID1, peerID2, peerID3])), "Should error trying to distrust yourself via peer ID")
1441 assertTrusts(context: c, peerIDs: [peerID1, peerID2, peerID3])
1442
1443 // Passing in nonsense should error too
1444 XCTAssertNotNil(c.distrustSync(test: self, peerIDs: Set(["not a real peer ID"])), "Should error when passing in unknown peer IDs")
1445
1446 // Now, distrust both peers.
1447 XCTAssertNil(c.distrustSync(test: self, peerIDs: Set([peerID2, peerID3])), "Should be no error distrusting peers")
1448 assertDistrusts(context: c, peerIDs: [peerID2, peerID3])
1449
1450 // peers should accept their fates
1451 let (_, c2UpdateError) = c2.updateSync(test: self)
1452 XCTAssertNil(c2UpdateError, "Should be able to update second container")
1453 assertDistrusts(context: c2, peerIDs: [peerID2])
1454
1455 let (_, c3UpdateError) = c3.updateSync(test: self)
1456 XCTAssertNil(c3UpdateError, "Should be able to update third container")
1457 assertDistrusts(context: c3, peerIDs: [peerID3])
1458 }
1459
1460 func testFetchWithBadChangeToken() throws {
1461 let (c, peerID1) = try establish(reload: false, store: tmpStoreDescription(name: "container.db"))
1462
1463 // But all that goes away, and a new peer establishes
1464 self.cuttlefish.state = FakeCuttlefishServer.State()
1465 let (_, peerID2) = try establish(reload: false, contextID: "second", store: tmpStoreDescription(name: "container-peer2.db"))
1466
1467 // And the first container fetches again, which should succeed
1468 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1469 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1470 self.cuttlefish.nextFetchErrors.append(ckError)
1471 _ = c.updateSync(test: self)
1472
1473 // and c's model should only include peerID2
1474 c.moc.performAndWait {
1475 let modelPeers = c.model.allPeerIDs()
1476 XCTAssertEqual(modelPeers.count, 1, "Model should have one peer")
1477 XCTAssert(modelPeers.contains(peerID2), "Model should contain peer 2")
1478 XCTAssertFalse(modelPeers.contains(peerID1), "Model should no longer container peer 1 (ego peer)")
1479 }
1480 }
1481
1482 func testFetchEscrowContents() throws {
1483 let description = tmpStoreDescription(name: "container.db")
1484 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1485 let (entropyA, bottleIDA, spkiA, errorA) = containerA.fetchEscrowContentsSync(test: self)
1486 XCTAssertNotNil(errorA, "Should be an error fetching escrow contents")
1487 XCTAssertEqual(errorA.debugDescription, "Optional(TrustedPeersHelperUnitTests.ContainerError.noPreparedIdentity)", "error should be no prepared identity")
1488 XCTAssertNil(entropyA, "Should not have some entropy to bottle")
1489 XCTAssertNil(bottleIDA, "Should not have a bottleID")
1490 XCTAssertNil(spkiA, "Should not have an SPKI")
1491
1492 let (c, peerID) = try establish(reload: false, store: description)
1493 XCTAssertNotNil(peerID, "establish should return a peer id")
1494
1495 let (entropy, bottleID, spki, error) = c.fetchEscrowContentsSync(test: self)
1496 XCTAssertNil(error, "Should be no error fetching escrow contents")
1497 XCTAssertNotNil(entropy, "Should have some entropy to bottle")
1498 XCTAssertNotNil(bottleID, "Should have a bottleID")
1499 XCTAssertNotNil(spki, "Should have an SPKI")
1500 }
1501
1502 func testBottles() {
1503 do {
1504 let peerSigningKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1505 let peerEncryptionKey = _SFECKeyPair.init(randomKeyPairWith: _SFECKeySpecifier.init(curve: SFEllipticCurve.nistp384))!
1506 let bottle = try BottledPeer(peerID: "peerID", bottleID: UUID().uuidString, peerSigningKey: peerSigningKey, peerEncryptionKey: peerEncryptionKey, bottleSalt: "123456789")
1507
1508 let keys = bottle.escrowKeys
1509 XCTAssertNotNil(keys, "keys should not be nil")
1510
1511 XCTAssertNotNil(bottle, "bottle should not be nil")
1512
1513 XCTAssertNotNil(bottle.escrowSigningPublicKeyHash(), "escrowSigningPublicKeyHash should not be nil")
1514
1515 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey))
1516 XCTAssertThrowsError(try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey))
1517
1518 XCTAssertNotNil(BottledPeer.signingOperation(), "signing operation should not be nil")
1519
1520 let verifyBottleEscrowSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingEscrowKey, pubKey: keys.signingKey.publicKey() as! _SFECPublicKey)
1521 XCTAssertNotNil(verifyBottleEscrowSignature, "verifyBottleEscrowSignature should not be nil")
1522
1523 let verifyBottlePeerSignature = try BottledPeer.verifyBottleSignature(data: bottle.contents, signature: bottle.signatureUsingPeerKey, pubKey: peerSigningKey.publicKey() as! _SFECPublicKey)
1524 XCTAssertNotNil(verifyBottlePeerSignature, "verifyBottlePeerSignature should not be nil")
1525
1526 let deserializedBottle = try BottledPeer(contents: bottle.contents, secret: bottle.secret, bottleSalt: "123456789", signatureUsingEscrow: bottle.signatureUsingEscrowKey, signatureUsingPeerKey: bottle.signatureUsingPeerKey)
1527 XCTAssertNotNil(deserializedBottle, "deserializedBottle should not be nil")
1528
1529 XCTAssertEqual(deserializedBottle.contents, bottle.contents, "bottle data should be equal")
1530
1531 } catch {
1532 XCTFail("error testing bottles \(error)")
1533 }
1534 }
1535
1536 func testFetchBottles() throws {
1537 let store = tmpStoreDescription(name: "container.db")
1538 let (c, _) = try establish(reload: false, store: store)
1539
1540 let (bottles, _, fetchError) = c.fetchViableBottlesSync(test: self)
1541 XCTAssertNil(fetchError, "should be no error fetching viable bottles")
1542 XCTAssertNotNil(bottles, "should have fetched some bottles")
1543 XCTAssertEqual(bottles!.count, 1, "should have fetched one bottle")
1544
1545 do {
1546 let state = c.getStateSync(test: self)
1547 XCTAssertEqual(state.bottles.count, 1, "first container should have a bottle for peer")
1548 }
1549
1550 let c2 = try Container(name: ContainerName(container: "test", context: "newcomer"), persistentStoreDescription: store, cuttlefish: self.cuttlefish)
1551 do {
1552 let state = c2.getStateSync(test: self)
1553 XCTAssertEqual(state.bottles.count, 0, "before fetch, second container should not have any stored bottles")
1554 }
1555
1556 let (c2bottles, _, c2FetchError) = c2.fetchViableBottlesSync(test: self)
1557 XCTAssertNil(c2FetchError, "should be no error fetching viable bottles")
1558 XCTAssertNotNil(c2bottles, "should have fetched some bottles")
1559 XCTAssertEqual(c2bottles!.count, 1, "should have fetched one bottle")
1560
1561 do {
1562 let state = c2.getStateSync(test: self)
1563 XCTAssertEqual(state.bottles.count, 1, "after fetch, second container should have one stored bottles")
1564 }
1565 }
1566
1567 func testTrustStatus() throws {
1568 let store = tmpStoreDescription(name: "container.db")
1569
1570 let preC = try Container(name: ContainerName(container: "preC", context: "preCContext"),
1571 persistentStoreDescription: store,
1572 cuttlefish: self.cuttlefish)
1573 let (preEgoStatus, precStatusError) = preC.trustStatusSync(test: self)
1574 XCTAssertNil(precStatusError, "No error fetching status")
1575 XCTAssertEqual(preEgoStatus.egoStatus, .unknown, "Before establish, trust status should be 'unknown'")
1576 XCTAssertNil(preEgoStatus.egoPeerID, "should not have a peer ID")
1577 XCTAssertEqual(preEgoStatus.numberOfPeersInOctagon, 0, "should not see number of peers")
1578 XCTAssertFalse(preEgoStatus.isExcluded, "should be excluded")
1579
1580 let (c, _) = try establish(reload: false, store: store)
1581 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1582 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1583 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1584 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1585 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1586 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1587
1588 let c2 = try Container(name: ContainerName(container: "differentContainer", context: "a different context"),
1589 persistentStoreDescription: store,
1590 cuttlefish: self.cuttlefish)
1591
1592 let (egoStatus, statusError) = c2.trustStatusSync(test: self)
1593 XCTAssertNil(statusError, "No error fetching status")
1594 XCTAssertEqual(egoStatus.egoStatus, .excluded, "After establish, other container should be 'excluded'")
1595 XCTAssertNil(egoStatus.egoPeerID, "should not have a peerID")
1596 XCTAssertEqual(egoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1597 XCTAssertTrue(egoStatus.isExcluded, "should not be excluded")
1598 }
1599
1600 func testTrustStatusWhenMissingIdentityKeys() throws {
1601 let store = tmpStoreDescription(name: "container.db")
1602 let (c, _) = try establish(reload: false, store: store)
1603 let (cEgoStatus, cStatusError) = c.trustStatusSync(test: self)
1604 XCTAssertNil(cStatusError, "Should be no error fetching trust status directly after establish")
1605 XCTAssertEqual(cEgoStatus.egoStatus, [.fullyReciprocated, .selfTrust], "After establish, should be fully reciprocated")
1606 XCTAssertNotNil(cEgoStatus.egoPeerID, "should have a peer ID")
1607 XCTAssertEqual(cEgoStatus.numberOfPeersInOctagon, 1, "should be 1 peer")
1608 XCTAssertFalse(cEgoStatus.isExcluded, "should not be excluded")
1609
1610 let result = try removeEgoKeysSync(peerID: cEgoStatus.egoPeerID!)
1611 XCTAssertTrue(result, "result should be true")
1612
1613 let (distrustedStatus, distrustedError) = c.trustStatusSync(test: self)
1614 XCTAssertNotNil(distrustedError, "error should not be nil")
1615 XCTAssertEqual(distrustedStatus.egoStatus, [.excluded], "trust status should be excluded")
1616 XCTAssertTrue(distrustedStatus.isExcluded, "should be excluded")
1617 }
1618
1619 func testSetRecoveryKey() throws {
1620 let store = tmpStoreDescription(name: "container.db")
1621
1622 let c = try Container(name: ContainerName(container: "c", context: "context"),
1623 persistentStoreDescription: store,
1624 cuttlefish: self.cuttlefish)
1625
1626 let machineIDs = Set(["aaa", "bbb", "ccc"])
1627 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1628
1629 print("preparing peer A")
1630 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1631 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1632 do {
1633 let state = c.getStateSync(test: self)
1634 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1635 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1636 XCTAssertNotNil(secret, "secret should not be nil")
1637 XCTAssertNil(error, "error should be nil")
1638 }
1639 XCTAssertNil(error)
1640 XCTAssertNotNil(aPeerID)
1641 XCTAssertNotNil(aPermanentInfo)
1642 XCTAssertNotNil(aPermanentInfoSig)
1643
1644 print("establishing A")
1645 do {
1646 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1647 XCTAssertNil(error)
1648 XCTAssertNotNil(peerID)
1649 }
1650 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1651 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1652
1653 let (setRecoveryError) = c.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1654 XCTAssertNil(setRecoveryError, "error should be nil")
1655 }
1656
1657 func roundTripThroughSetValueTransformer(set: Set<String>) {
1658 let t = SetValueTransformer()
1659
1660 let transformedSet = t.transformedValue(set) as? Data
1661 XCTAssertNotNil(transformedSet, "SVT should return some data")
1662
1663 let recoveredSet = t.reverseTransformedValue(transformedSet) as? Set<String>
1664 XCTAssertNotNil(recoveredSet, "SVT should return some recovered set")
1665
1666 XCTAssertEqual(set, recoveredSet, "Recovered set should be the same as original")
1667 }
1668
1669 func testSetValueTransformer() {
1670 roundTripThroughSetValueTransformer(set: Set<String>([]))
1671 roundTripThroughSetValueTransformer(set: Set<String>(["asdf"]))
1672 roundTripThroughSetValueTransformer(set: Set<String>(["asdf", "three", "test"]))
1673 }
1674
1675 func testGetRepairSuggestion() throws {
1676 let store = tmpStoreDescription(name: "container.db")
1677
1678 let c = try Container(name: ContainerName(container: "c", context: "context"),
1679 persistentStoreDescription: store,
1680 cuttlefish: self.cuttlefish)
1681
1682 let machineIDs = Set(["aaa", "bbb", "ccc"])
1683 XCTAssertNil(c.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1684
1685 print("preparing peer A")
1686 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1687 c.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1688 do {
1689 let state = c.getStateSync(test: self)
1690 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1691 let secret = c.loadSecretSync(test: self, label: aPeerID!)
1692 XCTAssertNotNil(secret, "secret should not be nil")
1693 XCTAssertNil(error, "error should be nil")
1694 }
1695 XCTAssertNil(error)
1696 XCTAssertNotNil(aPeerID)
1697 XCTAssertNotNil(aPermanentInfo)
1698 XCTAssertNotNil(aPermanentInfoSig)
1699
1700 print("establishing A")
1701 do {
1702 let (peerID, _, error) = c.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1703 XCTAssertNil(error)
1704 XCTAssertNotNil(peerID)
1705 }
1706 let (repairAccount, repairEscrow, resetOctagon, healthError) = c.requestHealthCheckSync(requiresEscrowCheck: true, test: self)
1707 XCTAssertEqual(repairAccount, false, "")
1708 XCTAssertEqual(repairEscrow, false, "")
1709 XCTAssertEqual(resetOctagon, false, "")
1710 XCTAssertNil(healthError)
1711 }
1712
1713 func testFetchChangesFailDuringVouchWithBottle() throws {
1714 var bottleA: BottleMO
1715 var entropy: Data
1716 let description = tmpStoreDescription(name: "container.db")
1717 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1718 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1719
1720 let machineIDs = Set(["aaa", "bbb"])
1721 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1722 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1723
1724 print("preparing A")
1725 let (aPeerID, aPermanentInfo, aPermanentInfoSig, _, _, error) =
1726 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1727 do {
1728 var state = containerA.getStateSync(test: self)
1729 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1730
1731 bottleA = state.bottles.removeFirst()
1732
1733 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1734 XCTAssertNotNil(secret, "secret should not be nil")
1735 XCTAssertNil(error, "error should be nil")
1736 }
1737 XCTAssertNotNil(aPeerID)
1738 XCTAssertNotNil(aPermanentInfo)
1739 XCTAssertNotNil(aPermanentInfoSig)
1740
1741 print("establishing A")
1742 do {
1743 assertNoTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1744 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
1745 XCTAssertNil(error)
1746 XCTAssertNotNil(peerID)
1747 assertTLKShareFor(peerID: aPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1748 }
1749 do {
1750 let state = containerA.getStateSync(test: self)
1751 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1752 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1753 entropy = secret!
1754 XCTAssertNotNil(secret, "secret should not be nil")
1755 XCTAssertNil(error, "error should be nil")
1756 }
1757
1758 _ = containerB.updateSync(test: self)
1759
1760 print("preparing B")
1761 let (bPeerID, _, _, _, _, error2) =
1762 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1763 do {
1764 let state = containerB.getStateSync(test: self)
1765 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1766 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1767 XCTAssertNotNil(secret, "secret should not be nil")
1768 XCTAssertNil(error, "error should be nil")
1769 }
1770 XCTAssertNil(error2)
1771
1772 do {
1773 print("B prepares to join via bottle")
1774
1775 let (voucherData, voucherSig, error3) = containerB.vouchWithBottleSync(test: self, b: bottleA.bottleID!, entropy: entropy, bottleSalt: "123456789", tlkShares: [])
1776
1777 XCTAssertNil(error3)
1778 XCTAssertNotNil(voucherData)
1779 XCTAssertNotNil(voucherSig)
1780
1781 // And the first container fetches again, which should succeed
1782 let cuttlefishError = NSError(domain: CuttlefishErrorDomain, code: CuttlefishErrorCode.changeTokenExpired.rawValue, userInfo: nil)
1783 let ckError = NSError(domain: CKInternalErrorDomain, code: CKInternalErrorCode.errorInternalPluginError.rawValue, userInfo: [NSUnderlyingErrorKey: cuttlefishError])
1784 self.cuttlefish.nextFetchErrors.append(ckError)
1785
1786 // Before B joins, there should be no TLKShares for B
1787 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1788
1789 print("B joins")
1790 let (peerID, _, error) = containerB.joinSync(test: self, voucherData: voucherData!, voucherSig: voucherSig!, ckksKeys: [self.manateeKeySet], tlkShares: [])
1791 XCTAssertNotNil(error)
1792 XCTAssertNil(peerID)
1793 }
1794 }
1795
1796 func testDistrustedPeerRecoveryKeyNotSet() throws {
1797 let description = tmpStoreDescription(name: "container.db")
1798 let containerA = try Container(name: ContainerName(container: "a", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1799 let containerB = try Container(name: ContainerName(container: "b", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1800
1801
1802 let machineIDs = Set(["aaa", "bbb"])
1803 XCTAssertNil(containerA.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1804 XCTAssertNil(containerB.setAllowedMachineIDsSync(test: self, allowedMachineIDs: machineIDs))
1805
1806 print("preparing peer A")
1807 let (aPeerID, aPermanentInfo, aPermanentInfoSig, aStableInfo, aStableInfoSig, error) =
1808 containerA.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1809 do {
1810 let state = containerA.getStateSync(test: self)
1811 XCTAssertFalse( state.bottles.filter { $0.peerID == aPeerID } .isEmpty, "should have a bottle for peer")
1812 let secret = containerA.loadSecretSync(test: self, label: aPeerID!)
1813 XCTAssertNotNil(secret, "secret should not be nil")
1814 XCTAssertNil(error, "error should be nil")
1815 }
1816 XCTAssertNil(error)
1817 XCTAssertNotNil(aPeerID)
1818 XCTAssertNotNil(aPermanentInfo)
1819 XCTAssertNotNil(aPermanentInfoSig)
1820 XCTAssertNotNil(aStableInfo)
1821 XCTAssertNotNil(aStableInfoSig)
1822
1823 print("establishing A")
1824 do {
1825 let (peerID, _, error) = containerA.establishSync(test: self, ckksKeys: [], tlkShares: [], preapprovedKeys: nil)
1826 XCTAssertNil(error)
1827 XCTAssertNotNil(peerID)
1828 }
1829
1830 print("preparing B")
1831 let (bPeerID, bPermanentInfo, bPermanentInfoSig, bStableInfo, bStableInfoSig, error2) =
1832 containerB.prepareSync(test: self, epoch: 1, machineID: "bbb", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1833 do {
1834 let state = containerB.getStateSync(test: self)
1835 XCTAssertFalse( state.bottles.filter { $0.peerID == bPeerID } .isEmpty, "should have a bottle for peer")
1836 let secret = containerB.loadSecretSync(test: self, label: bPeerID!)
1837 XCTAssertNotNil(secret, "secret should not be nil")
1838 XCTAssertNil(error, "error should be nil")
1839 }
1840 XCTAssertNil(error2)
1841 XCTAssertNotNil(bPeerID)
1842 XCTAssertNotNil(bPermanentInfo)
1843 XCTAssertNotNil(bPermanentInfoSig)
1844
1845 do {
1846 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1847 print("A vouches for B, but doesn't provide any TLKShares")
1848 let (_, _, errorVouchingWithoutTLKs) =
1849 containerA.vouchSync(test: self,
1850 peerID: bPeerID!,
1851 permanentInfo: bPermanentInfo!,
1852 permanentInfoSig: bPermanentInfoSig!,
1853 stableInfo: bStableInfo!,
1854 stableInfoSig: bStableInfoSig!,
1855 ckksKeys: [])
1856 XCTAssertNil(errorVouchingWithoutTLKs, "Should be no error vouching without uploading TLKShares")
1857 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1858
1859 print("A vouches for B, but doesn't only has provisional TLKs at the time")
1860 let provisionalManateeKeySet = try self.makeFakeKeyHierarchy(zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1861 provisionalManateeKeySet.newUpload = true
1862
1863 let (_, _, errorVouchingWithProvisionalTLKs) =
1864 containerA.vouchSync(test: self,
1865 peerID: bPeerID!,
1866 permanentInfo: bPermanentInfo!,
1867 permanentInfoSig: bPermanentInfoSig!,
1868 stableInfo: bStableInfo!,
1869 stableInfoSig: bStableInfoSig!,
1870 ckksKeys: [provisionalManateeKeySet])
1871 XCTAssertNil(errorVouchingWithProvisionalTLKs, "Should be no error vouching without uploading TLKShares for a non-existent key")
1872 assertNoTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1873
1874 print("A vouches for B")
1875 let (voucherData, voucherSig, error3) =
1876 containerA.vouchSync(test: self,
1877 peerID: bPeerID!,
1878 permanentInfo: bPermanentInfo!,
1879 permanentInfoSig: bPermanentInfoSig!,
1880 stableInfo: bStableInfo!,
1881 stableInfoSig: bStableInfoSig!,
1882 ckksKeys: [self.manateeKeySet])
1883 XCTAssertNil(error3)
1884 XCTAssertNotNil(voucherData)
1885 XCTAssertNotNil(voucherSig)
1886
1887 // As part of the vouch, A should have uploaded a tlkshare for B
1888 assertTLKShareFor(peerID: bPeerID!, keyUUID: self.manateeKeySet.tlk.uuid, zoneID: CKRecordZone.ID(zoneName: "Manatee"))
1889
1890 print("B joins")
1891 let (peerID, _, error) = containerB.joinSync(test: self,
1892 voucherData: voucherData!,
1893 voucherSig: voucherSig!,
1894 ckksKeys: [],
1895 tlkShares: [])
1896 XCTAssertNil(error)
1897 XCTAssertEqual(peerID, bPeerID!)
1898 }
1899
1900 print("A updates")
1901 do {
1902 let (_, error) = containerA.updateSync(test: self)
1903 XCTAssertNil(error)
1904 }
1905 print("B updates")
1906 do {
1907 let (_, error) = containerB.updateSync(test: self)
1908 XCTAssertNil(error)
1909 }
1910
1911 // Now, A distrusts B.
1912 XCTAssertNil(containerA.distrustSync(test: self, peerIDs: Set([bPeerID!])), "Should be no error distrusting peers")
1913 assertDistrusts(context: containerA, peerIDs: [bPeerID!])
1914
1915
1916 let recoveryKey = SecRKCreateRecoveryKeyString(nil)
1917 XCTAssertNotNil(recoveryKey, "recoveryKey should not be nil")
1918
1919 let (setRecoveryError) = containerB.setRecoveryKeySync(test: self, recoveryKey: recoveryKey!, recoverySalt: "altDSID", ckksKeys: [])
1920 XCTAssertNil(setRecoveryError, "error should be nil")
1921
1922 print("A updates")
1923 do {
1924 let (_, error) = containerA.updateSync(test: self)
1925 XCTAssertNil(error)
1926 }
1927 print("B updates")
1928 do {
1929 let (_, error) = containerB.updateSync(test: self)
1930 XCTAssertNil(error)
1931 }
1932
1933 do {
1934 let (dict, error) = containerA.dumpSync(test: self)
1935
1936 XCTAssertNil(error, "Should be no error dumping")
1937 XCTAssertNotNil(dict, "Should receive a dump dictionary")
1938
1939 let selfInfo: [AnyHashable: Any]? = dict!["self"] as! [AnyHashable: Any]?
1940 XCTAssertNotNil(selfInfo, "Should have a self dictionary")
1941
1942 let stableInfo: [AnyHashable: Any]? = selfInfo!["stableInfo"] as! [AnyHashable: Any]?
1943 XCTAssertNotNil(stableInfo, "stableInfo should not be nil")
1944
1945 let recoverySigningPublicKey: Data? = stableInfo!["recovery_signing_public_key"] as! Data?
1946 XCTAssertNil(recoverySigningPublicKey, "recoverySigningPublicKey should be nil")
1947
1948 let recoveryEncryptionPublicKey: Data? = stableInfo!["recovery_encryption_public_key"] as! Data?
1949 XCTAssertNil(recoveryEncryptionPublicKey, "recoveryEncryptionPublicKey should be nil")
1950
1951 }
1952 }
1953
1954 func assert(container: Container,
1955 allowedMachineIDs: Set<String>,
1956 disallowedMachineIDs: Set<String>,
1957 unknownMachineIDs: Set<String> = Set(),
1958 persistentStore: NSPersistentStoreDescription,
1959 cuttlefish: FakeCuttlefishServer) throws {
1960
1961 let midList = container.onqueueCurrentMIDList()
1962 XCTAssertEqual(midList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs should match")
1963 XCTAssertEqual(midList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs should match")
1964 XCTAssertEqual(midList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs should match")
1965
1966 // if we reload the container, does it still match?
1967 let reloadedContainer = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: persistentStore, cuttlefish: cuttlefish)
1968
1969 let reloadedMidList = reloadedContainer.onqueueCurrentMIDList()
1970 XCTAssertEqual(reloadedMidList.machineIDs(in: .allowed), allowedMachineIDs, "List of allowed machine IDs on a reloaded container should match")
1971 XCTAssertEqual(reloadedMidList.machineIDs(in: .disallowed), disallowedMachineIDs, "List of disallowed machine IDs on a reloaded container should match")
1972 XCTAssertEqual(reloadedMidList.machineIDs(in: .unknown), unknownMachineIDs, "List of unknown machine IDs on a reloaded container should match")
1973 }
1974
1975 func testAllowListManipulation() throws {
1976 let description = tmpStoreDescription(name: "container.db")
1977 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
1978
1979 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
1980
1981 XCTAssertNil(error)
1982 XCTAssertNotNil(peerID)
1983 XCTAssertNotNil(permanentInfo)
1984 XCTAssertNotNil(permanentInfoSig)
1985
1986 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
1987
1988 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"]), "should be able to set allowed machine IDs")
1989 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
1990 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
1991
1992 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"]), "should be able to set allowed machine IDs")
1993 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
1994 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
1995
1996 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["zzz", "kkk"]), "should be able to add allowed machine IDs")
1997 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
1998 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
1999
2000 // Receivng a 'remove' push should send the MIDs to the 'unknown' list
2001 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["bbb", "fff"]), "should be able to remove allowed machine IDs")
2002 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["bbb", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2003 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2004
2005 // once they're unknown, a full list set will make them disallowed
2006 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"]), "should be able to set allowed machine IDs")
2007 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2008 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2009
2010 // Resetting the list to what it is doesn't change the list
2011 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "zzz", "kkk"], listDifference: false), "should be able to set allowed machine IDs")
2012 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "zzz", "kkk"]), disallowedMachineIDs: Set(["bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2013 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2014
2015 // But changing it to something completely new does
2016 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm"]), "should be able to set allowed machine IDs")
2017 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm"]), disallowedMachineIDs: Set(["aaa", "zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2018 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2019
2020 // And, readding a previously disallowed machine ID works too
2021 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["xxx", "mmm", "aaa"]), "should be able to set allowed machine IDs")
2022 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2023 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2024
2025 // A update() before establish() doesn't change the list, since it isn't actually changing anything
2026 let (_, updateError) = container.updateSync(test: self)
2027 XCTAssertNil(updateError, "Should not be an error updating the container without first establishing")
2028 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2029
2030 let (_, _, establishError) = container.establishSync(test: self, ckksKeys: [self.manateeKeySet], tlkShares: [], preapprovedKeys: [])
2031 XCTAssertNil(establishError, "Should be able to establish() with no error")
2032 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set(["zzz", "kkk", "bbb", "ccc", "fff"]), persistentStore: description, cuttlefish: self.cuttlefish)
2033 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2034
2035 // But a successful update() does remove all disallowed machine IDs, as they're no longer relevant
2036 let (_, updateError2) = container.updateSync(test: self)
2037 XCTAssertNil(updateError2, "Should not be an error updating the container after establishing")
2038 try self.assert(container: container, allowedMachineIDs: Set(["xxx", "mmm", "aaa"]), disallowedMachineIDs: Set([]), persistentStore: description, cuttlefish: self.cuttlefish)
2039 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2040 }
2041
2042 func testAllowListManipulationWithAddsAndRemoves() throws {
2043 let description = tmpStoreDescription(name: "container.db")
2044 let container = try Container(name: ContainerName(container: "test", context: OTDefaultContext), persistentStoreDescription: description, cuttlefish: cuttlefish)
2045
2046 try self.assert(container: container, allowedMachineIDs: [], disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2047
2048 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2049 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2050 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2051
2052 // Now, an 'add' comes in for some peers
2053 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["ddd", "eee"]), "should be able to receive an add push")
2054 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc", "ddd", "eee"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2055 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2056
2057 // But, the next time we ask IDMS, they still haven't made it to the full list, and in fact, C has disappeared.
2058 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2059 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd", "eee"]), disallowedMachineIDs: Set(["ccc"]), persistentStore: description, cuttlefish: self.cuttlefish)
2060 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2061
2062 // And a remove comes in for E. It becomes 'unknown'
2063 XCTAssertNil(container.removeAllowedMachineIDsSync(test: self, machineIDs: ["eee"]), "should be able to receive an add push")
2064 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc"]), unknownMachineIDs: Set(["eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2065 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2066
2067 // and a list set after the remove confirms the removal
2068 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2069 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2070 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2071
2072 // Then a new list set includes D! Hurray IDMS. Note that this is not a "list change", because the list doesn't actually change
2073 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"], listDifference: false), "should be able to set allowed machine IDs")
2074 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: Set(["ccc", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2075 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2076
2077 // And another list set no longer includes D, so it should now be disallowed
2078 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2079 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2080 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2081
2082 // And just to check the 48 hour boundary...
2083 XCTAssertNil(container.addAllowedMachineIDsSync(test: self, machineIDs: ["xxx"]), "should be able to receive an add push")
2084 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: false), "should be able to set allowed machine IDs")
2085 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "xxx"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee"]), persistentStore: description, cuttlefish: self.cuttlefish)
2086
2087 container.moc.performAndWait {
2088 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2089
2090 knownMachineMOs.forEach {
2091 if $0.machineID == "xxx" {
2092 $0.modified = Date(timeIntervalSinceNow: -60*60*72)
2093 }
2094 }
2095
2096 try! container.moc.save()
2097 }
2098
2099 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2100
2101 // Setting the list again should kick out X, since it was 'added' too long ago
2102 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2103 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: Set(["ccc", "ddd", "eee", "xxx"]), persistentStore: description, cuttlefish: self.cuttlefish)
2104 }
2105
2106 func testAllowSetUpgrade() throws {
2107 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2108
2109 let description = tmpStoreDescription(name: "container.db")
2110 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2111 let mom = getOrMakeModel(url: url)
2112 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2113 persistentContainer.persistentStoreDescriptions = [description]
2114
2115 persistentContainer.loadPersistentStores { _, error in
2116 XCTAssertNil(error, "Should be no error loading persistent stores")
2117 }
2118
2119 let moc = persistentContainer.newBackgroundContext()
2120 moc.performAndWait {
2121 let containerMO = ContainerMO(context: moc)
2122 containerMO.name = containerName.asSingleString()
2123 containerMO.allowedMachineIDs = Set(["aaa", "bbb", "ccc"]) as NSSet
2124
2125 XCTAssertNoThrow(try! moc.save())
2126 }
2127
2128 // Now TPH boots up with a preexisting model
2129 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2130
2131 let (peerID, permanentInfo, permanentInfoSig, _, _, error) = container.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2132
2133 XCTAssertNil(error)
2134 XCTAssertNotNil(peerID)
2135 XCTAssertNotNil(permanentInfo)
2136 XCTAssertNotNil(permanentInfoSig)
2137
2138 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2139
2140 // Setting a new list should work fine
2141 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ddd"]), "should be able to set allowed machine IDs")
2142 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ddd"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2143
2144 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2145 }
2146
2147 func testAllowStatusUpgrade() throws {
2148 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2149
2150 let description = tmpStoreDescription(name: "container.db")
2151 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2152 let mom = getOrMakeModel(url: url)
2153 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2154 persistentContainer.persistentStoreDescriptions = [description]
2155
2156 persistentContainer.loadPersistentStores { _, error in
2157 XCTAssertNil(error, "Should be no error loading persistent stores")
2158 }
2159
2160 let moc = persistentContainer.newBackgroundContext()
2161 moc.performAndWait {
2162 let containerMO = ContainerMO(context: moc)
2163 containerMO.name = containerName.asSingleString()
2164
2165 let machine = MachineMO(context: moc)
2166 machine.allowed = true
2167 machine.modified = Date()
2168 machine.machineID = "aaa"
2169 containerMO.addToMachines(machine)
2170
2171 let machineB = MachineMO(context: moc)
2172 machineB.allowed = false
2173 machineB.modified = Date()
2174 machineB.machineID = "bbb"
2175 containerMO.addToMachines(machineB)
2176
2177 try! moc.save()
2178 }
2179
2180 // Now TPH boots up with a preexisting model
2181 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2182 try self.assert(container: container, allowedMachineIDs: Set(["aaa"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2183
2184 // Setting a new list should work fine
2185 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "ddd"]), "should be able to set allowed machine IDs")
2186 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "ddd"]), disallowedMachineIDs: ["bbb"], persistentStore: description, cuttlefish: self.cuttlefish)
2187
2188 XCTAssertEqual(container.containerMO.allowedMachineIDs, Set<String>() as NSSet, "Set of allowed machine IDs should now be empty")
2189 }
2190
2191 func testMachineIDListSetWithUnknownMachineIDs() throws {
2192 let description = tmpStoreDescription(name: "container.db")
2193 let (container, _) = try establish(reload: false, store: description)
2194
2195 container.moc.performAndWait {
2196 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2197
2198 knownMachineMOs.forEach {
2199 container.containerMO.removeFromMachines($0)
2200 }
2201
2202 try! container.moc.save()
2203 }
2204
2205 // and set the machine ID list to something that doesn't include 'aaa'
2206 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2207 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2208 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2209
2210 // But check that it exists, and set its modification date to a while ago for an upcoming test
2211 container.moc.performAndWait {
2212 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2213
2214 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2215 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2216
2217 let aaaMO = aaaMOs.first!
2218 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2219 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2220
2221 aaaMO.modified = Date(timeIntervalSinceNow: -60)
2222 try! container.moc.save()
2223 }
2224
2225 // With it 'modified' only 60s ago, we shouldn't want a list fetch
2226 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2227
2228 // Setting it again is fine...
2229 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2230 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2231
2232 // And doesn't reset the modified date on the record
2233 container.moc.performAndWait {
2234 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2235
2236 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2237 XCTAssert(aaaMOs.count == 1, "Should have one machine MO for aaa")
2238
2239 let aaaMO = aaaMOs.first!
2240 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2241 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2242
2243 XCTAssertLessThan(aaaMO.modified!, Date(timeIntervalSinceNow: -5), "Modification date of record should still be its previously on-disk value")
2244 }
2245
2246 // And can be promoted to 'allowed'
2247 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb"], listDifference: true), "should be able to set allowed machine IDs")
2248 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb"]), disallowedMachineIDs: ["ccc"], persistentStore: description, cuttlefish: self.cuttlefish)
2249 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2250 }
2251
2252 func testMachineIDListSetDisallowedOldUnknownMachineIDs() throws {
2253 let description = tmpStoreDescription(name: "container.db")
2254 let (container, _) = try establish(reload: false, store: description)
2255
2256 container.moc.performAndWait {
2257 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2258
2259 knownMachineMOs.forEach {
2260 container.containerMO.removeFromMachines($0)
2261 }
2262
2263 try! container.moc.save()
2264 }
2265
2266 // and set the machine ID list to something that doesn't include 'aaa'
2267 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2268 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(["aaa"]), persistentStore: description, cuttlefish: self.cuttlefish)
2269 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2270
2271 // But an entry for "aaa" should exist, as a peer in the model claims it as their MID
2272 container.moc.performAndWait {
2273 let knownMachineMOs = container.containerMO.machines as? Set<MachineMO> ?? Set()
2274
2275 let aaaMOs = knownMachineMOs.filter { $0.machineID == "aaa" }
2276 XCTAssertEqual(aaaMOs.count, 1, "Should have one machine MO for aaa")
2277
2278 let aaaMO = aaaMOs.first!
2279 XCTAssertEqual(aaaMO.status, Int64(TPMachineIDStatus.unknown.rawValue), "Status of aaa MO should be 'unknown'")
2280 XCTAssertFalse(aaaMO.allowed, "allowed should no longer be a used field")
2281
2282 // Pretend that aaa was added 49 hours ago
2283 aaaMO.modified = Date(timeIntervalSinceNow: -60*60*49)
2284 try! container.moc.save()
2285 }
2286
2287 XCTAssertTrue(container.onqueueFullIDMSListWouldBeHelpful(), "Container should think it could use an IDMS list set: there's machine IDs pending removal")
2288
2289 // And, setting the list again should disallow aaa, since it is so old
2290 // Note that this _should_ return a list difference, since A is promoted to disallowed
2291 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2292 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2293 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2294
2295 // Setting ths list again has no change
2296 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["bbb", "ccc"], listDifference: false), "should be able to set allowed machine IDs")
2297 try self.assert(container: container, allowedMachineIDs: Set(["bbb", "ccc"]), disallowedMachineIDs: ["aaa"], persistentStore: description, cuttlefish: self.cuttlefish)
2298 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2299
2300 // But A can appear again, no problem.
2301 XCTAssertNil(container.setAllowedMachineIDsSync(test: self, allowedMachineIDs: ["aaa", "bbb", "ccc"], listDifference: true), "should be able to set allowed machine IDs")
2302 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], persistentStore: description, cuttlefish: self.cuttlefish)
2303 XCTAssertFalse(container.onqueueFullIDMSListWouldBeHelpful(), "Container shouldn't think it could use an IDMS list set")
2304 }
2305
2306 func testMachineIDListHandlingWithPeers() throws {
2307 let description = tmpStoreDescription(name: "container.db")
2308 let (container, peerID1) = try establish(reload: false, store: description)
2309
2310 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set(), persistentStore: description, cuttlefish: self.cuttlefish)
2311
2312 let unknownMachineID = "not-on-list"
2313 let (_, peerID2) = try self.joinByVoucher(sponsor: container,
2314 containerID: "second",
2315 machineID: unknownMachineID,
2316 machineIDs: Set([unknownMachineID, "aaa", "bbb", "ccc"]),
2317 store: description)
2318
2319 // And the first container accepts the join...
2320 let (_, cUpdateError) = container.updateSync(test: self)
2321 XCTAssertNil(cUpdateError, "Should be able to update first container")
2322 assertTrusts(context: container, peerIDs: [peerID1, peerID2])
2323
2324 try self.assert(container: container, allowedMachineIDs: Set(["aaa", "bbb", "ccc"]), disallowedMachineIDs: [], unknownMachineIDs: Set([unknownMachineID]), persistentStore: description, cuttlefish: self.cuttlefish)
2325 }
2326
2327 func testContainerAndModelConsistency() throws{
2328
2329 let preTestContainerName = ContainerName(container: "testToCreatePrepareData", context: "context")
2330 let description = tmpStoreDescription(name: "container.db")
2331 let containerTest = try Container(name: preTestContainerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2332 let (peerID, permanentInfo, permanentInfoSig, stableInfo, stableInfoSig, error) = containerTest.prepareSync(test: self, epoch: 1, machineID: "aaa", bottleSalt: "123456789", bottleID: UUID().uuidString, modelID: "iPhone1,1")
2333 XCTAssertNil(error)
2334 XCTAssertNotNil(peerID)
2335 XCTAssertNotNil(permanentInfo)
2336 XCTAssertNotNil(permanentInfoSig)
2337 XCTAssertNotNil(stableInfo)
2338 XCTAssertNotNil(stableInfoSig)
2339
2340 let containerName = ContainerName(container: "test", context: OTDefaultContext)
2341 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
2342 let mom = getOrMakeModel(url: url)
2343 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
2344 persistentContainer.persistentStoreDescriptions = [description]
2345
2346 persistentContainer.loadPersistentStores { _, error in
2347 XCTAssertNil(error, "Should be no error loading persistent stores")
2348 }
2349
2350 let moc = persistentContainer.newBackgroundContext()
2351 moc.performAndWait {
2352 let containerMO = ContainerMO(context: moc)
2353 containerMO.name = containerName.asSingleString()
2354 containerMO.allowedMachineIDs = Set(["aaa"]) as NSSet
2355 containerMO.egoPeerID = peerID
2356 containerMO.egoPeerPermanentInfo = permanentInfo
2357 containerMO.egoPeerPermanentInfoSig = permanentInfoSig
2358 containerMO.egoPeerStableInfoSig = stableInfoSig
2359 containerMO.egoPeerStableInfo = stableInfo
2360 let containerEgoStableInfo = TPPeerStableInfo(data: stableInfo!, sig: stableInfoSig!)
2361 do{
2362 let peerKeys: OctagonSelfPeerKeys = try loadEgoKeysSync(peerID: containerMO.egoPeerID!)
2363 let info3: TPPeerStableInfo = TPPeerStableInfo(clock: containerEgoStableInfo!.clock + 2,
2364 policyVersion:containerEgoStableInfo!.policyVersion,
2365 policyHash:containerEgoStableInfo!.policyHash,
2366 policySecrets:containerEgoStableInfo!.policySecrets,
2367 deviceName:containerEgoStableInfo!.deviceName,
2368 serialNumber:containerEgoStableInfo!.serialNumber,
2369 osVersion:containerEgoStableInfo!.osVersion,
2370 signing:peerKeys.signingKey,
2371 recoverySigningPubKey:containerEgoStableInfo!.recoverySigningPublicKey,
2372 recoveryEncryptionPubKey:containerEgoStableInfo!.recoveryEncryptionPublicKey,
2373 error:nil)
2374
2375 //setting the containerMO's ego stable info to an old clock
2376 containerMO.egoPeerStableInfo = containerEgoStableInfo!.data
2377 containerMO.egoPeerStableInfoSig = containerEgoStableInfo!.sig
2378
2379 //now we are adding the ego stable info with a clock of 3 to the list of peers
2380 let peer = PeerMO(context: moc)
2381 peer.peerID = peerID
2382 peer.permanentInfo = permanentInfo
2383 peer.permanentInfoSig = permanentInfoSig
2384 peer.stableInfo = info3.data
2385 peer.stableInfoSig = info3.sig
2386 peer.isEgoPeer = true
2387 peer.container = containerMO
2388
2389 containerMO.addToPeers(peer)
2390
2391 //at this point the containerMO's egoStableInfo should have a clock of 1
2392 //the saved ego peer in the peer list has a clock of 3
2393
2394 } catch {
2395 XCTFail("load ego keys failed: \(error)")
2396 }
2397 XCTAssertNoThrow(try! moc.save())
2398 }
2399
2400 // Now TPH boots up with a preexisting model
2401 let container = try Container(name: containerName, persistentStoreDescription: description, cuttlefish: cuttlefish)
2402
2403 let stableInfoAfterBoot = TPPeerStableInfo(data: container.containerMO.egoPeerStableInfo!, sig: container.containerMO.egoPeerStableInfoSig!)
2404 XCTAssertNotNil(stableInfoAfterBoot)
2405
2406 //after boot the clock should be updated to the one that was saved in the model
2407 XCTAssertEqual(stableInfoAfterBoot!.clock, 3, "clock should be updated to 3")
2408 }
2409 }