2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
29 import SecurityFoundation
31 let tplogDebug = OSLog(subsystem: "com.apple.security.trustedpeers", category: "debug")
32 let tplogTrace = OSLog(subsystem: "com.apple.security.trustedpeers", category: "trace")
34 let egoIdentitiesAccessGroup = "com.apple.security.egoIdentities"
36 public enum ContainerError: Error {
37 case unableToCreateKeyPair
38 case noPreparedIdentity
39 case failedToStoreIdentity
40 case needsAuthentication
41 case missingStableInfo
42 case missingDynamicInfo
44 case invalidPermanentInfoOrSig
45 case invalidStableInfoOrSig
46 case invalidVoucherOrSig
47 case sponsorNotRegistered(String)
48 case unknownPolicyVersion(UInt64)
49 case preparedIdentityNotOnAllowedList(String)
50 case couldNotLoadAllowedList
51 case noPeersPreapprovePreparedIdentity
52 case policyDocumentDoesNotValidate
53 case tooManyBottlesForPeer
55 case restoreBottleFailed
56 case noBottlesForEscrowRecordID
57 case bottleDoesNotContainContents
58 case bottleDoesNotContainEscrowKeySignature
59 case bottleDoesNotContainerPeerKeySignature
60 case bottleDoesNotContainPeerID
61 case failedToCreateBottledPeer
62 case signatureVerificationFailed
63 case bottleDoesNotContainerEscrowKeySPKI
64 case failedToFetchEscrowContents
65 case failedToCreateRecoveryKey
66 case untrustedRecoveryKeys
68 case recoveryKeysNotEnrolled
69 case bottleCreatingPeerNotFound
70 case unknownCloudKitError
71 case cloudkitResponseMissing
72 case failedToLoadSecret(errorCode: Int)
73 case failedToLoadSecretDueToType
74 case failedToAssembleBottle
76 case failedToStoreSecret(errorCode: Int)
79 extension ContainerError: LocalizedError {
80 public var errorDescription: String? {
82 case .unableToCreateKeyPair:
83 return "unable to create key pair"
84 case .noPreparedIdentity:
85 return "no prepared identity"
86 case .failedToStoreIdentity:
87 return "failed to stored identity"
88 case .needsAuthentication:
89 return "needs authentication"
90 case .missingStableInfo:
91 return "missing stable info"
92 case .missingDynamicInfo:
93 return "missing dynamic info"
96 case .invalidPermanentInfoOrSig:
97 return "invalid permanent info or signature"
98 case .invalidStableInfoOrSig:
99 return "invalid stable info or signature"
100 case .invalidVoucherOrSig:
101 return "invalid voucher or signature"
102 case .sponsorNotRegistered(let s):
103 return "sponsor not registered: \(s)"
104 case .unknownPolicyVersion(let v):
105 return "unknown policy version: \(v)"
106 case .preparedIdentityNotOnAllowedList(let id):
107 return "prepared identity (\(id)) not on allowed machineID list"
108 case .couldNotLoadAllowedList:
109 return "could not load allowed machineID list"
110 case .noPeersPreapprovePreparedIdentity:
111 return "no peers preapprove prepared identity"
112 case .policyDocumentDoesNotValidate:
113 return "policy document from server doesn't validate"
114 case .tooManyBottlesForPeer:
115 return "too many bottles exist for peer"
116 case .noBottleForPeer:
117 return "no bottle exists for peer"
118 case .restoreBottleFailed:
119 return "failed to restore bottle"
120 case .noBottlesForEscrowRecordID:
121 return "0 bottles exist for escrow record id"
122 case .bottleDoesNotContainContents:
123 return "bottle does not contain encrypted contents"
124 case .bottleDoesNotContainEscrowKeySignature:
125 return "bottle does not contain escrow signature"
126 case .bottleDoesNotContainerPeerKeySignature:
127 return "bottle does not contain peer signature"
128 case .bottleDoesNotContainPeerID:
129 return "bottle does not contain peer id"
130 case .failedToCreateBottledPeer:
131 return "failed to create a bottled peer"
132 case .signatureVerificationFailed:
133 return "failed to verify signature"
134 case .bottleDoesNotContainerEscrowKeySPKI:
135 return "bottle does not contain escrowed key spki"
136 case .failedToFetchEscrowContents:
137 return "failed to fetch escrow contents"
138 case .failedToCreateRecoveryKey:
139 return "failed to create recovery keys"
140 case .untrustedRecoveryKeys:
141 return "untrusted recovery keys"
142 case .noBottlesPresent:
143 return "no bottle present"
144 case .recoveryKeysNotEnrolled:
145 return "recovery key is not enrolled with octagon"
146 case .bottleCreatingPeerNotFound:
147 return "The peer that created the bottle was not found"
148 case .unknownCloudKitError:
149 return "unknown error from cloudkit"
150 case .cloudkitResponseMissing:
151 return "Response missing from CloudKit"
152 case .failedToLoadSecret(errorCode: let errorCode):
153 return "failed to load secret: \(errorCode)"
154 case .failedToLoadSecretDueToType:
155 return "Failed to load secret due to type mismatch (value was not dictionary)"
156 case .failedToAssembleBottle:
157 return "failed to assemble bottle for peer"
159 return "peerID is invalid"
160 case .failedToStoreSecret(errorCode: let errorCode):
161 return "failed to store the secret in the keychain \(errorCode)"
166 extension ContainerError: CustomNSError {
168 public static var errorDomain: String {
169 return "com.apple.security.trustedpeers.container"
172 public var errorCode: Int {
174 case .unableToCreateKeyPair:
176 case .noPreparedIdentity:
178 case .failedToStoreIdentity:
180 case .needsAuthentication:
182 case .missingStableInfo:
184 case .missingDynamicInfo:
188 case .invalidPermanentInfoOrSig:
190 case .invalidVoucherOrSig:
192 case .invalidStableInfoOrSig:
194 case .sponsorNotRegistered:
196 case .unknownPolicyVersion:
198 case .preparedIdentityNotOnAllowedList:
200 case .noPeersPreapprovePreparedIdentity:
202 case .policyDocumentDoesNotValidate:
204 // 16 was invalidPeerID and failedToAssembleBottle
205 case .tooManyBottlesForPeer:
207 case .noBottleForPeer:
209 case .restoreBottleFailed:
211 case .noBottlesForEscrowRecordID:
213 case .bottleDoesNotContainContents:
215 case .bottleDoesNotContainEscrowKeySignature:
217 case .bottleDoesNotContainerPeerKeySignature:
219 case .bottleDoesNotContainPeerID:
221 case .failedToCreateBottledPeer:
223 case .signatureVerificationFailed:
225 // 27 was failedToLoadSecret*
226 case .bottleDoesNotContainerEscrowKeySPKI:
228 case .failedToFetchEscrowContents:
230 case .couldNotLoadAllowedList:
232 case .failedToCreateRecoveryKey:
234 case .untrustedRecoveryKeys:
236 case .noBottlesPresent:
238 case .recoveryKeysNotEnrolled:
240 case .bottleCreatingPeerNotFound:
242 case .unknownCloudKitError:
244 case .cloudkitResponseMissing:
246 case .failedToLoadSecret:
248 case .failedToLoadSecretDueToType:
250 case .failedToStoreSecret:
252 case .failedToAssembleBottle:
259 public var underlyingError: NSError? {
261 case .failedToLoadSecret(errorCode: let errorCode):
262 return NSError(domain: "securityd", code: errorCode)
263 case .failedToStoreSecret(errorCode: let errorCode):
264 return NSError(domain: "securityd", code: errorCode)
270 public var errorUserInfo: [String: Any] {
271 var ret = [String: Any]()
272 if let desc = self.errorDescription {
273 ret[NSLocalizedDescriptionKey] = desc
275 if let underlyingError = self.underlyingError {
276 ret[NSUnderlyingErrorKey] = underlyingError
282 internal func traceError(_ error: Error?) -> String {
283 if let error = error {
284 return "error: \(String(describing: error))"
290 func saveSecret(_ secret: Data, label: String) throws {
292 let query: [CFString: Any] = [
293 kSecClass: kSecClassInternetPassword,
294 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
295 kSecUseDataProtectionKeychain: true,
296 kSecAttrAccessGroup: "com.apple.security.octagon",
297 kSecAttrSynchronizable: kCFBooleanFalse,
298 kSecAttrDescription: label,
300 kSecValueData: secret,
303 var results: CFTypeRef?
304 var status = SecItemAdd(query as CFDictionary, &results)
306 if status == errSecSuccess {
310 if status == errSecDuplicateItem {
311 // Add every primary key attribute to this find dictionary
312 var findQuery: [CFString: Any] = [:]
313 findQuery[kSecClass] = query[kSecClass]
314 findQuery[kSecAttrSynchronizable] = query[kSecAttrSynchronizable]
315 findQuery[kSecAttrAccessGroup] = query[kSecAttrAccessGroup]
316 findQuery[kSecAttrServer] = query[kSecAttrDescription]
317 findQuery[kSecAttrPath] = query[kSecAttrPath]
318 findQuery[kSecUseDataProtectionKeychain] = query[kSecUseDataProtectionKeychain];
320 var updateQuery: [CFString: Any] = query
321 updateQuery[kSecClass] = nil
323 status = SecItemUpdate(findQuery as CFDictionary, updateQuery as CFDictionary)
325 if status != errSecSuccess {
326 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
329 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
333 func loadSecret(label: String) throws -> (Data?) {
336 let query: [CFString: Any] = [
337 kSecClass: kSecClassInternetPassword,
338 kSecAttrAccessGroup: "com.apple.security.octagon",
339 kSecAttrDescription: label,
340 kSecReturnAttributes: true,
341 kSecReturnData: true,
342 kSecAttrSynchronizable: kCFBooleanFalse,
343 kSecMatchLimit: kSecMatchLimitOne,
346 var result: CFTypeRef?
347 let status = SecItemCopyMatching(query as CFDictionary, &result)
349 if status != errSecSuccess || result == nil {
350 throw ContainerError.failedToLoadSecret(errorCode: Int(status))
354 if let dictionary = result as? [CFString: Any] {
355 secret = dictionary[kSecValueData] as? Data
357 throw ContainerError.failedToLoadSecretDueToType
363 func saveEgoKeyPair(_ keyPair: _SFECKeyPair, identifier: String, resultHandler: @escaping (Bool, Error?) -> Void) {
364 let keychainManager = _SFKeychainManager.default()
365 let signingIdentity = _SFIdentity(keyPair: keyPair)
366 let accessibility = SFAccessibilityMakeWithMode(SFAccessibilityMode.accessibleWhenUnlocked) // class A
367 let accessPolicy = _SFAccessPolicy(accessibility: accessibility,
368 sharingPolicy: SFSharingPolicy.thisDeviceOnly)
369 accessPolicy.accessGroup = egoIdentitiesAccessGroup
370 keychainManager.setIdentity(signingIdentity,
371 forIdentifier: identifier,
372 accessPolicy: accessPolicy,
373 resultHandler: resultHandler)
376 func loadEgoKeyPair(identifier: String, resultHandler: @escaping (_SFECKeyPair?, Error?) -> Void) {
377 let keychainManager = _SFKeychainManager.default()
379 // FIXME constrain to egoIdentitiesAccessGroup, <rdar://problem/39597940>
380 keychainManager.identity(forIdentifier: identifier) { result in
381 switch result.resultType {
382 case .valueAvailable:
383 resultHandler(result.value?.keyPair as? _SFECKeyPair, nil)
384 case .needsAuthentication:
385 resultHandler(nil, ContainerError.needsAuthentication)
387 resultHandler(nil, result.error)
392 func loadEgoKeys(peerID: String, resultHandler: @escaping (OctagonSelfPeerKeys?, Error?) -> Void) {
393 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: peerID)) { signingKey, error in
394 guard let signingKey = signingKey else {
395 os_log("Unable to load signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
396 resultHandler(nil, error)
400 loadEgoKeyPair(identifier: encryptionKeyIdentifier(peerID: peerID)) { encryptionKey, error in
401 guard let encryptionKey = encryptionKey else {
402 os_log("Unable to load encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
403 resultHandler(nil, error)
408 resultHandler(try OctagonSelfPeerKeys(peerID: peerID, signingKey: signingKey, encryptionKey: encryptionKey), nil)
410 resultHandler(nil, error)
416 func removeEgoKeysSync(peerID: String) throws -> Bool {
417 var resultSema = DispatchSemaphore(value: 0)
419 let keychainManager = _SFKeychainManager.default()
421 var retresultForSigningDeletion: Bool = false
422 var reterrorForSigningDeletion: Error? = nil
424 //remove signing keys
425 keychainManager.removeItem(withIdentifier: signingKeyIdentifier(peerID: peerID)) { result, error in
426 retresultForSigningDeletion = result
427 reterrorForSigningDeletion = error
433 if let error = reterrorForSigningDeletion {
437 if retresultForSigningDeletion == false {
438 return retresultForSigningDeletion
441 // now let's do the same thing with the encryption keys
442 resultSema = DispatchSemaphore(value: 0)
443 var retresultForEncryptionDeletion: Bool = false
444 var reterrorForEncryptionDeletion: Error? = nil
446 keychainManager.removeItem(withIdentifier: encryptionKeyIdentifier(peerID: peerID)) { result, error in
447 retresultForEncryptionDeletion = result
448 reterrorForEncryptionDeletion = error
453 if let error = reterrorForEncryptionDeletion {
457 return retresultForEncryptionDeletion && retresultForSigningDeletion
460 func loadEgoKeysSync(peerID: String) throws -> OctagonSelfPeerKeys {
461 // Gotta promote to synchronous; 'antipattern' ahoy
462 let resultSema = DispatchSemaphore(value: 0)
464 var result: OctagonSelfPeerKeys?
467 loadEgoKeys(peerID: peerID) { keys, error in
474 if let error = reserror {
478 if let result = result {
485 func signingKeyIdentifier(peerID: String) -> String {
486 return "signing-key " + peerID
489 func encryptionKeyIdentifier(peerID: String) -> String {
490 return "encryption-key " + peerID
493 func makeTLKShares(ckksTLKs: [CKKSKeychainBackedKey]?, asPeer: CKKSSelfPeer, toPeer: CKKSPeer, epoch: Int) throws -> [TLKShare] {
494 return try (ckksTLKs ?? []).map { tlk in
495 os_log("Making TLKShare for %@ for key %@", log: tplogDebug, type: .default, toPeer.description, tlk)
496 // Not being able to convert a TLK to a TLKShare is a failure, but not having a TLK is only half bad
498 return TLKShare.convert(ckksTLKShare: try CKKSTLKShare(tlk, as: asPeer, to: toPeer, epoch: epoch, poisoned: 0))
500 let nserror = error as NSError
501 if nserror.domain == "securityd" && nserror.code == errSecItemNotFound {
502 os_log("No TLK contents for %@, no TLK share possible", log: tplogDebug, type: .default, tlk)
511 func extract(tlkShares: [CKKSTLKShare], peer: CKKSSelfPeer) {
512 os_log("Attempting to recover %d TLK shares for peer %@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID)
513 for share in tlkShares {
514 guard share.receiverPeerID == peer.peerID else {
515 os_log("Skipping %@ (wrong peerID)", log: tplogDebug, type: .default, share)
520 // TODO: how should we handle peer sets here?
521 let key = try share.recoverTLK(peer,
522 trustedPeers: [peer as! AnyHashable],
525 try key.saveMaterialToKeychain()
526 os_log("Recovered %@ (from %@)", log: tplogDebug, type: .default, key, share)
528 os_log("Failed to recover share %@: %@", log: tplogDebug, type: .default, share, error as CVarArg)
534 struct ContainerState {
535 var egoPeerID: String?
536 var peers: [String: TPPeer] = [:]
537 var vouchers: [TPVoucher] = []
538 var bottles = Set<BottleMO>()
539 var recoverySigningKey: Data?
540 var recoveryEncryptionKey: Data?
543 internal struct StableChanges {
544 let deviceName: String?
545 let serialNumber: String?
546 let osVersion: String?
547 let policyVersion: UInt64?
548 let policySecrets: [String: Data]?
549 let recoverySigningPubKey: Data?
550 var recoveryEncryptionPubKey: Data?
553 // CoreData doesn't handle creating an identical model from an identical URL. Help it out.
554 private var nsObjectModels: [URL: NSManagedObjectModel] = [:]
555 private let nsObjectModelsQueue = DispatchQueue(label: "com.apple.security.TrustedPeersHelper.nsObjectModels")
556 func getOrMakeModel(url: URL) -> NSManagedObjectModel {
557 return nsObjectModelsQueue.sync {
558 if let model = nsObjectModels[url] {
561 let newModel = NSManagedObjectModel(contentsOf: url)!
562 nsObjectModels[url] = newModel
567 struct ContainerName: Hashable, CustomStringConvertible {
568 let container: String
571 func asSingleString() -> String {
572 // Just to be nice, hide the fact that multiple contexts exist on most machines
573 if self.context == OTDefaultContext {
574 return self.container
576 return self.container + "-" + self.context
580 var description: String {
581 return "Container(\(self.container),\(self.context))"
585 /// This maps to a Cuttlefish service backed by a CloudKit container,
586 /// and a corresponding local Core Data persistent container.
588 /// Methods may be invoked concurrently.
589 class Container: NSObject {
590 let name: ContainerName
592 private let cuttlefish: CuttlefishAPIAsync
594 // Only one request (from Client) is permitted to be in progress at a time.
595 // That includes while waiting for network, i.e. one request must complete
596 // before the next can begin. Otherwise two requests could in parallel be
597 // fetching updates from Cuttlefish, with ensuing changeToken overwrites etc.
598 // This applies for mutating requests -- requests that can only read the current
599 // state (on the moc queue) do not need to.
600 internal let semaphore = DispatchSemaphore(value: 1)
602 // All Core Data access happens through moc: NSManagedObjectContext. The
603 // moc insists on having its own queue, and all operations must happen on
605 internal let moc: NSManagedObjectContext
607 // Rather than Container having its own dispatch queue, we use moc's queue
608 // to synchronise access to our own state as well. So the following instance
609 // variables must only be accessed within blocks executed by calling
610 // moc.perform() or moc.performAndWait().
611 internal var containerMO: ContainerMO
612 internal var model: TPModel
615 Construct a Container.
617 - Parameter name: The name the CloudKit container to which requests will be routed.
619 The "real" container that drives CKKS etc should be named `"com.apple.security.keychain"`.
620 Use other names for test containers such as for rawfish (rawhide-style testing for cuttlefish)
622 - Parameter persistentStoreURL: The location the local Core Data database for this container will be stored.
624 - Parameter cuttlefish: Interface to cuttlefish.
626 init(name: ContainerName, persistentStoreDescription: NSPersistentStoreDescription, cuttlefish: CuttlefishAPIAsync) throws {
627 var initError: Error?
628 var containerMO: ContainerMO?
631 // Set up Core Data stack
632 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
633 let mom = getOrMakeModel(url: url)
634 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
635 persistentContainer.persistentStoreDescriptions = [persistentStoreDescription]
637 persistentContainer.loadPersistentStores { _, error in
640 if let initError = initError {
644 let moc = persistentContainer.newBackgroundContext()
645 moc.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
648 // Fetch an existing ContainerMO record if it exists, or create and save one
650 let containerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Container")
651 containerFetch.predicate = NSPredicate(format: "name == %@", name.asSingleString())
652 let fetchedContainers = try moc.fetch(containerFetch)
653 if let container = fetchedContainers.first as? ContainerMO {
654 containerMO = container
656 containerMO = ContainerMO(context: moc)
657 containerMO!.name = name.asSingleString()
660 // Perform upgrades as needed
661 Container.onqueueUpgradeMachineIDSetToModel(container: containerMO!, moc: moc)
662 Container.onqueueUpgradeMachineIDSetToUseStatus(container: containerMO!, moc: moc)
664 model = Container.loadModel(from: containerMO!)
665 Container.ensureEgoConsistency(from: containerMO!, model: model!)
672 if let initError = initError {
678 self.containerMO = containerMO!
679 self.cuttlefish = cuttlefish
685 // Must be on containerMO's moc queue to call this
686 internal static func loadModel(from containerMO: ContainerMO) -> TPModel {
687 // Populate model from persistent store
688 let model = TPModel(decrypter: Decrypter())
689 let keyFactory = TPECPublicKeyFactory()
690 let peers = containerMO.peers as? Set<PeerMO>
691 peers?.forEach { peer in
692 guard let permanentInfo = TPPeerPermanentInfo(peerID: peer.peerID!,
693 data: peer.permanentInfo! as Data,
694 sig: peer.permanentInfoSig! as Data,
695 keyFactory: keyFactory) else {
698 model.registerPeer(with: permanentInfo)
699 if let data = peer.stableInfo, let sig = peer.stableInfoSig {
700 if let stableInfo = TPPeerStableInfo(data: data as Data, sig: sig as Data) {
702 try model.update(stableInfo, forPeerWithID: permanentInfo.peerID)
704 os_log("loadModel unable to update stable info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg)
707 os_log("loadModel: peer %@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
710 os_log("loadModel: peer %@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
712 if let data = peer.dynamicInfo, let sig = peer.dynamicInfoSig {
713 if let dynamicInfo = TPPeerDynamicInfo(data: data as Data, sig: sig as Data) {
715 try model.update(dynamicInfo, forPeerWithID: permanentInfo.peerID)
717 os_log("loadModel unable to update dynamic info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg)
720 os_log("loadModel: peer %@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
723 os_log("loadModel: peer %@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
725 peer.vouchers?.forEach {
726 let v = $0 as! VoucherMO
727 if let data = v.voucherInfo, let sig = v.voucherInfoSig {
728 if let voucher = TPVoucher(infoWith: data, sig: sig) {
729 model.register(voucher)
735 // Register persisted policies (cached from cuttlefish)
736 let policies = containerMO.policies as? Set<PolicyMO>
737 policies?.forEach { policyMO in
738 if let policyHash = policyMO.policyHash,
739 let policyData = policyMO.policyData {
740 if let policyDoc = TPPolicyDocument.policyDoc(withHash: policyHash, data: policyData) {
741 model.register(policyDoc)
746 // Register built-in policies
747 builtInPolicyDocuments().forEach { policyDoc in
748 model.register(policyDoc)
751 let knownMachines = containerMO.machines as? Set<MachineMO> ?? Set()
752 let allowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.allowed.rawValue }.compactMap { $0.machineID })
753 let disallowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.disallowed.rawValue }.compactMap { $0.machineID })
755 os_log("loadModel: allowedMachineIDs: %@", log: tplogDebug, type: .default, allowedMachineIDs)
756 os_log("loadModel: disallowedMachineIDs: %@", log: tplogDebug, type: .default, disallowedMachineIDs)
758 if allowedMachineIDs.count == 0 {
759 os_log("loadModel: no allowedMachineIDs?", log: tplogDebug, type: .default)
765 // Must be on containerMO's moc queue to call this
766 internal static func ensureEgoConsistency(from containerMO: ContainerMO, model: TPModel) {
767 guard let egoPeerID = containerMO.egoPeerID,
768 let egoStableData = containerMO.egoPeerStableInfo,
769 let egoStableSig = containerMO.egoPeerStableInfoSig
771 os_log("ensureEgoConsistency failed to find ego peer information", log: tplogDebug, type: .error)
775 guard let containerEgoStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
776 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from container", log: tplogDebug, type: .error)
780 guard let modelStableInfo = model.getStableInfoForPeer(withID: egoPeerID) else {
781 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from model", log: tplogDebug, type: .error)
785 if modelStableInfo.clock > containerEgoStableInfo.clock {
786 containerMO.egoPeerStableInfo = modelStableInfo.data
787 containerMO.egoPeerStableInfoSig = modelStableInfo.sig
792 static func dictionaryRepresentation(bottle: BottleMO) -> [String: Any] {
793 var dict: [String: String] = [:]
795 dict["bottleID"] = bottle.bottleID
796 dict["peerID"] = bottle.peerID
797 dict["signingSPKI"] = bottle.escrowedSigningSPKI?.base64EncodedString()
798 dict["signatureUsingPeerKey"] = bottle.signatureUsingPeerKey?.base64EncodedString()
799 dict["signatureUsingSPKI"] = bottle.signatureUsingEscrowKey?.base64EncodedString()
800 // ignore the bottle contents; they're mostly unreadable
805 static func peerdictionaryRepresentation(peer: TPPeer) -> [String: Any] {
806 var peerDict: [String: Any] = [
807 "permanentInfo": peer.permanentInfo.dictionaryRepresentation(),
808 "peerID": peer.peerID,
810 if let stableInfo = peer.stableInfo {
811 peerDict["stableInfo"] = stableInfo.dictionaryRepresentation()
813 if let dynamicInfo = peer.dynamicInfo {
814 peerDict["dynamicInfo"] = dynamicInfo.dictionaryRepresentation()
820 func onQueueDetermineLocalTrustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
821 let peerCountsByModelID = self.model.peerCountsByModelID()
823 if let egoPeerID = self.containerMO.egoPeerID {
824 var status = self.model.statusOfPeer(withID: egoPeerID)
825 var isExcluded: Bool = (status == .excluded)
827 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, loadError in
828 var returnError = loadError
830 guard returnError == nil else {
832 if let error = (loadError as NSError?) {
833 os_log("trust status: Unable to load ego keys: %@", log: tplogDebug, type: .default, error as CVarArg)
834 if error.code == errSecItemNotFound && error.domain == NSOSStatusErrorDomain {
835 os_log("trust status: Lost the ego key pair, returning 'excluded' in hopes of fixing up the identity", log: tplogDebug, type: .debug)
838 } else if error.code == errSecInteractionNotAllowed && error.domain == NSOSStatusErrorDomain {
839 // we have an item, but can't read it, suppressing error
845 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
847 peerCountsByModelID: peerCountsByModelID,
848 isExcluded: isExcluded,
850 reply(egoStatus, returnError)
854 //ensure egoPeerKeys are populated
855 guard egoPeerKeys != nil else {
856 os_log("trust status: No error but Ego Peer Keys are nil", log: tplogDebug, type: .default)
857 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
859 peerCountsByModelID: peerCountsByModelID,
863 reply(egoStatus, loadError)
867 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
869 peerCountsByModelID: peerCountsByModelID,
870 isExcluded: isExcluded,
872 reply(egoStatus, nil)
877 // With no ego peer ID, either return 'excluded' if there are extant peers, or 'unknown' to signal no peers at all
878 if self.model.allPeerIDs().isEmpty {
879 os_log("No existing peers in account", log: tplogDebug, type: .debug)
880 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
882 peerCountsByModelID: peerCountsByModelID,
885 reply(egoStatus, nil)
888 os_log("Existing peers in account, but we don't have a peer ID. We are excluded.", log: tplogDebug, type: .debug)
889 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
891 peerCountsByModelID: peerCountsByModelID,
894 reply(egoStatus, nil)
900 func trustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
901 self.semaphore.wait()
902 let reply: (TrustedPeersHelperEgoPeerStatus, Error?) -> Void = {
903 // Suppress logging of successful replies here; it's not that useful
904 let logType: OSLogType = $1 == nil ? .debug : .info
905 os_log("trustStatus complete: %@ %@",
906 log: tplogTrace, type: logType, TPPeerStatusToString($0.egoStatus), traceError($1))
908 self.semaphore.signal()
911 self.moc.performAndWait {
912 // Knowledge of your peer status only exists if you know about other peers. If you haven't fetched, fetch.
913 if self.containerMO.changeToken == nil {
914 self.fetchAndPersistChanges { fetchError in
915 guard fetchError == nil else {
916 if let error = fetchError {
917 os_log("Unable to fetch changes, trust status is unknown: %@", log: tplogDebug, type: .default, error as CVarArg)
920 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
922 peerCountsByModelID: [:],
925 reply(egoStatus, fetchError)
929 self.moc.performAndWait {
930 self.onQueueDetermineLocalTrustStatus(reply: reply)
934 self.onQueueDetermineLocalTrustStatus(reply: reply)
939 func fetchTrustState(reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) {
940 let reply: (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void = {
941 os_log("fetch trust state complete: %@ %@",
942 log: tplogTrace, type: .info, String(reflecting: $0), traceError($2))
946 self.moc.performAndWait {
947 if let egoPeerID = self.containerMO.egoPeerID,
948 let egoPermData = self.containerMO.egoPeerPermanentInfo,
949 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig {
951 let keyFactory = TPECPublicKeyFactory()
952 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
953 os_log("fetchTrustState failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
954 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
958 let isPreapproved = self.model.hasPeerPreapprovingKey(permanentInfo.signingPubKey.spki())
959 os_log("fetchTrustState: ego peer is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
961 let egoPeerStatus = TrustedPeersHelperPeerState(peerID: egoPeerID,
962 isPreapproved: isPreapproved,
963 status: self.model.statusOfPeer(withID: egoPeerID),
964 memberChanges: false,
965 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful())
967 var tphPeers: [TrustedPeersHelperPeer] = []
969 if let egoPeer = self.model.peer(withID: egoPeerID) {
970 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
971 if let peer = self.model.peer(withID: trustedPeerID) {
972 let peerViews = try? self.model.getViewsForPeer(peer.permanentInfo,
973 stableInfo: peer.stableInfo,
976 tphPeers.append(TrustedPeersHelperPeer(peerID: trustedPeerID,
977 signingSPKI: peer.permanentInfo.signingPubKey.spki(),
978 encryptionSPKI: peer.permanentInfo.encryptionPubKey.spki(),
979 viewList: peerViews ?? Set()))
981 os_log("No peer for trusted ID %@", log: tplogDebug, type: .default, trustedPeerID)
985 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
988 os_log("Returning trust state: %@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers)
989 reply(egoPeerStatus, tphPeers, nil)
991 // With no ego peer ID, there are no trusted peers
992 os_log("No peer ID => no trusted peers", log: tplogDebug, type: .debug)
993 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: false, unknownMachineIDs: false), [], nil)
998 func dump(reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
999 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1000 os_log("dump complete: %@",
1001 log: tplogTrace, type: .info, traceError($1))
1004 self.moc.performAndWait {
1005 var d: [AnyHashable: Any] = [:]
1007 if let egoPeerID = self.containerMO.egoPeerID {
1008 if let peer = self.model.peer(withID: egoPeerID) {
1009 d["self"] = Container.peerdictionaryRepresentation(peer: peer)
1011 d["self"] = ["peerID": egoPeerID]
1017 d["peers"] = self.model.allPeers().filter { $0.peerID != self.containerMO.egoPeerID }.map { peer in
1018 Container.peerdictionaryRepresentation(peer: peer)
1021 d["vouchers"] = self.model.allVouchers().map { $0.dictionaryRepresentation() }
1023 if let bottles = self.containerMO.bottles as? Set<BottleMO> {
1024 d["bottles"] = bottles.map { Container.dictionaryRepresentation(bottle: $0) }
1029 let midList = self.onqueueCurrentMIDList()
1030 d["machineIDsAllowed"] = midList.machineIDs(in: .allowed).sorted()
1031 d["machineIDsDisallowed"] = midList.machineIDs(in: .disallowed).sorted()
1037 func dumpEgoPeer(reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) {
1038 let reply: (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void = {
1039 os_log("dumpEgoPeer complete: %@", log: tplogTrace, type: .info, traceError($4))
1040 reply($0, $1, $2, $3, $4)
1042 self.moc.performAndWait {
1043 guard let egoPeerID = self.containerMO.egoPeerID else {
1044 reply(nil, nil, nil, nil, ContainerError.noPreparedIdentity)
1048 guard let peer = self.model.peer(withID: egoPeerID) else {
1049 reply(egoPeerID, nil, nil, nil, nil)
1053 reply(egoPeerID, peer.permanentInfo, peer.stableInfo, peer.dynamicInfo, nil)
1057 func validatePeers(request: ValidatePeersRequest, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1058 self.semaphore.wait()
1059 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1060 os_log("validatePeers complete %@", log: tplogTrace, type: .info, traceError($1))
1061 self.semaphore.signal()
1065 self.cuttlefish.validatePeers(request) { response, error in
1066 os_log("ValidatePeers(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1067 guard let response = response, error == nil else {
1068 os_log("validatePeers failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1069 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
1073 var info: [AnyHashable: Any] = [:]
1074 info["health"] = response.validatorsHealth as AnyObject
1075 info["results"] = try? JSONSerialization.jsonObject(with: response.jsonUTF8Data())
1081 func getViews(inViews: [String], reply: @escaping ([String]?, Error?) -> Void) {
1082 let reply: ([String]?, Error?) -> Void = {
1083 os_log("getViews complete %@", log: tplogTrace, type: .info, traceError($1))
1086 self.moc.performAndWait {
1087 guard let egoPeerID = self.containerMO.egoPeerID,
1088 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1089 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1090 let egoStableData = self.containerMO.egoPeerStableInfo,
1091 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1093 os_log("getViews failed to find ego peer information", log: tplogDebug, type: .error)
1094 reply(nil, ContainerError.noPreparedIdentity)
1097 guard let stableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1098 os_log("getViews failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
1099 reply(nil, ContainerError.invalidStableInfoOrSig)
1103 let keyFactory = TPECPublicKeyFactory()
1104 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1105 os_log("getViews failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
1106 reply(nil, ContainerError.invalidPermanentInfoOrSig)
1111 let views = try self.model.getViewsForPeer(permanentInfo, stableInfo: stableInfo, inViews: Set(inViews))
1112 reply(Array(views), nil)
1120 func reset(reply: @escaping (Error?) -> Void) {
1121 self.semaphore.wait()
1122 let reply: (Error?) -> Void = {
1123 os_log("reset complete %@", log: tplogTrace, type: .info, traceError($0))
1124 self.semaphore.signal()
1128 self.moc.performAndWait {
1129 let request = ResetRequest()
1130 self.cuttlefish.reset(request) { response, error in
1131 os_log("Reset(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1132 guard let response = response, error == nil else {
1133 os_log("reset failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1134 reply(error ?? ContainerError.cloudkitResponseMissing)
1138 // Erase container's persisted state
1139 self.moc.performAndWait {
1140 self.moc.delete(self.containerMO)
1141 self.containerMO = ContainerMO(context: self.moc)
1142 self.containerMO.name = self.name.asSingleString()
1143 self.model = Container.loadModel(from: self.containerMO)
1145 try self.onQueuePersist(changes: response.changes)
1146 os_log("reset succeded", log: tplogDebug, type: .default)
1149 os_log("reset persist failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1157 func localReset(reply: @escaping (Error?) -> Void) {
1158 self.semaphore.wait()
1159 let reply: (Error?) -> Void = {
1160 os_log("localReset complete %@", log: tplogTrace, type: .info, traceError($0))
1161 self.semaphore.signal()
1165 self.moc.performAndWait {
1167 // Erase container's persisted state
1168 self.moc.delete(self.containerMO)
1169 self.containerMO = ContainerMO(context: self.moc)
1170 self.containerMO.name = self.name.asSingleString()
1171 self.model = Container.loadModel(from: self.containerMO)
1181 func loadOrCreateKeyPair(privateKeyPersistentRef: Data?) throws -> _SFECKeyPair {
1182 if let privateKeyPersistentRef = privateKeyPersistentRef {
1183 return try TPHObjectiveC.fetchKeyPair(withPrivateKeyPersistentRef: privateKeyPersistentRef)
1185 let keySpecifier = _SFECKeySpecifier(curve: SFEllipticCurve.nistp384)
1186 guard let keyPair = _SFECKeyPair(randomKeyPairWith: keySpecifier) else {
1187 throw ContainerError.unableToCreateKeyPair
1193 // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion
1194 func prepare(epoch: UInt64,
1199 deviceName: String?,
1200 serialNumber: String,
1202 policyVersion: UInt64?,
1203 policySecrets: [String: Data]?,
1204 signingPrivateKeyPersistentRef: Data?,
1205 encryptionPrivateKeyPersistentRef: Data?,
1206 reply: @escaping (String?, Data?, Data?, Data?, Data?, Error?) -> Void) {
1207 self.semaphore.wait()
1208 let reply: (String?, Data?, Data?, Data?, Data?, Error?) -> Void = {
1209 os_log("prepare complete peerID: %@ %@",
1210 log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($5))
1211 self.semaphore.signal()
1212 reply($0, $1, $2, $3, $4, $5)
1215 // Create a new peer identity with random keys, and store the keys in keychain
1216 let permanentInfo: TPPeerPermanentInfo
1217 let signingKeyPair: _SFECKeyPair
1218 let encryptionKeyPair: _SFECKeyPair
1220 signingKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: signingPrivateKeyPersistentRef)
1221 encryptionKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: encryptionPrivateKeyPersistentRef)
1223 permanentInfo = try TPPeerPermanentInfo(machineID: machineID,
1226 signing: signingKeyPair,
1227 encryptionKeyPair: encryptionKeyPair,
1228 peerIDHashAlgo: TPHashAlgo.SHA256)
1231 reply(nil, nil, nil, nil, nil, error)
1235 let peerID = permanentInfo.peerID
1237 let bottle: BottledPeer
1239 bottle = try BottledPeer(peerID: peerID,
1241 peerSigningKey: signingKeyPair,
1242 peerEncryptionKey: encryptionKeyPair,
1243 bottleSalt: bottleSalt)
1245 _ = try saveSecret(bottle.secret, label: peerID)
1247 os_log("bottle creation failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1248 reply(nil, nil, nil, nil, nil, error)
1252 saveEgoKeyPair(signingKeyPair, identifier: signingKeyIdentifier(peerID: peerID)) { success, error in
1253 guard success else {
1254 os_log("Unable to save signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1255 reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1258 saveEgoKeyPair(encryptionKeyPair, identifier: encryptionKeyIdentifier(peerID: peerID)) { success, error in
1259 guard success else {
1260 os_log("Unable to save encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1261 reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1265 // Save the prepared identity as containerMO.egoPeer* and its bottle
1266 self.moc.performAndWait {
1268 let policyVersion = policyVersion ?? prevailingPolicyVersion
1269 let policyDoc = try self.getPolicyDoc(policyVersion)
1271 let stableInfo = TPPeerStableInfo(clock: 1,
1272 policyVersion: policyDoc.policyVersion,
1273 policyHash: policyDoc.policyHash,
1274 policySecrets: policySecrets,
1275 deviceName: deviceName,
1276 serialNumber: serialNumber,
1277 osVersion: osVersion,
1278 signing: signingKeyPair,
1279 recoverySigningPubKey: nil,
1280 recoveryEncryptionPubKey: nil,
1283 self.containerMO.egoPeerID = permanentInfo.peerID
1284 self.containerMO.egoPeerPermanentInfo = permanentInfo.data
1285 self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig
1286 self.containerMO.egoPeerStableInfo = stableInfo.data
1287 self.containerMO.egoPeerStableInfoSig = stableInfo.sig
1289 let bottleMO = BottleMO(context: self.moc)
1290 bottleMO.peerID = bottle.peerID
1291 bottleMO.bottleID = bottle.bottleID
1292 bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI
1293 bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
1294 bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey
1295 bottleMO.contents = bottle.contents
1297 self.containerMO.addToBottles(bottleMO)
1301 reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, nil)
1303 reply(nil, nil, nil, nil, nil, error)
1309 func getEgoEpoch(reply: @escaping (UInt64, Error?) -> Void) {
1310 let reply: (UInt64, Error?) -> Void = {
1311 os_log("getEgoEpoch complete: %d %@", log: tplogTrace, type: .info, $0, traceError($1))
1315 self.moc.performAndWait {
1316 guard let egoPeerID = self.containerMO.egoPeerID else {
1317 reply(0, ContainerError.noPreparedIdentity)
1320 guard let egoPeer = self.model.peer(withID: egoPeerID) else {
1321 reply(0, ContainerError.noPreparedIdentity)
1325 reply(egoPeer.permanentInfo.epoch, nil)
1328 func establish(ckksKeys: [CKKSKeychainBackedKeySet],
1329 tlkShares: [CKKSTLKShare],
1330 preapprovedKeys: [Data]?,
1331 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
1332 self.semaphore.wait()
1333 let reply: (String?, [CKRecord], Error?) -> Void = {
1334 os_log("establish complete peer: %@ %@",
1335 log: tplogTrace, type: .default, ($0 ?? "NULL") as CVarArg, traceError($2))
1336 self.semaphore.signal()
1340 self.moc.performAndWait {
1341 self.onqueueEstablish(ckksKeys: ckksKeys,
1342 tlkShares: tlkShares,
1343 preapprovedKeys: preapprovedKeys,
1348 func onqueueTTRUntrusted() -> Void {
1349 let ttr = SecTapToRadar(tapToRadar: "Device not IDMS trusted",
1350 description: "Device not IDMS trusted",
1355 func onqueueEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1356 tlkShares: [CKKSTLKShare],
1357 preapprovedKeys: [Data]?,
1358 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
1359 // Fetch ego peer identity from local storage.
1360 guard let egoPeerID = self.containerMO.egoPeerID,
1361 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1362 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1363 let egoStableData = self.containerMO.egoPeerStableInfo,
1364 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1366 reply(nil, [], ContainerError.noPreparedIdentity)
1370 let keyFactory = TPECPublicKeyFactory()
1371 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1372 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
1375 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1376 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1377 reply(nil, [], ContainerError.invalidStableInfoOrSig)
1380 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
1381 os_log("establish: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
1382 self.onqueueTTRUntrusted()
1383 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
1387 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
1388 guard let egoPeerKeys = egoPeerKeys else {
1389 os_log("Don't have my own peer keys; can't establish: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1390 reply(nil, [], error)
1393 self.moc.performAndWait {
1394 let viewKeys: [ViewKeys] = ckksKeys.map(ViewKeys.convert)
1395 let allTLKShares: [TLKShare]
1397 let octagonShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk }, asPeer: egoPeerKeys, toPeer: egoPeerKeys, epoch: Int(selfPermanentInfo.epoch))
1398 let sosShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
1400 allTLKShares = octagonShares + sosShares
1402 os_log("Unable to make TLKShares for self: %@", log: tplogDebug, type: .default, error as CVarArg)
1403 reply(nil, [], error)
1407 let dynamicInfo: TPPeerDynamicInfo
1409 dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
1410 peerPermanentInfo: selfPermanentInfo,
1411 peerStableInfo: selfStableInfo,
1413 preapprovedKeys: preapprovedKeys,
1414 signing: egoPeerKeys.signingKey,
1415 currentMachineIDs: self.onqueueCurrentMIDList())
1417 os_log("dynamic info: %@", log: tplogDebug, type: .default, dynamicInfo)
1419 reply(nil, [], error)
1423 let peer = Peer.with {
1424 $0.peerID = egoPeerID
1425 $0.permanentInfoAndSig.peerPermanentInfo = egoPermData
1426 $0.permanentInfoAndSig.sig = egoPermSig
1427 $0.stableInfoAndSig.peerStableInfo = egoStableData
1428 $0.stableInfoAndSig.sig = egoStableSig
1429 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
1434 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
1436 reply(nil, [], error)
1439 os_log("Beginning establish for peer %@", log: tplogDebug, type: .default, egoPeerID)
1440 os_log("Establish permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
1441 os_log("Establish permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
1442 os_log("Establish stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
1443 os_log("Establish stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
1444 os_log("Establish dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
1445 os_log("Establish dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
1447 os_log("Establish introducing %d key sets, %d tlk shares", log: tplogDebug, type: .default, viewKeys.count, allTLKShares.count)
1450 os_log("Establish bottle: %@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString())
1451 os_log("Establish peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
1453 os_log("Establish unable to encode bottle/peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
1456 let request = EstablishRequest.with {
1459 $0.viewKeys = viewKeys
1460 $0.tlkShares = allTLKShares
1462 self.cuttlefish.establish(request) { response, error in
1463 os_log("Establish(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1464 os_log("Establish: viewKeys: %@", String(describing: viewKeys))
1465 guard let response = response, error == nil else {
1466 os_log("establish failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1467 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
1472 os_log("Establish returned changes: %@", log: tplogDebug, type: .default, try response.changes.jsonString())
1474 os_log("Establish returned changes, but they can't be serialized", log: tplogDebug, type: .default)
1477 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
1480 try self.persist(changes: response.changes)
1482 guard response.changes.more == false else {
1483 os_log("establish succeeded, but more changes need fetching...", log: tplogDebug, type: .default)
1485 self.fetchAndPersistChanges { fetchError in
1486 guard fetchError == nil else {
1487 // This is an odd error condition: we might be able to fetch again and be in a good state...
1488 os_log("fetch-after-establish failed: %@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error")
1489 reply(nil, keyHierarchyRecords, fetchError)
1493 os_log("fetch-after-establish succeeded", log: tplogDebug, type: .default)
1494 reply(egoPeerID, keyHierarchyRecords, nil)
1499 os_log("establish succeeded", log: tplogDebug, type: .default)
1500 reply(egoPeerID, keyHierarchyRecords, nil)
1502 os_log("establish handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1503 reply(nil, keyHierarchyRecords, error)
1510 func setRecoveryKey(recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping (Error?) -> Void) {
1511 self.semaphore.wait()
1512 let reply: (Error?) -> Void = {
1513 os_log("setRecoveryKey complete: %@", log: tplogTrace, type: .info, traceError($0))
1514 self.semaphore.signal()
1518 os_log("beginning a setRecoveryKey", log: tplogDebug, type: .default)
1520 self.moc.performAndWait {
1521 guard let egoPeerID = self.containerMO.egoPeerID else {
1522 os_log("no prepared identity, cannot set recovery key", log: tplogDebug, type: .default)
1523 reply(ContainerError.noPreparedIdentity)
1527 var recoveryKeys: RecoveryKey
1529 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
1531 os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg)
1532 reply(ContainerError.failedToCreateRecoveryKey)
1536 let signingPublicKey: Data = recoveryKeys.peerKeys.signingVerificationKey.keyData
1537 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionVerificationKey.keyData
1539 os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString())
1540 os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString())
1542 guard let stableInfoData = self.containerMO.egoPeerStableInfo else {
1543 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1544 reply(ContainerError.nonMember)
1547 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1548 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1549 reply(ContainerError.nonMember)
1552 guard let permInfoData = self.containerMO.egoPeerPermanentInfo else {
1553 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1554 reply(ContainerError.nonMember)
1557 guard let permInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1558 os_log("permInfoSig does not exist", log: tplogDebug, type: .default)
1559 reply(ContainerError.nonMember)
1562 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
1563 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1564 reply(ContainerError.invalidStableInfoOrSig)
1567 let keyFactory = TPECPublicKeyFactory()
1568 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permInfoData, sig: permInfoSig, keyFactory: keyFactory) else {
1569 os_log("cannot create TPPeerPermanentInfo", log: tplogDebug, type: .default)
1570 reply(ContainerError.invalidStableInfoOrSig)
1574 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
1575 guard let signingKeyPair = signingKeyPair else {
1576 os_log("handle: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1580 self.moc.performAndWait {
1582 let tlkShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
1583 asPeer: recoveryKeys.peerKeys,
1584 toPeer: recoveryKeys.peerKeys,
1585 epoch: Int(permanentInfo.epoch))
1587 let policyVersion = stableInfo.policyVersion
1588 let policyDoc = try self.getPolicyDoc(policyVersion)
1590 let updatedStableInfo = TPPeerStableInfo(clock: stableInfo.clock + 1,
1591 policyVersion: policyDoc.policyVersion,
1592 policyHash: policyDoc.policyHash,
1593 policySecrets: stableInfo.policySecrets,
1594 deviceName: stableInfo.deviceName,
1595 serialNumber: stableInfo.serialNumber,
1596 osVersion: stableInfo.osVersion,
1597 signing: signingKeyPair,
1598 recoverySigningPubKey: signingPublicKey,
1599 recoveryEncryptionPubKey: encryptionPublicKey,
1601 let signedStableInfo = SignedPeerStableInfo(updatedStableInfo)
1603 let request = SetRecoveryKeyRequest.with {
1604 $0.peerID = egoPeerID
1605 $0.recoverySigningPubKey = signingPublicKey
1606 $0.recoveryEncryptionPubKey = encryptionPublicKey
1607 $0.stableInfoAndSig = signedStableInfo
1608 $0.tlkShares = tlkShares
1609 $0.changeToken = self.containerMO.changeToken ?? ""
1612 self.cuttlefish.setRecoveryKey(request) { response, error in
1613 os_log("SetRecoveryKey(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1614 guard let response = response, error == nil else {
1615 os_log("setRecoveryKey failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1616 reply(error ?? ContainerError.cloudkitResponseMissing)
1620 self.moc.performAndWait {
1622 self.containerMO.egoPeerStableInfo = updatedStableInfo.data
1623 self.containerMO.egoPeerStableInfoSig = updatedStableInfo.sig
1624 try self.onQueuePersist(changes: response.changes)
1626 os_log("setRecoveryKey succeeded", log: tplogDebug, type: .default)
1629 os_log("setRecoveryKey handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1642 func currentSetContainerBottleID(bottleMOs: Set<BottleMO>, bottleID: String) -> (Bool) {
1643 let bmos = bottleMOs.filter {
1644 $0.bottleID == bottleID
1646 return !bmos.isEmpty
1649 func findBottleForEscrowRecordID(bottleID: String, reply: @escaping (BottleMO?, Error?) -> Void) {
1652 var bottles: Set<BottleMO> = []
1653 var shouldPerformFetch = false
1655 if let containerBottles = self.containerMO.bottles as? Set<BottleMO> {
1656 if self.currentSetContainerBottleID(bottleMOs: containerBottles, bottleID: bottleID) == false {
1657 shouldPerformFetch = true
1659 bottles = containerBottles
1662 shouldPerformFetch = true
1665 if shouldPerformFetch == true {
1666 self.fetchViableBottlesWithSemaphore { _, _, error in
1667 guard error == nil else {
1668 os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1673 guard let newBottles = self.containerMO.bottles as? Set<BottleMO> else {
1674 os_log("no bottles on container: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1675 reply(nil, ContainerError.noBottlesPresent)
1679 guard self.currentSetContainerBottleID(bottleMOs: newBottles, bottleID: bottleID) == true else {
1680 reply(nil, ContainerError.noBottlesForEscrowRecordID)
1684 os_log("findBottleForEscrowRecordID found bottle: %@", log: tplogDebug, type: .default, newBottles)
1686 bottles = newBottles.filter {
1687 $0.bottleID == bottleID
1689 if bottles.count > 1 {
1690 reply(nil, ContainerError.tooManyBottlesForPeer)
1693 bmo = bottles.removeFirst()
1697 var filteredBottles = bottles.filter {
1698 $0.bottleID == bottleID
1700 if filteredBottles.count > 1 {
1701 reply(nil, ContainerError.tooManyBottlesForPeer)
1704 bmo = filteredBottles.removeFirst()
1709 func onqueueRecoverBottle(managedBottle: BottleMO, entropy: Data, bottleSalt: String) throws -> BottledPeer {
1710 guard let bottledContents = managedBottle.contents else {
1711 throw ContainerError.bottleDoesNotContainContents
1713 guard let signatureUsingEscrowKey = managedBottle.signatureUsingEscrowKey else {
1714 throw ContainerError.bottleDoesNotContainEscrowKeySignature
1717 guard let signatureUsingPeerKey = managedBottle.signatureUsingPeerKey else {
1718 throw ContainerError.bottleDoesNotContainerPeerKeySignature
1720 guard let sponsorPeerID = managedBottle.peerID else {
1721 throw ContainerError.bottleDoesNotContainPeerID
1724 //verify bottle signature using peer
1726 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1727 os_log("recover bottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1728 throw ContainerError.bottleCreatingPeerNotFound
1730 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1731 os_log("recover bottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1732 throw ContainerError.signatureVerificationFailed
1735 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1737 os_log("Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1738 throw ContainerError.failedToCreateBottledPeer
1742 return try BottledPeer(contents: bottledContents,
1744 bottleSalt: bottleSalt,
1745 signatureUsingEscrow: signatureUsingEscrowKey,
1746 signatureUsingPeerKey: signatureUsingPeerKey)
1748 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1751 return try BottledPeer(contents: bottledContents,
1754 signatureUsingEscrow: signatureUsingEscrowKey,
1755 signatureUsingPeerKey: signatureUsingPeerKey)
1757 os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1758 throw ContainerError.failedToCreateBottledPeer
1763 func vouchWithBottle(bottleID: String,
1766 tlkShares: [CKKSTLKShare],
1767 reply: @escaping (Data?, Data?, Error?) -> Void) {
1768 self.semaphore.wait()
1769 let reply: (Data?, Data?, Error?) -> Void = {
1770 os_log("vouchWithBottle complete: %@",
1771 log: tplogTrace, type: .info, traceError($2))
1772 self.semaphore.signal()
1776 self.fetchAndPersistChanges { error in
1777 guard error == nil else {
1778 os_log("vouchWithBottle unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1779 reply(nil, nil, error)
1783 self.findBottleForEscrowRecordID(bottleID: bottleID) { returnedBMO, error in
1784 self.moc.performAndWait {
1785 guard error == nil else {
1786 os_log("vouchWithBottle unable to find bottle for escrow record id: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1787 reply(nil, nil, error)
1791 guard let bmo: BottleMO = returnedBMO else {
1792 os_log("vouchWithBottle bottle is nil: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1793 reply(nil, nil, error)
1797 guard let bottledContents = bmo.contents else {
1798 reply(nil, nil, ContainerError.bottleDoesNotContainContents)
1801 guard let signatureUsingEscrowKey = bmo.signatureUsingEscrowKey else {
1802 reply(nil, nil, ContainerError.bottleDoesNotContainEscrowKeySignature)
1806 guard let signatureUsingPeerKey = bmo.signatureUsingPeerKey else {
1807 reply(nil, nil, ContainerError.bottleDoesNotContainerPeerKeySignature)
1810 guard let sponsorPeerID = bmo.peerID else {
1811 reply(nil, nil, ContainerError.bottleDoesNotContainPeerID)
1815 //verify bottle signature using peer
1817 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1818 os_log("vouchWithBottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1819 reply(nil, nil, ContainerError.bottleCreatingPeerNotFound)
1822 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1823 os_log("vouchWithBottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1824 reply(nil, nil, ContainerError.signatureVerificationFailed)
1828 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1830 os_log("vouchWithBottle: Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1831 reply(nil, nil, ContainerError.failedToCreateBottledPeer)
1835 //create bottled peer
1836 let bottledPeer: BottledPeer
1838 bottledPeer = try BottledPeer(contents: bottledContents,
1840 bottleSalt: bottleSalt,
1841 signatureUsingEscrow: signatureUsingEscrowKey,
1842 signatureUsingPeerKey: signatureUsingPeerKey)
1844 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1847 bottledPeer = try BottledPeer(contents: bottledContents,
1850 signatureUsingEscrow: signatureUsingEscrowKey,
1851 signatureUsingPeerKey: signatureUsingPeerKey)
1854 os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1855 reply(nil, nil, ContainerError.failedToCreateBottledPeer)
1860 os_log("Have a bottle for peer %@", log: tplogDebug, type: .default, bottledPeer.peerID)
1862 // Extract any TLKs we have been given
1863 extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys)
1865 self.moc.performAndWait {
1866 // I must have an ego identity in order to vouch using bottle
1867 guard let egoPeerID = self.containerMO.egoPeerID else {
1868 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
1869 reply(nil, nil, ContainerError.nonMember)
1872 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
1873 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1874 reply(nil, nil, ContainerError.nonMember)
1877 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1878 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
1879 reply(nil, nil, ContainerError.nonMember)
1882 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
1883 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1884 reply(nil, nil, ContainerError.nonMember)
1887 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1888 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1889 reply(nil, nil, ContainerError.nonMember)
1892 let keyFactory = TPECPublicKeyFactory()
1893 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
1894 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
1895 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
1898 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
1899 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
1900 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
1905 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
1906 stableInfo: beneficiaryStableInfo,
1907 withSponsorID: sponsorPeerID,
1908 reason: TPVoucherReason.restore,
1909 signing: bottledPeer.peerKeys.signingKey)
1910 reply(voucher.data, voucher.sig, nil)
1913 os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg)
1914 reply(nil, nil, error)
1923 func vouchWithRecoveryKey(recoveryKey: String,
1925 tlkShares: [CKKSTLKShare],
1926 reply: @escaping (Data?, Data?, Error?) -> Void) {
1927 self.semaphore.wait()
1928 let reply: (Data?, Data?, Error?) -> Void = {
1929 os_log("vouchWithRecoveryKey complete: %@",
1930 log: tplogTrace, type: .info, traceError($2))
1931 self.semaphore.signal()
1935 self.moc.performAndWait {
1936 os_log("beginning a vouchWithRecoveryKey", log: tplogDebug, type: .default)
1938 // I must have an ego identity in order to vouch using bottle
1939 guard let egoPeerID = self.containerMO.egoPeerID else {
1940 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
1941 reply(nil, nil, ContainerError.nonMember)
1944 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
1945 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1946 reply(nil, nil, ContainerError.nonMember)
1949 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1950 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
1951 reply(nil, nil, ContainerError.nonMember)
1954 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
1955 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1956 reply(nil, nil, ContainerError.nonMember)
1959 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1960 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1961 reply(nil, nil, ContainerError.nonMember)
1964 let keyFactory = TPECPublicKeyFactory()
1965 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
1966 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
1967 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
1970 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
1971 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
1972 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
1976 //create recovery key set
1977 var recoveryKeys: RecoveryKey
1979 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
1981 os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg)
1982 reply(nil, nil, ContainerError.failedToCreateRecoveryKey)
1986 extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys)
1988 let signingPublicKey: Data = recoveryKeys.peerKeys.signingKey.publicKey.keyData
1989 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionKey.publicKey.keyData
1991 os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString())
1992 os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString())
1994 guard self.model.isRecoveryKeyEnrolled() else {
1995 os_log("Recovery Key is not enrolled", log: tplogDebug, type: .default)
1996 reply(nil, nil, ContainerError.recoveryKeysNotEnrolled)
2000 //find matching peer containing recovery keys
2001 guard let sponsorPeerID = self.model.peerIDThatTrustsRecoveryKeys(TPRecoveryKeyPair(signingSPKI: signingPublicKey, encryptionSPKI: encryptionPublicKey)) else {
2002 os_log("Untrusted recovery key set", log: tplogDebug, type: .default)
2003 reply(nil, nil, ContainerError.untrustedRecoveryKeys)
2008 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2009 stableInfo: beneficiaryStableInfo,
2010 withSponsorID: sponsorPeerID,
2011 reason: TPVoucherReason.recoveryKey,
2012 signing: recoveryKeys.peerKeys.signingKey)
2013 reply(voucher.data, voucher.sig, nil)
2016 os_log("Error creating voucher using recovery key set: %@", log: tplogDebug, type: .default, error as CVarArg)
2017 reply(nil, nil, error)
2023 func vouch(peerID: String,
2024 permanentInfo: Data,
2025 permanentInfoSig: Data,
2027 stableInfoSig: Data,
2028 ckksKeys: [CKKSKeychainBackedKeySet],
2029 reply: @escaping (Data?, Data?, Error?) -> Void) {
2030 self.semaphore.wait()
2031 let reply: (Data?, Data?, Error?) -> Void = {
2032 os_log("vouch complete: %@", log: tplogTrace, type: .info, traceError($2))
2033 self.semaphore.signal()
2037 self.moc.performAndWait {
2038 // I must have an ego identity in order to vouch for someone else.
2039 guard let egoPeerID = self.containerMO.egoPeerID,
2040 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2041 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else {
2042 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2043 reply(nil, nil, ContainerError.nonMember)
2047 let keyFactory = TPECPublicKeyFactory()
2049 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2050 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2054 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: peerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2055 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2056 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2060 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2061 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2062 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2066 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2067 guard let egoPeerKeys = egoPeerKeys else {
2068 os_log("Don't have my own keys: can't vouch for %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, (error as CVarArg?) ?? "no error")
2069 reply(nil, nil, error)
2072 self.moc.performAndWait {
2073 let voucher: TPVoucher
2075 voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2076 stableInfo: beneficiaryStableInfo,
2077 withSponsorID: egoPeerID,
2078 reason: TPVoucherReason.secureChannel,
2079 signing: egoPeerKeys.signingKey)
2082 os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg)
2083 reply(nil, nil, error)
2087 // And generate and upload any tlkShares
2088 // Note that this might not be the whole list: <rdar://problem/47899980> Octagon: Limited Peers
2089 let tlkShares: [TLKShare]
2091 // Note: we only want to send up TLKs for uploaded ckks zones
2092 let ckksTLKs = ckksKeys.filter { !$0.newUpload }.map { $0.tlk }
2094 tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs,
2095 asPeer: egoPeerKeys,
2096 toPeer: beneficiaryPermanentInfo,
2097 epoch: Int(selfPermanentInfo.epoch))
2099 os_log("Unable to make TLKShares for beneficiary %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, error as CVarArg)
2100 reply(nil, nil, error)
2104 guard !tlkShares.isEmpty else {
2105 os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default)
2106 reply(voucher.data, voucher.sig, nil)
2110 self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "",
2112 stableInfoAndSig: nil,
2113 dynamicInfoAndSig: nil,
2114 tlkShares: tlkShares,
2115 viewKeys: []) { response, error in
2116 guard let response = response, error == nil else {
2117 os_log("Unable to upload new tlkshares: %@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
2118 reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing)
2122 let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init)
2123 os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords)
2124 // We don't need to save these; CKKS will refetch them as needed
2126 reply(voucher.data, voucher.sig, nil)
2133 func departByDistrustingSelf(reply: @escaping (Error?) -> Void) {
2134 self.semaphore.wait()
2135 let reply: (Error?) -> Void = {
2136 os_log("departByDistrustingSelf complete: %@", log: tplogTrace, type: .info, traceError($0))
2137 self.semaphore.signal()
2141 self.moc.performAndWait {
2142 guard let egoPeerID = self.containerMO.egoPeerID else {
2143 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2144 reply(ContainerError.noPreparedIdentity)
2148 self.onqueueDistrust(peerIDs: [egoPeerID], reply: reply)
2152 func distrust(peerIDs: Set<String>,
2153 reply: @escaping (Error?) -> Void) {
2154 self.semaphore.wait()
2155 let reply: (Error?) -> Void = {
2156 os_log("distrust complete: %@", log: tplogTrace, type: .info, traceError($0))
2157 self.semaphore.signal()
2161 self.moc.performAndWait {
2162 guard let egoPeerID = self.containerMO.egoPeerID else {
2163 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2164 reply(ContainerError.noPreparedIdentity)
2168 guard !peerIDs.contains(egoPeerID) else {
2169 os_log("Self-distrust via peerID not allowed", log: tplogDebug, type: .default)
2170 reply(ContainerError.invalidPeerID)
2174 self.onqueueDistrust(peerIDs: peerIDs, reply: reply)
2178 func onqueueDistrust(peerIDs: Set<String>,
2179 reply: @escaping (Error?) -> Void) {
2181 guard let egoPeerID = self.containerMO.egoPeerID else {
2182 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2183 reply(ContainerError.noPreparedIdentity)
2187 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
2188 guard let signingKeyPair = signingKeyPair else {
2189 os_log("No longer have signing key pair; can't sign distrust: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2194 self.moc.performAndWait {
2195 let dynamicInfo: TPPeerDynamicInfo
2197 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
2199 removingPeerIDs: Array(peerIDs),
2200 preapprovedKeys: nil,
2201 signing: signingKeyPair,
2202 currentMachineIDs: self.onqueueCurrentMIDList())
2205 os_log("Error preparing dynamic info: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2210 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
2211 os_log("attempting distrust for %@ with: %@", log: tplogDebug, type: .default, peerIDs, dynamicInfo)
2213 let request = UpdateTrustRequest.with {
2214 $0.changeToken = self.containerMO.changeToken ?? ""
2215 $0.peerID = egoPeerID
2216 $0.dynamicInfoAndSig = signedDynamicInfo
2218 self.cuttlefish.updateTrust(request) { response, error in
2219 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2220 guard let response = response, error == nil else {
2221 os_log("updateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2222 reply(error ?? ContainerError.cloudkitResponseMissing)
2227 try self.persist(changes: response.changes)
2228 os_log("distrust succeeded", log: tplogDebug, type: .default)
2231 os_log("distrust handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
2239 func fetchEscrowContents(reply: @escaping (Data?, String?, Data?, Error?) -> Void) {
2240 self.semaphore.wait()
2241 let reply: (Data?, String?, Data?, Error?) -> Void = {
2242 os_log("fetchEscrowContents complete: %@", log: tplogTrace, type: .info, traceError($3))
2243 self.semaphore.signal()
2244 reply($0, $1, $2, $3)
2246 os_log("beginning a fetchEscrowContents", log: tplogDebug, type: .default)
2248 self.moc.performAndWait {
2249 guard let egoPeerID = self.containerMO.egoPeerID else {
2250 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2251 reply (nil, nil, nil, ContainerError.noPreparedIdentity)
2255 guard let bottles = self.containerMO.bottles as? Set<BottleMO> else {
2256 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2257 reply (nil, nil, nil, ContainerError.noBottleForPeer)
2261 var bmoSet = bottles.filter { $0.peerID == egoPeerID }
2262 let bmo = bmoSet.removeFirst()
2263 let bottleID = bmo.bottleID
2267 guard let loaded = try loadSecret(label: egoPeerID) else {
2268 os_log("fetchEscrowContents failed to load entropy", log: tplogDebug, type: .default)
2269 reply (nil, nil, nil, ContainerError.failedToFetchEscrowContents)
2274 os_log("fetchEscrowContents failed to load entropy: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2275 reply (nil, nil, nil, error)
2279 guard let signingPublicKey = bmo.escrowedSigningSPKI else {
2280 os_log("fetchEscrowContents no escrow signing spki", log: tplogDebug, type: .default)
2281 reply (nil, nil, nil, ContainerError.bottleDoesNotContainerEscrowKeySPKI)
2284 reply(entropy, bottleID, signingPublicKey, nil)
2288 func fetchViableBottles(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2289 self.semaphore.wait()
2290 let reply: ([String]?, [String]?, Error?) -> Void = {
2291 os_log("fetchViableBottles complete: %@", log: tplogTrace, type: .info, traceError($2))
2292 self.semaphore.signal()
2296 self.fetchViableBottlesWithSemaphore(reply: reply)
2299 func onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: TPCachedViableBottles) -> Bool {
2300 guard let egoPeerID = self.containerMO.egoPeerID else {
2301 os_log("bottleForEgoPeer: No identity.", log: tplogDebug, type: .default)
2304 guard let bottles: Set<BottleMO> = self.containerMO.bottles as? Set<BottleMO> else {
2305 os_log("bottleForEgoPeer: No Bottles.", log: tplogDebug, type: .default)
2308 var matchesCached: Bool = false
2309 for bottle in bottles {
2310 guard let bottleID: String = bottle.bottleID else {
2313 if bottle.peerID == egoPeerID && (cachedBottles.viableBottles.contains(bottleID) || cachedBottles.partialBottles.contains(bottleID)) {
2314 matchesCached = true
2318 return matchesCached
2321 func fetchViableBottlesWithSemaphore(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2322 os_log("beginning a fetchViableBottles", log: tplogDebug, type: .default)
2324 let cachedBottles:TPCachedViableBottles = self.model.currentCachedViableBottlesSet()
2325 self.moc.performAndWait {
2326 if self.onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: cachedBottles)
2327 && (cachedBottles.viableBottles.count > 0 || cachedBottles.partialBottles.count > 0) {
2328 os_log("returning from fetchViableBottles, using cached bottles", log: tplogDebug, type: .default)
2329 reply (cachedBottles.viableBottles, cachedBottles.partialBottles, nil)
2333 self.cuttlefish.fetchViableBottles { response, error in
2334 guard error == nil else {
2335 os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2336 reply(nil, nil, error)
2340 self.moc.performAndWait {
2342 guard let escrowPairs = response?.viableBottles else {
2343 os_log("fetchViableBottles returned no viable bottles: %@", log: tplogDebug, type: .default)
2348 var partialPairs: [EscrowPair] = []
2349 if let partial = response?.partialBottles {
2350 partialPairs = partial
2352 os_log("fetchViableBottles returned no partially viable bottles, but that's ok: %@", log: tplogDebug, type: .default)
2355 let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID }
2356 os_log("fetchViableBottles returned viable bottles: %@", log: tplogDebug, type: .default, viableBottleIDs)
2358 let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID }
2359 os_log("fetchViableBottles returned partial bottles: %@", log: tplogDebug, type: .default, partialBottleIDs)
2361 escrowPairs.forEach { pair in
2362 let bottle = pair.bottle
2364 // Save this bottle only if we don't already have it
2365 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2366 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2367 existing.peerID == bottle.peerID &&
2368 existing.bottleID == bottle.bottleID &&
2369 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2370 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2371 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2372 existing.contents == bottle.contents
2374 if !matchingBottles.isEmpty {
2375 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2380 let bmo = BottleMO(context: self.moc)
2381 bmo.peerID = bottle.peerID
2382 bmo.bottleID = bottle.bottleID
2383 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2384 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2385 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2386 bmo.contents = bottle.contents
2388 os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo)
2389 self.containerMO.addToBottles(bmo)
2392 partialPairs.forEach { pair in
2393 let bottle = pair.bottle
2395 // Save this bottle only if we don't already have it
2396 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2397 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2398 existing.peerID == bottle.peerID &&
2399 existing.bottleID == bottle.bottleID &&
2400 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2401 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2402 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2403 existing.contents == bottle.contents
2405 if !matchingBottles.isEmpty {
2406 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2411 let bmo = BottleMO(context: self.moc)
2412 bmo.peerID = bottle.peerID
2413 bmo.bottleID = bottle.bottleID
2414 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2415 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2416 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2417 bmo.contents = bottle.contents
2419 os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo)
2420 self.containerMO.addToBottles(bmo)
2425 os_log("fetchViableBottles saved bottles", log: tplogDebug, type: .default)
2426 let cached = TPCachedViableBottles.init(viableBottles: viableBottleIDs, partialBottles: partialBottleIDs)
2427 self.model.setViableBottles(cached)
2428 reply(viableBottleIDs, partialBottleIDs, nil)
2430 os_log("fetchViableBottles unable to save bottles: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2431 reply(nil, nil, error)
2439 func fetchPolicy(reply: @escaping (TPPolicy?, Error?) -> Void) {
2440 self.semaphore.wait()
2441 let reply: (TPPolicy?, Error?) -> Void = {
2442 os_log("fetchPolicy complete: %@", log: tplogTrace, type: .info, traceError($1))
2443 self.semaphore.signal()
2447 self.moc.performAndWait {
2448 var keys: [NSNumber: String] = [:]
2450 guard let stableInfoData = self.containerMO.egoPeerStableInfo,
2451 let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2452 os_log("fetchPolicy failed to find ego peer stableinfodata/sig", log: tplogDebug, type: .error)
2453 reply(nil, ContainerError.noPreparedIdentity)
2456 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
2457 os_log("fetchPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
2458 reply(nil, ContainerError.invalidStableInfoOrSig)
2462 let policyVersionCounter = stableInfo.policyVersion
2463 let policyVersion = NSNumber(value: policyVersionCounter)
2464 keys[policyVersion] = stableInfo.policyHash
2466 if let policyDocument = self.model.policy(withVersion: policyVersionCounter) {
2467 os_log("fetchPolicy: have a local version of policy %@: %@", log: tplogDebug, type: .default, policyVersion, policyDocument)
2469 let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2473 os_log("TPPolicyDocument failed: %@", log: tplogDebug, type: .default, error as CVarArg)
2479 self.fetchPolicyDocuments(keys: keys) { result, error in
2480 guard error == nil else {
2484 guard let result = result else {
2485 os_log("fetchPolicy: nil policies returned")
2486 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2489 guard result.count == 1 else {
2490 os_log("fetchPolicy: wrong length returned")
2491 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2494 guard let r = result[policyVersion] else {
2495 os_log("fetchPolicy: version not found")
2496 reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value))
2499 guard let data = r[1].data(using: .utf8) else {
2500 os_log("fetchPolicy: failed to convert data")
2501 reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value))
2504 guard let pd = TPPolicyDocument.policyDoc(withHash: r[0], data: data) else {
2505 os_log("fetchPolicy: pd is nil")
2506 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2510 let policy = try pd.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2513 os_log("TPPolicyDocument: %@", log: tplogDebug, type: .default, error as CVarArg)
2520 // All-or-nothing: return an error in case full list cannot be returned.
2521 // Completion handler data format: [version : [hash, data]]
2522 func fetchPolicyDocuments(keys: [NSNumber: String],
2523 reply: @escaping ([NSNumber: [String]]?, Error?) -> Void) {
2524 self.semaphore.wait()
2525 let reply: ([NSNumber: [String]]?, Error?) -> Void = {
2526 os_log("fetchPolicyDocuments complete: %@", log: tplogTrace, type: .info, traceError($1))
2527 self.semaphore.signal()
2532 var docs: [NSNumber: [String]] = [:]
2534 self.moc.performAndWait {
2535 for (version, hash) in keys {
2536 if let policydoc = try? self.getPolicyDoc(version.uint64Value), policydoc.policyHash == hash {
2537 docs[version] = [policydoc.policyHash, policydoc.protobuf.base64EncodedString()]
2548 let request = FetchPolicyDocumentsRequest.with {
2549 $0.keys = keys.map { key, value in
2550 PolicyDocumentKey.with { $0.version = key.uint64Value; $0.hash = value }}
2553 self.cuttlefish.fetchPolicyDocuments(request) { response, error in
2554 os_log("FetchPolicyDocuments(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2555 guard let response = response, error == nil else {
2556 os_log("FetchPolicyDocuments failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2557 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
2561 self.moc.performAndWait {
2562 for mapEntry in response.entries {
2563 // TODO: validate the policy's signature
2565 guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else {
2566 os_log("Can't make policy document with hash %@ and data %@",
2567 log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString())
2568 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2572 guard let hash = keys[NSNumber(value: doc.policyVersion)], hash == doc.policyHash else {
2573 os_log("Requested hash %@ does not match fetched hash %@", log: tplogDebug, type: .default,
2574 keys[NSNumber(value: doc.policyVersion)] ?? "<nil>", doc.policyHash)
2575 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2578 keys[NSNumber(value: doc.policyVersion)] = nil // Server responses should be unique, let's enforce
2579 docs[NSNumber(value: doc.policyVersion)] = [doc.policyHash, doc.protobuf.base64EncodedString()]
2580 self.model.register(doc)
2584 try self.moc.save() // if this fails callers might make bad data assumptions
2591 let (unknownVersion, _) = keys.first!
2592 reply(nil, ContainerError.unknownPolicyVersion(unknownVersion.uint64Value))
2601 // Must be on moc queue to call this.
2602 // Caller is responsible for saving the moc afterwards.
2604 private func registerPeerMO(permanentInfo: TPPeerPermanentInfo,
2605 stableInfo: TPPeerStableInfo? = nil,
2606 dynamicInfo: TPPeerDynamicInfo? = nil,
2607 vouchers: [TPVoucher]? = nil,
2608 isEgoPeer: Bool = false) throws -> PeerMO {
2609 let peerID = permanentInfo.peerID
2611 let peer = PeerMO(context: self.moc)
2612 peer.peerID = peerID
2613 peer.permanentInfo = permanentInfo.data
2614 peer.permanentInfoSig = permanentInfo.sig
2615 peer.stableInfo = stableInfo?.data
2616 peer.stableInfoSig = stableInfo?.sig
2617 peer.dynamicInfo = dynamicInfo?.data
2618 peer.dynamicInfoSig = dynamicInfo?.sig
2619 peer.isEgoPeer = isEgoPeer
2620 self.containerMO.addToPeers(peer)
2622 self.model.registerPeer(with: permanentInfo)
2623 if let stableInfo = stableInfo {
2624 try self.model.update(stableInfo, forPeerWithID: peerID)
2626 if let dynamicInfo = dynamicInfo {
2627 try self.model.update(dynamicInfo, forPeerWithID: peerID)
2629 vouchers?.forEach { voucher in
2630 self.model.register(voucher)
2631 let voucherMO = VoucherMO(context: self.moc)
2632 voucherMO.voucherInfo = voucher.data
2633 voucherMO.voucherInfoSig = voucher.sig
2634 peer.addToVouchers(voucherMO)
2639 /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */
2640 func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet],
2641 tlkShares: [CKKSTLKShare],
2642 egoPeerKeys: OctagonSelfPeerKeys,
2643 egoPeerDynamicInfo: TPPeerDynamicInfo,
2644 epoch: Int) throws -> ([ViewKeys], [TLKShare]) {
2645 let newCKKSKeys = ckksKeys.filter { $0.newUpload }
2646 let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert)
2648 let octagonSelfShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
2649 asPeer: egoPeerKeys,
2650 toPeer: egoPeerKeys,
2652 let extraShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
2654 var peerShares: [TLKShare] = []
2656 for keyset in newCKKSKeys {
2658 let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo,
2659 toAccessView: keyset.tlk.zoneID.zoneName)
2660 os_log("Planning to share %@ with peers %@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess)
2662 let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) }
2663 let viewPeerShares = try peers.map { receivingPeer in
2664 TLKShare.convert(ckksTLKShare: try CKKSTLKShare(keyset.tlk,
2666 to: receivingPeer.permanentInfo,
2671 peerShares = peerShares + viewPeerShares
2674 os_log("Unable to create TLKShares for keyset %@: %@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg)
2678 return (newViewKeys, octagonSelfShares + peerShares + extraShares)
2681 func onqueuePreparePeerForJoining(egoPeerID: String,
2682 peerPermanentInfo: TPPeerPermanentInfo,
2683 stableInfo: TPPeerStableInfo,
2685 preapprovedKeys: [Data]?,
2686 vouchers: [SignedVoucher],
2687 egoPeerKeys: OctagonSelfPeerKeys) throws -> (Peer, TPPeerDynamicInfo) {
2688 let dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
2689 peerPermanentInfo: peerPermanentInfo,
2690 peerStableInfo: stableInfo,
2691 sponsorID: sponsorID,
2692 preapprovedKeys: preapprovedKeys,
2693 signing: egoPeerKeys.signingKey,
2694 currentMachineIDs: self.onqueueCurrentMIDList())
2696 let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: nil,
2697 egoPeerID: egoPeerID,
2698 dynamicInfo: dynamicInfo,
2699 signingKeyPair: egoPeerKeys.signingKey)
2701 let peer = Peer.with {
2702 $0.peerID = egoPeerID
2703 $0.permanentInfoAndSig = SignedPeerPermanentInfo(peerPermanentInfo)
2704 $0.stableInfoAndSig = SignedPeerStableInfo(newStableInfo ?? stableInfo)
2705 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
2706 $0.vouchers = vouchers
2709 return (peer, dynamicInfo)
2712 func join(voucherData: Data,
2714 ckksKeys: [CKKSKeychainBackedKeySet],
2715 tlkShares: [CKKSTLKShare],
2716 preapprovedKeys: [Data]?,
2717 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
2718 self.semaphore.wait()
2719 let reply: (String?, [CKRecord], Error?) -> Void = {
2720 os_log("join complete: %@", log: tplogTrace, type: .info, traceError($2))
2721 self.semaphore.signal()
2725 self.fetchAndPersistChanges { error in
2726 guard error == nil else {
2727 reply(nil, [], error)
2730 self.moc.performAndWait {
2731 guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else {
2732 reply(nil, [], ContainerError.invalidVoucherOrSig)
2735 guard let sponsor = self.model.peer(withID: voucher.sponsorID) else {
2736 reply(nil, [], ContainerError.sponsorNotRegistered(voucher.sponsorID))
2740 // Fetch ego peer identity from local storage.
2741 guard let egoPeerID = self.containerMO.egoPeerID,
2742 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2743 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2744 let egoStableData = self.containerMO.egoPeerStableInfo,
2745 let egoStableSig = self.containerMO.egoPeerStableInfoSig
2747 reply(nil, [], ContainerError.noPreparedIdentity)
2751 let keyFactory = TPECPublicKeyFactory()
2752 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2753 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
2756 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
2757 reply(nil, [], ContainerError.invalidStableInfoOrSig)
2760 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
2761 os_log("join: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
2762 self.onqueueTTRUntrusted()
2763 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
2767 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2768 guard let egoPeerKeys = egoPeerKeys else {
2769 os_log("Don't have my own peer keys; can't join: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
2770 reply(nil, [], error)
2773 self.moc.performAndWait {
2775 let newDynamicInfo: TPPeerDynamicInfo
2777 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
2778 peerPermanentInfo: selfPermanentInfo,
2779 stableInfo: selfStableInfo,
2780 sponsorID: sponsor.peerID,
2781 preapprovedKeys: preapprovedKeys,
2782 vouchers: [SignedVoucher.with {
2783 $0.voucher = voucher.data
2784 $0.sig = voucher.sig
2786 egoPeerKeys: egoPeerKeys)
2788 os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg)
2789 reply(nil, [], error)
2793 let allTLKShares: [TLKShare]
2794 let viewKeys: [ViewKeys]
2796 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
2797 tlkShares: tlkShares,
2798 egoPeerKeys: egoPeerKeys,
2799 egoPeerDynamicInfo: newDynamicInfo,
2800 epoch: Int(selfPermanentInfo.epoch))
2802 os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg)
2803 reply(nil, [], error)
2808 try self.model.checkIntroduction(forCandidate: selfPermanentInfo,
2809 stableInfo: peer.stableInfoAndSig.toStableInfo(),
2810 withSponsorID: sponsor.peerID)
2812 os_log("Error checking introduction: %@", log: tplogDebug, type: .default, error as CVarArg)
2813 reply(nil, [], error)
2819 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
2821 reply(nil, [], error)
2825 os_log("Beginning join for peer %@", log: tplogDebug, type: .default, egoPeerID)
2826 os_log("Join permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
2827 os_log("Join permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
2828 os_log("Join stableInfo: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString())
2829 os_log("Join stableInfoSig: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString())
2830 os_log("Join dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
2831 os_log("Join dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
2833 os_log("Join vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
2834 os_log("Join voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
2836 os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
2839 os_log("Join peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
2841 os_log("Join unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
2844 let changeToken = self.containerMO.changeToken ?? ""
2845 let request = JoinWithVoucherRequest.with {
2846 $0.changeToken = changeToken
2849 $0.tlkShares = allTLKShares
2850 $0.viewKeys = viewKeys
2852 self.cuttlefish.joinWithVoucher(request) { response, error in
2853 os_log("JoinWithVoucher(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2854 guard let response = response, error == nil else {
2855 os_log("joinWithVoucher failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2856 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
2860 self.moc.performAndWait {
2862 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
2863 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
2864 try self.onQueuePersist(changes: response.changes)
2865 os_log("JoinWithVoucher succeeded", log: tplogDebug)
2867 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
2868 reply(egoPeerID, keyHierarchyRecords, nil)
2870 os_log("JoinWithVoucher failed: %@", log: tplogDebug, String(describing: error))
2871 reply(nil, [], error)
2881 func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Error?) -> Void) {
2882 self.semaphore.wait()
2883 let reply: (Bool, Bool, Bool, Error?) -> Void = {
2884 os_log("health check complete: %@", log: tplogTrace, type: .info, traceError($3))
2885 self.semaphore.signal()
2886 reply($0, $1, $2, $3)
2889 os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck)
2891 self.moc.performAndWait {
2892 guard let egoPeerID = self.containerMO.egoPeerID else {
2893 // No identity, nothing to do
2894 os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default)
2895 reply(false, false, false, ContainerError.noPreparedIdentity)
2898 let request = GetRepairActionRequest.with {
2899 $0.peerID = egoPeerID
2900 $0.requiresEscrowCheck = requiresEscrowCheck
2903 self.cuttlefish.getRepairAction(request) { response, error in
2904 guard error == nil else {
2905 reply(false, false, false, error)
2908 guard let action = response?.repairAction else {
2909 os_log("repair response is empty, returning false: %@", log: tplogDebug, type: .default)
2910 reply(false, false, false, nil)
2913 var postRepairAccount: Bool = false
2914 var postRepairEscrow: Bool = false
2915 var resetOctagon: Bool = false
2920 case .postRepairAccount:
2921 postRepairAccount = true
2923 case .postRepairEscrow:
2924 postRepairEscrow = true
2932 reply(postRepairAccount, postRepairEscrow, resetOctagon, nil)
2937 func preflightPreapprovedJoin(reply: @escaping (Bool, Error?) -> Void) {
2938 self.semaphore.wait()
2939 let reply: (Bool, Error?) -> Void = {
2940 os_log("preflightPreapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($1))
2941 self.semaphore.signal()
2945 self.fetchAndPersistChanges { error in
2946 guard error == nil else {
2947 os_log("preflightPreapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
2952 // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us?
2954 guard !self.model.allPeerIDs().isEmpty else {
2955 // If, after fetch and handle changes, there's no peers, then we can likely establish.
2960 guard let egoPeerID = self.containerMO.egoPeerID,
2961 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2962 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
2964 os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
2965 reply(false, ContainerError.noPreparedIdentity)
2969 let keyFactory = TPECPublicKeyFactory()
2970 guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2971 os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug)
2972 reply(false, ContainerError.invalidPermanentInfoOrSig)
2976 guard self.model.hasPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else {
2977 os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
2978 reply(false, ContainerError.noPeersPreapprovePreparedIdentity)
2986 func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet],
2987 tlkShares: [CKKSTLKShare],
2988 preapprovedKeys: [Data]?,
2989 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
2990 self.semaphore.wait()
2991 let reply: (String?, [CKRecord], Error?) -> Void = {
2992 os_log("preapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($2))
2993 self.semaphore.signal()
2997 self.fetchAndPersistChangesIfNeeded { error in
2998 guard error == nil else {
2999 os_log("preapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3000 reply(nil, [], error)
3003 self.moc.performAndWait {
3004 // If, after fetch and handle changes, there's no peers, then fire off an establish
3005 // Note that if the establish fails, retrying this call might work.
3006 // That's up to the caller.
3007 if self.model.allPeerIDs().isEmpty {
3008 os_log("preapprovedJoin but no existing peers, attempting establish", log: tplogDebug, type: .debug)
3009 self.onqueueEstablish(ckksKeys: ckksKeys,
3010 tlkShares: tlkShares,
3011 preapprovedKeys: preapprovedKeys,
3016 // Fetch ego peer identity from local storage.
3017 guard let egoPeerID = self.containerMO.egoPeerID,
3018 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3019 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3020 let egoStableData = self.containerMO.egoPeerStableInfo,
3021 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3023 os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3024 reply(nil, [], ContainerError.noPreparedIdentity)
3028 let keyFactory = TPECPublicKeyFactory()
3029 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3030 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
3033 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3034 reply(nil, [], ContainerError.invalidStableInfoOrSig)
3038 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3039 os_log("preapprovedJoin: self machineID %@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3040 self.onqueueTTRUntrusted()
3041 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3044 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3045 guard let egoPeerKeys = egoPeerKeys else {
3046 os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3047 reply(nil, [], error)
3051 guard self.model.hasPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else {
3052 os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3053 reply(nil, [], ContainerError.noPeersPreapprovePreparedIdentity)
3057 self.moc.performAndWait {
3060 let newDynamicInfo: TPPeerDynamicInfo
3062 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3063 peerPermanentInfo: selfPermanentInfo,
3064 stableInfo: selfStableInfo,
3066 preapprovedKeys: preapprovedKeys,
3068 egoPeerKeys: egoPeerKeys)
3070 os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg)
3071 reply(nil, [], error)
3075 let allTLKShares: [TLKShare]
3076 let viewKeys: [ViewKeys]
3078 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3079 tlkShares: tlkShares,
3080 egoPeerKeys: egoPeerKeys,
3081 egoPeerDynamicInfo: newDynamicInfo,
3082 epoch: Int(selfPermanentInfo.epoch))
3084 os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg)
3085 reply(nil, [], error)
3091 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3093 reply(nil, [], error)
3097 os_log("Beginning preapprovedJoin for peer %@", log: tplogDebug, type: .default, egoPeerID)
3098 os_log("preapprovedJoin permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3099 os_log("preapprovedJoin permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3100 os_log("preapprovedJoin stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
3101 os_log("preapprovedJoin stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
3102 os_log("preapprovedJoin dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3103 os_log("preapprovedJoin dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3105 os_log("preapprovedJoin vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3106 os_log("preapprovedJoin voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3108 os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3111 os_log("preapprovedJoin peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3113 os_log("preapprovedJoin unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
3116 let changeToken = self.containerMO.changeToken ?? ""
3117 let request = JoinWithVoucherRequest.with {
3118 $0.changeToken = changeToken
3121 $0.tlkShares = allTLKShares
3122 $0.viewKeys = viewKeys
3124 self.cuttlefish.joinWithVoucher(request) { response, error in
3125 os_log("preapprovedJoin(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3126 guard let response = response, error == nil else {
3127 os_log("preapprovedJoin failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3128 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
3132 self.moc.performAndWait {
3134 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3135 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3136 try self.onQueuePersist(changes: response.changes)
3137 os_log("preapprovedJoin succeeded", log: tplogDebug)
3139 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3140 reply(egoPeerID, keyHierarchyRecords, nil)
3142 os_log("preapprovedJoin failed: %@", log: tplogDebug, String(describing: error))
3143 reply(nil, [], error)
3153 func update(deviceName: String?,
3154 serialNumber: String?,
3156 policyVersion: UInt64?,
3157 policySecrets: [String: Data]?,
3158 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3159 self.semaphore.wait()
3160 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3161 os_log("update complete: %@", log: tplogTrace, type: .info, traceError($1))
3162 self.semaphore.signal()
3166 // Get (and save) the latest from cuttlefish
3167 let stableChanges = StableChanges(deviceName: deviceName,
3168 serialNumber: serialNumber,
3169 osVersion: osVersion,
3170 policyVersion: policyVersion,
3171 policySecrets: policySecrets,
3172 recoverySigningPubKey: nil,
3173 recoveryEncryptionPubKey: nil)
3174 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply)
3177 func set(preapprovedKeys: [Data],
3178 reply: @escaping (Error?) -> Void) {
3179 self.semaphore.wait()
3180 let reply: (Error?) -> Void = {
3181 os_log("setPreapprovedKeys complete: %@", log: tplogTrace, type: .info, traceError($0))
3182 self.semaphore.signal()
3186 self.moc.performAndWait {
3187 os_log("setPreapprovedKeys: %@", log: tplogDebug, type: .default, preapprovedKeys)
3189 guard let egoPeerID = self.containerMO.egoPeerID else {
3190 // No identity, nothing to do
3191 os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default)
3192 reply(ContainerError.noPreparedIdentity)
3195 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3196 guard let signingKeyPair = signingKeyPair else {
3197 os_log("setPreapprovedKeys: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3198 reply(error ?? ContainerError.unableToCreateKeyPair)
3202 self.moc.performAndWait {
3203 let dynamicInfo: TPPeerDynamicInfo
3205 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3207 removingPeerIDs: nil,
3208 preapprovedKeys: preapprovedKeys,
3209 signing: signingKeyPair,
3210 currentMachineIDs: self.onqueueCurrentMIDList())
3212 os_log("setPreapprovedKeys: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg)
3217 os_log("setPreapprovedKeys: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo)
3219 if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo {
3220 os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default)
3225 os_log("setPreapprovedKeys: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3226 let request = UpdateTrustRequest.with {
3227 $0.changeToken = self.containerMO.changeToken ?? ""
3228 $0.peerID = egoPeerID
3229 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3232 self.cuttlefish.updateTrust(request) { response, error in
3233 os_log("setPreapprovedKeys(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3234 guard let response = response, error == nil else {
3235 os_log("setPreapprovedKeys failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3236 reply(error ?? ContainerError.cloudkitResponseMissing)
3239 os_log("setPreapprovedKeys: updateTrust suceeded", log: tplogDebug, type: .default)
3242 try self.persist(changes: response.changes)
3244 os_log("setPreapprovedKeys: could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg)
3256 func updateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3257 tlkShares: [CKKSTLKShare],
3258 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3259 self.semaphore.wait()
3260 let reply: ([CKRecord]?, Error?) -> Void = {
3261 os_log("updateTLKs complete: %@", log: tplogTrace, type: .info, traceError($1))
3262 self.semaphore.signal()
3266 os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys)
3268 self.moc.performAndWait {
3269 guard let egoPeerID = self.containerMO.egoPeerID,
3270 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3271 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3273 os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default)
3274 reply(nil, ContainerError.noPreparedIdentity)
3278 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else {
3279 os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys)
3280 reply(nil, ContainerError.invalidPermanentInfoOrSig)
3284 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3285 guard let egoPeerKeys = egoPeerKeys else {
3286 os_log("Don't have my own peer keys; can't upload new TLKs: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3290 self.moc.performAndWait {
3291 guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else {
3292 os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default)
3293 reply(nil, ContainerError.missingDynamicInfo)
3297 let allTLKShares: [TLKShare]
3298 let viewKeys: [ViewKeys]
3300 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3301 tlkShares: tlkShares,
3302 egoPeerKeys: egoPeerKeys,
3303 egoPeerDynamicInfo: egoPeerDynamicInfo,
3304 epoch: Int(selfPermanentInfo.epoch))
3306 os_log("Unable to process keys before uploading: %@", log: tplogDebug, type: .default, error as CVarArg)
3311 let request = UpdateTrustRequest.with {
3312 $0.changeToken = self.containerMO.changeToken ?? ""
3313 $0.peerID = egoPeerID
3314 $0.tlkShares = allTLKShares
3315 $0.viewKeys = viewKeys
3318 self.cuttlefish.updateTrust(request) { response, error in
3319 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3321 guard error == nil else {
3326 let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? []
3327 os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count)
3328 reply(keyHierarchyRecords, nil)
3335 func getState(reply: @escaping (ContainerState) -> Void) {
3336 self.semaphore.wait()
3337 let reply: (ContainerState) -> Void = {
3338 os_log("getState complete: %@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
3339 self.semaphore.signal()
3343 self.moc.performAndWait {
3344 var state = ContainerState()
3345 state.egoPeerID = self.containerMO.egoPeerID
3347 if self.containerMO.bottles != nil {
3348 self.containerMO.bottles!.forEach { bottle in
3349 state.bottles.insert(bottle as! BottleMO)
3353 self.model.allPeers().forEach { peer in
3354 state.peers[peer.peerID] = peer
3356 state.vouchers = Array(self.model.allVouchers())
3361 // This will only fetch changes if no changes have ever been fetched before
3362 private func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) {
3363 self.moc.performAndWait {
3364 if self.containerMO.changeToken == nil {
3365 self.onqueueFetchAndPersistChanges(reply: reply)
3372 private func fetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3373 self.moc.performAndWait {
3374 self.onqueueFetchAndPersistChanges(reply: reply)
3378 private func onqueueFetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3379 let request = FetchChangesRequest.with {
3380 $0.changeToken = self.containerMO.changeToken ?? ""
3382 os_log("Fetching with change token: %@", log: tplogDebug, type: .default, request.changeToken.count > 0 ? request.changeToken : "empty")
3384 self.cuttlefish.fetchChanges(request) { response, error in
3385 os_log("FetchChanges(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3386 guard let response = response, error == nil else {
3388 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired):
3389 os_log("change token is expired; resetting local CK storage", log: tplogDebug, type: .default)
3391 self.moc.performAndWait {
3393 try self.deleteLocalCloudKitData()
3395 os_log("Failed to reset local data: %@", log: tplogDebug, type: .default, error as CVarArg)
3400 self.fetchAndPersistChanges(reply: reply)
3405 os_log("Fetch error is an unknown error: %@", log: tplogDebug, type: .default, String(describing: error))
3408 os_log("Could not fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3414 try self.persist(changes: response.changes)
3416 os_log("Could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg)
3421 if response.changes.more {
3422 os_log("persist: More changes indicated. Fetching...", log: tplogDebug, type: .default)
3423 self.fetchAndPersistChanges(reply: reply)
3426 os_log("persist: no more changes!", log: tplogDebug, type: .default)
3433 // Check for delta update in trust lists, that should lead to update of TLKShares
3435 private func haveTrustMemberChanges(newDynamicInfo: TPPeerDynamicInfo, oldDynamicInfo: TPPeerDynamicInfo?) -> Bool {
3436 guard let oldDynamicInfo = oldDynamicInfo else {
3439 if (newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs) {
3442 if (newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs) {
3445 if (newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals) {
3451 // Fetch and persist changes from Cuttlefish. If this results
3452 // in us calculating a new dynamicInfo then give the new dynamicInfo
3453 // to Cuttlefish. If Cuttlefish returns more changes then persist
3454 // them locally, update dynamicInfo if needed, and keep doing that
3455 // until dynamicInfo is unchanged and there are no more changes to fetch.
3457 // Must be holding the semaphore to call this, and it remains
3458 // the caller's responsibility to release it after it completes
3459 // (i.e. after reply is invoked).
3460 internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil,
3461 changesPending: Bool = false,
3462 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3463 self.fetchAndPersistChanges { error in
3464 if let error = error {
3465 os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %@", log: tplogDebug, type: .default, error as CVarArg)
3469 self.updateTrustIfNeeded(stableChanges: stableChanges, changesPending: changesPending, reply: reply)
3473 // If this results in us calculating a new dynamicInfo then,
3474 // upload the new dynamicInfo
3475 // to Cuttlefish. If Cuttlefish returns more changes then persist
3476 // them locally, update dynamicInfo if needed, and keep doing that
3477 // until dynamicInfo is unchanged and there are no more changes to fetch.
3479 // Must be holding the semaphore to call this, and it remains
3480 // the caller's responsibility to release it after it completes
3481 // (i.e. after reply is invoked).
3482 private func updateTrustIfNeeded(stableChanges: StableChanges? = nil,
3483 changesPending: Bool = false,
3484 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3485 self.moc.performAndWait {
3486 guard let egoPeerID = self.containerMO.egoPeerID else {
3487 // No identity, nothing to do
3488 os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default)
3489 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false), nil)
3492 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3493 guard let signingKeyPair = signingKeyPair else {
3494 os_log("updateTrustIfNeeded: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3495 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false), error)
3498 guard self.model.hasPeer(withID: egoPeerID) else {
3499 // Not in circle, nothing to do
3500 let isPreapproved = self.model.hasPeerPreapprovingKey(signingKeyPair.publicKey().spki())
3501 os_log("updateTrustIfNeeded: ego peer is not in model, is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
3502 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3503 isPreapproved: isPreapproved,
3505 memberChanges: changesPending,
3506 unknownMachineIDs: false),
3510 self.moc.performAndWait {
3511 let dynamicInfo: TPPeerDynamicInfo
3512 var stableInfo: TPPeerStableInfo?
3514 // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair,
3515 // and then only load the key if it has changed and we need to sign a new one. This would also
3516 // help make our detection of change immune from non-canonical serialization of dynamicInfo.
3517 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3519 removingPeerIDs: nil,
3520 preapprovedKeys: nil,
3521 signing: signingKeyPair,
3522 currentMachineIDs: self.onqueueCurrentMIDList())
3524 stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges,
3525 egoPeerID: egoPeerID,
3526 dynamicInfo: dynamicInfo,
3527 signingKeyPair: signingKeyPair)
3529 os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg)
3530 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3531 isPreapproved: false,
3532 status: self.model.statusOfPeer(withID: egoPeerID),
3533 memberChanges: changesPending,
3534 unknownMachineIDs: false),
3539 os_log("updateTrustIfNeeded: produced a stableInfo: %@", log: tplogDebug, type: .default, String(describing: stableInfo))
3540 os_log("updateTrustIfNeeded: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo)
3542 let peer = self.model.peer(withID: egoPeerID)
3543 if (stableInfo == nil || stableInfo == peer?.stableInfo) &&
3544 dynamicInfo == peer?.dynamicInfo {
3545 os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default)
3546 // No change to the dynamicInfo: update the MID list now that we've reached a steady state
3548 self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo)
3551 os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %@", log: tplogDebug, type: .default, error as CVarArg)
3554 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3555 isPreapproved: false,
3556 status: self.model.statusOfPeer(withID: egoPeerID),
3557 memberChanges: changesPending,
3558 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful()),
3562 // Check if we change that should trigger a notification that should trigger TLKShare updates
3563 let haveChanges = changesPending || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo)
3565 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
3566 os_log("updateTrustIfNeeded: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3567 var request = UpdateTrustRequest.with {
3568 $0.changeToken = self.containerMO.changeToken ?? ""
3569 $0.peerID = egoPeerID
3570 $0.dynamicInfoAndSig = signedDynamicInfo
3572 if let stableInfo = stableInfo {
3573 request.stableInfoAndSig = SignedPeerStableInfo(stableInfo)
3575 self.cuttlefish.updateTrust(request) { response, error in
3576 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3577 guard let response = response, error == nil else {
3578 os_log("UpdateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3579 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3584 try self.persist(changes: response.changes)
3586 os_log("updateTrust failed: %@", log: tplogDebug, String(describing: error))
3591 if response.changes.more {
3592 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges,
3593 changesPending: haveChanges,
3596 self.updateTrustIfNeeded(stableChanges: stableChanges,
3597 changesPending: haveChanges,
3607 private func persist(changes: Changes) throws {
3608 // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait.
3609 // So, do it ourself
3610 var outsideBlockError: Error?
3612 self.moc.performAndWait {
3613 os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default,
3614 changes.differences.count,
3616 os_log("persist: New change token: %@", log: tplogDebug, type: .default, changes.changeToken)
3619 try self.onQueuePersist(changes: changes)
3621 outsideBlockError = error
3625 if let outsideBlockError = outsideBlockError {
3626 throw outsideBlockError
3630 // Must be on moc queue to call this.
3631 // Changes are registered in the model and stored in moc.
3632 private func onQueuePersist(changes: Changes) throws {
3633 self.containerMO.changeToken = changes.changeToken
3634 self.containerMO.moreChanges = changes.more
3636 if changes.differences.count > 0 {
3637 self.model.clearViableBottles()
3640 try changes.differences.forEach { peerDifference in
3641 if let operation = peerDifference.operation {
3643 case .add(let peer):
3644 try self.addOrUpdate(peer: peer)
3646 case .update(let peer):
3647 try self.addOrUpdate(peer: peer)
3648 // Update containerMO ego data if it has changed.
3649 if peer.peerID == self.containerMO.egoPeerID {
3650 guard let stableInfoAndSig:TPPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3653 self.containerMO.egoPeerStableInfo = stableInfoAndSig.data
3654 self.containerMO.egoPeerStableInfoSig = stableInfoAndSig.sig
3657 case .remove(let peer):
3658 self.model.deletePeer(withID: peer.peerID)
3659 if let peerMO = try self.fetchPeerMO(peerID: peer.peerID) {
3660 self.moc.delete(peerMO)
3666 let signingKey = changes.recoverySigningPubKey
3667 let encryptionKey = changes.recoveryEncryptionPubKey
3669 if signingKey.count > 0 && encryptionKey.count > 0 {
3670 self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey)
3675 // Must be on moc queue to call this
3676 // Deletes all things that should come back from CloudKit. Keeps the egoPeer data, though.
3677 private func deleteLocalCloudKitData() throws {
3678 os_log("Deleting all CloudKit data", log: tplogDebug, type: .default)
3681 let peerRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
3682 peerRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3683 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: peerRequest))
3685 let bottleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Bottle")
3686 bottleRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3687 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: bottleRequest))
3689 self.containerMO.peers = nil
3690 self.containerMO.bottles = nil
3691 self.containerMO.changeToken = nil
3692 self.containerMO.moreChanges = false
3694 self.model = Container.loadModel(from: self.containerMO)
3697 os_log("Local delete failed: %@", log: tplogDebug, type: .default, error as CVarArg)
3701 os_log("Saved model with %d peers", log: tplogDebug, type: .default, self.model.allPeerIDs().count)
3704 // Must be on moc queue to call this.
3705 private func addOrUpdate(signingKey: Data, encryptionKey: Data) {
3706 self.model.setRecoveryKeys(
3707 TPRecoveryKeyPair(signingSPKI: signingKey, encryptionSPKI: encryptionKey))
3710 // Must be on moc queue to call this.
3711 private func addOrUpdate(peer: Peer) throws {
3712 if !self.model.hasPeer(withID: peer.peerID) {
3714 guard let permanentInfo = peer.permanentInfoAndSig.toPermanentInfo(peerID: peer.peerID) else {
3715 // Ignoring bad peer
3718 let stableInfo = peer.stableInfoAndSig.toStableInfo()
3719 let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo()
3720 let vouchers = peer.vouchers.compactMap {
3721 TPVoucher(infoWith: $0.voucher, sig: $0.sig)
3723 let isEgoPeer = peer.peerID == self.containerMO.egoPeerID
3724 try self.registerPeerMO(permanentInfo: permanentInfo,
3725 stableInfo: stableInfo,
3726 dynamicInfo: dynamicInfo,
3728 isEgoPeer: isEgoPeer)
3731 // The assertion here is that every peer registered in model is also present in containerMO
3732 let peerMO = try self.fetchPeerMO(peerID: peer.peerID)!
3734 if let stableInfo = peer.stableInfoAndSig.toStableInfo() {
3735 try self.model.update(stableInfo, forPeerWithID: peer.peerID)
3736 // Pull the stableInfo back out of the model, and persist that.
3737 // The model checks signatures and clocks to prevent replay attacks.
3738 let modelPeer = self.model.peer(withID: peer.peerID)
3739 peerMO.stableInfo = modelPeer?.stableInfo?.data
3740 peerMO.stableInfoSig = modelPeer?.stableInfo?.sig
3742 if let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo() {
3743 try self.model.update(dynamicInfo, forPeerWithID: peer.peerID)
3744 // Pull the dynamicInfo back out of the model, and persist that.
3745 // The model checks signatures and clocks to prevent replay attacks.
3746 let modelPeer = self.model.peer(withID: peer.peerID)
3747 peerMO.dynamicInfo = modelPeer?.dynamicInfo?.data
3748 peerMO.dynamicInfoSig = modelPeer?.dynamicInfo?.sig
3750 peer.vouchers.forEach {
3751 if let voucher = TPVoucher(infoWith: $0.voucher, sig: $0.sig) {
3752 self.model.register(voucher)
3753 let voucherMO = VoucherMO(context: self.moc)
3754 voucherMO.voucherInfo = voucher.data
3755 voucherMO.voucherInfoSig = voucher.sig
3756 peerMO.addToVouchers(voucherMO)
3762 // Must be on moc queue to call this.
3763 private func fetchPeerMO(peerID: String) throws -> PeerMO? {
3764 let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
3765 fetch.predicate = NSPredicate(format: "peerID == %@ && container == %@", peerID, self.containerMO)
3766 let peers = try self.moc.fetch(fetch)
3767 return peers.first as? PeerMO
3770 // Must be on moc queue to call this.
3771 private func getPolicyDoc(_ policyVersion: UInt64) throws -> TPPolicyDocument {
3772 guard let policyDoc = self.model.policy(withVersion: policyVersion) else {
3773 throw ContainerError.unknownPolicyVersion(policyVersion)
3775 assert(policyVersion == policyDoc.policyVersion)
3776 if policyVersion == prevailingPolicyVersion {
3777 assert(policyDoc.policyHash == prevailingPolicyHash)
3782 // Must be on moc queue to call this.
3783 private func createNewStableInfoIfNeeded(stableChanges: StableChanges?,
3785 dynamicInfo: TPPeerDynamicInfo,
3786 signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? {
3787 func noChange<T: Equatable>(_ change: T?, _ existing: T?) -> Bool {
3788 return (nil == change) || change == existing
3790 let existingStableInfo = self.model.peer(withID: egoPeerID)?.stableInfo
3791 if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) &&
3792 noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) &&
3793 noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) &&
3794 noChange(stableChanges?.policyVersion, existingStableInfo?.policyVersion) &&
3795 noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) &&
3796 noChange(stableChanges?.recoverySigningPubKey, existingStableInfo?.recoverySigningPublicKey) &&
3797 noChange(stableChanges?.recoveryEncryptionPubKey, existingStableInfo?.recoveryEncryptionPublicKey) &&
3798 self.model.doesPeerRecoveryKeyMatchPeers(egoPeerID) {
3801 let policyHash: String?
3802 if let policyVersion = stableChanges?.policyVersion {
3803 let policyDoc = try self.getPolicyDoc(policyVersion)
3804 policyHash = policyDoc.policyHash
3809 // Determine which recovery key we'd like to be using, given our current idea of who to trust
3810 let newRecoveryKeys = self.model.bestRecoveryKey(with: dynamicInfo)
3812 return try self.model.createStableInfo(withPolicyVersion: stableChanges?.policyVersion ?? existingStableInfo?.policyVersion ?? prevailingPolicyVersion,
3813 policyHash: policyHash ?? existingStableInfo?.policyHash ?? prevailingPolicyHash,
3814 policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets,
3815 deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "",
3816 serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "",
3817 osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "",
3818 signing: signingKeyPair,
3819 recoverySigningPubKey: newRecoveryKeys?.signingSPKI ?? existingStableInfo?.recoverySigningPublicKey,
3820 recoveryEncryptionPubKey: newRecoveryKeys?.encryptionSPKI ?? existingStableInfo?.recoveryEncryptionPublicKey)
3823 private func assembleBottle(egoPeerID: String) throws -> Bottle {
3824 let bottleMoSet = containerMO.bottles as? Set<BottleMO>
3826 var bottleMOs = bottleMoSet?.filter {
3827 $0.peerID == egoPeerID
3830 if let count = bottleMOs?.count {
3832 throw ContainerError.tooManyBottlesForPeer
3833 } else if count == 0 {
3834 throw ContainerError.noBottleForPeer
3837 throw ContainerError.failedToAssembleBottle
3840 let BM: BottleMO? = (bottleMOs?.removeFirst())
3842 let bottle = try Bottle.with {
3843 $0.peerID = egoPeerID
3845 if let bottledPeerData = BM?.contents {
3846 $0.contents = bottledPeerData
3848 throw ContainerError.failedToAssembleBottle
3851 if let bottledPeerEscrowSigningSPKI = BM?.escrowedSigningSPKI {
3852 $0.escrowedSigningSpki = bottledPeerEscrowSigningSPKI
3854 throw ContainerError.failedToAssembleBottle
3857 if let bottledPeerSignatureUsingEscrowKey = BM?.signatureUsingEscrowKey {
3858 $0.signatureUsingEscrowKey = bottledPeerSignatureUsingEscrowKey
3860 throw ContainerError.failedToAssembleBottle
3863 if let bottledPeerSignatureUsingPeerKey = BM?.signatureUsingPeerKey {
3864 $0.signatureUsingPeerKey = bottledPeerSignatureUsingPeerKey
3866 throw ContainerError.failedToAssembleBottle
3869 if let bID = BM?.bottleID {
3872 throw ContainerError.failedToAssembleBottle
3879 func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) {
3880 self.semaphore.wait()
3881 let reply: (Error?) -> Void = {
3882 os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0))
3883 self.semaphore.signal()
3887 var updatedRequest = request
3889 if let egoPeerID = self.containerMO.egoPeerID {
3890 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, _ in
3891 updatedRequest.octagonEgoIdentity = (egoPeerKeys != nil)
3895 self.moc.performAndWait {
3896 self.cuttlefish.reportHealth(updatedRequest) { response, error in
3897 os_log("reportHealth(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3898 guard error == nil else {
3907 func pushHealthInquiry(reply: @escaping (Error?) -> Void) {
3908 self.semaphore.wait()
3909 let reply: (Error?) -> Void = {
3910 os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0))
3911 self.semaphore.signal()
3915 self.moc.performAndWait {
3916 self.cuttlefish.pushHealthInquiry(HealthInquiryRequest()) { response, error in
3917 os_log("pushHealthInquiry(): %@, error: %@", log: tplogDebug, "\(String(describing: response))", "\(String(describing: error))")
3918 guard error == nil else {