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