2 * Copyright (c) 2018 - 2020 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@
25 import CloudKitCodeProtobuf
30 import SecurityFoundation
32 let tplogDebug = OSLog(subsystem: "com.apple.security.trustedpeers", category: "debug")
33 let tplogTrace = OSLog(subsystem: "com.apple.security.trustedpeers", category: "trace")
34 let egoIdentitiesAccessGroup = "com.apple.security.egoIdentities"
42 extension ResetReason {
43 static func from(cuttlefishResetReason: CuttlefishResetReason) -> ResetReason {
44 switch cuttlefishResetReason {
46 return ResetReason.unknown
47 case .userInitiatedReset:
48 return ResetReason.userInitiatedReset
50 return ResetReason.healthCheck
51 case .noBottleDuringEscrowRecovery:
52 return ResetReason.noBottleDuringEscrowRecovery
53 case .legacyJoinCircle:
54 return ResetReason.legacyJoinCircle
56 return ResetReason.recoveryKey
58 return ResetReason.testGenerated
60 fatalError("unknown reset reason: \(cuttlefishResetReason)")
65 public enum ContainerError: Error {
66 case unableToCreateKeyPair
67 case noPreparedIdentity
68 case failedToStoreIdentity
69 case needsAuthentication
70 case missingStableInfo
71 case missingDynamicInfo
73 case invalidPermanentInfoOrSig
74 case invalidStableInfoOrSig
75 case invalidVoucherOrSig
76 case sponsorNotRegistered(String)
77 case unknownPolicyVersion(UInt64)
78 case preparedIdentityNotOnAllowedList(String)
79 case couldNotLoadAllowedList
80 case noPeersPreapprovePreparedIdentity
81 case policyDocumentDoesNotValidate
82 case tooManyBottlesForPeer
84 case restoreBottleFailed
85 case noBottlesForEscrowRecordID
86 case bottleDoesNotContainContents
87 case bottleDoesNotContainEscrowKeySignature
88 case bottleDoesNotContainerPeerKeySignature
89 case bottleDoesNotContainPeerID
90 case failedToCreateBottledPeer
91 case signatureVerificationFailed
92 case bottleDoesNotContainerEscrowKeySPKI
93 case failedToFetchEscrowContents
94 case failedToCreateRecoveryKey
95 case untrustedRecoveryKeys
97 case recoveryKeysNotEnrolled
98 case bottleCreatingPeerNotFound
99 case unknownCloudKitError
100 case cloudkitResponseMissing
101 case failedToLoadSecret(errorCode: Int)
102 case failedToLoadSecretDueToType
103 case failedToAssembleBottle
105 case failedToStoreSecret(errorCode: Int)
106 case unknownSecurityFoundationError
107 case failedToSerializeData
108 case unknownInternalError
109 case unknownSyncUserControllableViewsValue(value: Int32)
110 case noPeersPreapprovedBySelf
111 case peerRegisteredButNotStored(String)
114 extension ContainerError: LocalizedError {
115 public var errorDescription: String? {
117 case .unableToCreateKeyPair:
118 return "unable to create key pair"
119 case .noPreparedIdentity:
120 return "no prepared identity"
121 case .failedToStoreIdentity:
122 return "failed to stored identity"
123 case .needsAuthentication:
124 return "needs authentication"
125 case .missingStableInfo:
126 return "missing stable info"
127 case .missingDynamicInfo:
128 return "missing dynamic info"
131 case .invalidPermanentInfoOrSig:
132 return "invalid permanent info or signature"
133 case .invalidStableInfoOrSig:
134 return "invalid stable info or signature"
135 case .invalidVoucherOrSig:
136 return "invalid voucher or signature"
137 case .sponsorNotRegistered(let s):
138 return "sponsor not registered: \(s)"
139 case .unknownPolicyVersion(let v):
140 return "unknown policy version: \(v)"
141 case .preparedIdentityNotOnAllowedList(let id):
142 return "prepared identity (\(id)) not on allowed machineID list"
143 case .couldNotLoadAllowedList:
144 return "could not load allowed machineID list"
145 case .noPeersPreapprovePreparedIdentity:
146 return "no peers preapprove prepared identity"
147 case .policyDocumentDoesNotValidate:
148 return "policy document from server doesn't validate"
149 case .tooManyBottlesForPeer:
150 return "too many bottles exist for peer"
151 case .noBottleForPeer:
152 return "no bottle exists for peer"
153 case .restoreBottleFailed:
154 return "failed to restore bottle"
155 case .noBottlesForEscrowRecordID:
156 return "0 bottles exist for escrow record id"
157 case .bottleDoesNotContainContents:
158 return "bottle does not contain encrypted contents"
159 case .bottleDoesNotContainEscrowKeySignature:
160 return "bottle does not contain escrow signature"
161 case .bottleDoesNotContainerPeerKeySignature:
162 return "bottle does not contain peer signature"
163 case .bottleDoesNotContainPeerID:
164 return "bottle does not contain peer id"
165 case .failedToCreateBottledPeer:
166 return "failed to create a bottled peer"
167 case .signatureVerificationFailed:
168 return "failed to verify signature"
169 case .bottleDoesNotContainerEscrowKeySPKI:
170 return "bottle does not contain escrowed key spki"
171 case .failedToFetchEscrowContents:
172 return "failed to fetch escrow contents"
173 case .failedToCreateRecoveryKey:
174 return "failed to create recovery keys"
175 case .untrustedRecoveryKeys:
176 return "untrusted recovery keys"
177 case .noBottlesPresent:
178 return "no bottle present"
179 case .recoveryKeysNotEnrolled:
180 return "recovery key is not enrolled with octagon"
181 case .bottleCreatingPeerNotFound:
182 return "The peer that created the bottle was not found"
183 case .unknownCloudKitError:
184 return "unknown error from cloudkit"
185 case .cloudkitResponseMissing:
186 return "Response missing from CloudKit"
187 case .failedToLoadSecret(errorCode: let errorCode):
188 return "failed to load secret: \(errorCode)"
189 case .failedToLoadSecretDueToType:
190 return "Failed to load secret due to type mismatch (value was not dictionary)"
191 case .failedToAssembleBottle:
192 return "failed to assemble bottle for peer"
194 return "peerID is invalid"
195 case .failedToStoreSecret(errorCode: let errorCode):
196 return "failed to store the secret in the keychain \(errorCode)"
197 case .unknownSecurityFoundationError:
198 return "SecurityFoundation returned an unknown type"
199 case .failedToSerializeData:
200 return "Failed to encode protobuf data"
201 case .unknownInternalError:
202 return "Internal code failed, but didn't return error"
203 case .unknownSyncUserControllableViewsValue(value: let value):
204 return "Unknown syncUserControllableViews number: \(value)"
205 case .noPeersPreapprovedBySelf:
206 return "No peers preapproved by the local peer"
207 case .peerRegisteredButNotStored(let s):
208 return "Peer \(s) not found in database"
213 extension ContainerError: CustomNSError {
214 public static var errorDomain: String {
215 return "com.apple.security.trustedpeers.container"
218 public var errorCode: Int {
220 case .unableToCreateKeyPair:
222 case .noPreparedIdentity:
224 case .failedToStoreIdentity:
226 case .needsAuthentication:
228 case .missingStableInfo:
230 case .missingDynamicInfo:
234 case .invalidPermanentInfoOrSig:
236 case .invalidVoucherOrSig:
238 case .invalidStableInfoOrSig:
240 case .sponsorNotRegistered:
242 case .unknownPolicyVersion:
244 case .preparedIdentityNotOnAllowedList:
246 case .noPeersPreapprovePreparedIdentity:
248 case .policyDocumentDoesNotValidate:
250 // 16 was invalidPeerID and failedToAssembleBottle
251 case .tooManyBottlesForPeer:
253 case .noBottleForPeer:
255 case .restoreBottleFailed:
257 case .noBottlesForEscrowRecordID:
259 case .bottleDoesNotContainContents:
261 case .bottleDoesNotContainEscrowKeySignature:
263 case .bottleDoesNotContainerPeerKeySignature:
265 case .bottleDoesNotContainPeerID:
267 case .failedToCreateBottledPeer:
269 case .signatureVerificationFailed:
271 // 27 was failedToLoadSecret*
272 case .bottleDoesNotContainerEscrowKeySPKI:
274 case .failedToFetchEscrowContents:
276 case .couldNotLoadAllowedList:
278 case .failedToCreateRecoveryKey:
280 case .untrustedRecoveryKeys:
282 case .noBottlesPresent:
284 case .recoveryKeysNotEnrolled:
286 case .bottleCreatingPeerNotFound:
288 case .unknownCloudKitError:
290 case .cloudkitResponseMissing:
292 case .failedToLoadSecret:
294 case .failedToLoadSecretDueToType:
296 case .failedToStoreSecret:
298 case .failedToAssembleBottle:
302 case .unknownSecurityFoundationError:
304 case .failedToSerializeData:
306 case .unknownInternalError:
308 case .unknownSyncUserControllableViewsValue:
310 case .noPeersPreapprovedBySelf:
312 case .peerRegisteredButNotStored:
317 public var underlyingError: NSError? {
319 case .failedToLoadSecret(errorCode: let errorCode):
320 return NSError(domain: "securityd", code: errorCode)
321 case .failedToStoreSecret(errorCode: let errorCode):
322 return NSError(domain: "securityd", code: errorCode)
328 public var errorUserInfo: [String: Any] {
329 var ret = [String: Any]()
330 if let desc = self.errorDescription {
331 ret[NSLocalizedDescriptionKey] = desc
333 if let underlyingError = self.underlyingError {
334 ret[NSUnderlyingErrorKey] = underlyingError
340 internal func traceError(_ error: Error?) -> String {
341 if let error = error {
342 return "error: \(String(describing: error))"
348 func saveSecret(_ secret: Data, label: String) throws {
349 let query: [CFString: Any] = [
350 kSecClass: kSecClassInternetPassword,
351 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
352 kSecUseDataProtectionKeychain: true,
353 kSecAttrAccessGroup: "com.apple.security.octagon",
354 kSecAttrSynchronizable: false,
355 kSecAttrDescription: label,
357 kSecValueData: secret,
360 var results: CFTypeRef?
361 var status = SecItemAdd(query as CFDictionary, &results)
363 if status == errSecSuccess {
367 if status == errSecDuplicateItem {
368 // Add every primary key attribute to this find dictionary
369 var findQuery: [CFString: Any] = [:]
370 findQuery[kSecClass] = query[kSecClass]
371 findQuery[kSecAttrSynchronizable] = query[kSecAttrSynchronizable]
372 findQuery[kSecAttrAccessGroup] = query[kSecAttrAccessGroup]
373 findQuery[kSecAttrServer] = query[kSecAttrDescription]
374 findQuery[kSecAttrPath] = query[kSecAttrPath]
375 findQuery[kSecUseDataProtectionKeychain] = query[kSecUseDataProtectionKeychain]
377 var updateQuery: [CFString: Any] = query
378 updateQuery[kSecClass] = nil
380 status = SecItemUpdate(findQuery as CFDictionary, updateQuery as CFDictionary)
382 if status != errSecSuccess {
383 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
386 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
390 func loadSecret(label: String) throws -> (Data?) {
393 let query: [CFString: Any] = [
394 kSecClass: kSecClassInternetPassword,
395 kSecAttrAccessGroup: "com.apple.security.octagon",
396 kSecAttrDescription: label,
397 kSecReturnAttributes: true,
398 kSecReturnData: true,
399 kSecAttrSynchronizable: false,
400 kSecMatchLimit: kSecMatchLimitOne,
403 var result: CFTypeRef?
404 let status = SecItemCopyMatching(query as CFDictionary, &result)
406 if status != errSecSuccess || result == nil {
407 throw ContainerError.failedToLoadSecret(errorCode: Int(status))
411 if let dictionary = result as? [CFString: Any] {
412 secret = dictionary[kSecValueData] as? Data
414 throw ContainerError.failedToLoadSecretDueToType
420 func saveEgoKeyPair(_ keyPair: _SFECKeyPair, identifier: String, resultHandler: @escaping (Bool, Error?) -> Void) {
421 let keychainManager = _SFKeychainManager.default()
422 let signingIdentity = _SFIdentity(keyPair: keyPair)
423 let accessibility = SFAccessibilityMakeWithMode(SFAccessibilityMode.accessibleWhenUnlocked) // class A
424 let accessPolicy = _SFAccessPolicy(accessibility: accessibility,
425 sharingPolicy: SFSharingPolicy.thisDeviceOnly)
426 accessPolicy.accessGroup = egoIdentitiesAccessGroup
427 keychainManager.setIdentity(signingIdentity,
428 forIdentifier: identifier,
429 accessPolicy: accessPolicy,
430 resultHandler: resultHandler)
433 func loadEgoKeyPair(identifier: String, resultHandler: @escaping (_SFECKeyPair?, Error?) -> Void) {
434 let keychainManager = _SFKeychainManager.default()
436 // FIXME constrain to egoIdentitiesAccessGroup, <rdar://problem/39597940>
437 keychainManager.identity(forIdentifier: identifier) { result in
438 switch result.resultType {
439 case .valueAvailable:
440 resultHandler(result.value?.keyPair as? _SFECKeyPair, nil)
441 case .needsAuthentication:
442 resultHandler(nil, ContainerError.needsAuthentication)
444 resultHandler(nil, result.error)
446 resultHandler(nil, ContainerError.unknownSecurityFoundationError)
451 func loadEgoKeys(peerID: String, resultHandler: @escaping (OctagonSelfPeerKeys?, Error?) -> Void) {
452 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: peerID)) { signingKey, error in
453 guard let signingKey = signingKey else {
454 os_log("Unable to load signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
455 resultHandler(nil, error)
459 loadEgoKeyPair(identifier: encryptionKeyIdentifier(peerID: peerID)) { encryptionKey, error in
460 guard let encryptionKey = encryptionKey else {
461 os_log("Unable to load encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
462 resultHandler(nil, error)
467 resultHandler(try OctagonSelfPeerKeys(peerID: peerID, signingKey: signingKey, encryptionKey: encryptionKey), nil)
469 resultHandler(nil, error)
475 func removeEgoKeysSync(peerID: String) throws -> Bool {
476 var resultSema = DispatchSemaphore(value: 0)
478 let keychainManager = _SFKeychainManager.default()
480 var retresultForSigningDeletion: Bool = false
481 var reterrorForSigningDeletion: Error?
483 //remove signing keys
484 keychainManager.removeItem(withIdentifier: signingKeyIdentifier(peerID: peerID)) { result, error in
485 retresultForSigningDeletion = result
486 reterrorForSigningDeletion = error
492 if let error = reterrorForSigningDeletion {
496 if retresultForSigningDeletion == false {
497 return retresultForSigningDeletion
500 // now let's do the same thing with the encryption keys
501 resultSema = DispatchSemaphore(value: 0)
502 var retresultForEncryptionDeletion: Bool = false
503 var reterrorForEncryptionDeletion: Error?
505 keychainManager.removeItem(withIdentifier: encryptionKeyIdentifier(peerID: peerID)) { result, error in
506 retresultForEncryptionDeletion = result
507 reterrorForEncryptionDeletion = error
512 if let error = reterrorForEncryptionDeletion {
516 return retresultForEncryptionDeletion && retresultForSigningDeletion
519 func loadEgoKeysSync(peerID: String) throws -> OctagonSelfPeerKeys {
520 // Gotta promote to synchronous; 'antipattern' ahoy
521 let resultSema = DispatchSemaphore(value: 0)
523 var result: OctagonSelfPeerKeys?
526 loadEgoKeys(peerID: peerID) { keys, error in
533 if let error = reserror {
537 if let result = result {
544 func signingKeyIdentifier(peerID: String) -> String {
545 return "signing-key " + peerID
548 func encryptionKeyIdentifier(peerID: String) -> String {
549 return "encryption-key " + peerID
552 func makeTLKShares(ckksTLKs: [CKKSKeychainBackedKey]?, asPeer: CKKSSelfPeer, toPeer: CKKSPeer, epoch: Int) throws -> [TLKShare] {
553 return try (ckksTLKs ?? []).map { tlk in
554 os_log("Making TLKShare for %@ for key %@", log: tplogDebug, type: .default, toPeer.description, tlk)
555 // Not being able to convert a TLK to a TLKShare is a failure, but not having a TLK is only half bad
557 return TLKShare.convert(ckksTLKShare: try CKKSTLKShare(tlk, as: asPeer, to: toPeer, epoch: epoch, poisoned: 0))
559 let nserror = error as NSError
560 if nserror.domain == "securityd" && nserror.code == errSecItemNotFound {
561 os_log("No TLK contents for %@, no TLK share possible", log: tplogDebug, type: .default, tlk)
572 func extract(tlkShares: [CKKSTLKShare], peer: OctagonSelfPeerKeys, model: TPModel) -> (Int64, Int64) {
573 os_log("Attempting to recover %d TLK shares for peer %{public}@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID)
575 return extract(tlkShares: tlkShares, peer: peer, sponsorPeerID: nil, model: model)
579 func extract(tlkShares: [CKKSTLKShare], peer: OctagonSelfPeerKeys, sponsorPeerID: String?, model: TPModel) -> (Int64, Int64) {
580 var tlksRecovered: Set<String> = Set()
581 var sharesRecovered: Int64 = 0
583 for share in tlkShares {
584 guard share.receiverPeerID == peer.peerID else {
585 os_log("Skipping %@ (wrong peerID)", log: tplogDebug, type: .default, share)
590 var trustedPeers: [AnyHashable] = [peer]
592 if let egoPeer = model.peer(withID: sponsorPeerID ?? peer.peerID) {
593 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
594 if let peer = model.peer(withID: trustedPeerID) {
595 let peerObj = CKKSActualPeer(peerID: trustedPeerID,
596 encryptionPublicKey: (peer.permanentInfo.encryptionPubKey as! _SFECPublicKey),
597 signing: (peer.permanentInfo.signingPubKey as! _SFECPublicKey),
600 trustedPeers.append(peerObj)
602 os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID)
606 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
609 let key = try share.recoverTLK(peer,
610 trustedPeers: Set(trustedPeers),
613 try key.saveMaterialToKeychain()
614 tlksRecovered.insert(key.uuid)
616 os_log("Recovered %@ (from %@)", log: tplogDebug, type: .default, key, share)
618 os_log("Failed to recover share %@: %{public}@", log: tplogDebug, type: .default, share, error as CVarArg)
622 return (Int64(tlksRecovered.count), sharesRecovered)
625 struct ContainerState {
626 var egoPeerID: String?
627 var peers: [String: TPPeer] = [:]
628 var vouchers: [TPVoucher] = []
629 var bottles = Set<BottleMO>()
630 var escrowRecords = Set<EscrowRecordMO>()
631 var recoverySigningKey: Data?
632 var recoveryEncryptionKey: Data?
635 internal struct StableChanges {
636 let deviceName: String?
637 let serialNumber: String?
638 let osVersion: String?
639 let policyVersion: UInt64?
640 let policySecrets: [String: Data]?
641 let setSyncUserControllableViews: TPPBPeerStableInfo_UserControllableViewStatus?
644 // CoreData doesn't handle creating an identical model from an identical URL. Help it out.
645 private var nsObjectModels: [URL: NSManagedObjectModel] = [:]
646 private let nsObjectModelsQueue = DispatchQueue(label: "com.apple.security.TrustedPeersHelper.nsObjectModels")
648 func getOrMakeModel(url: URL) -> NSManagedObjectModel {
649 return nsObjectModelsQueue.sync {
650 if let model = nsObjectModels[url] {
653 let newModel = NSManagedObjectModel(contentsOf: url)!
654 nsObjectModels[url] = newModel
659 struct ContainerName: Hashable, CustomStringConvertible {
660 let container: String
663 func asSingleString() -> String {
664 // Just to be nice, hide the fact that multiple contexts exist on most machines
665 if self.context == OTDefaultContext {
666 return self.container
668 return self.container + "-" + self.context
672 var description: String {
673 return "Container(\(self.container),\(self.context))"
677 extension ContainerMO {
678 func egoStableInfo() -> TPPeerStableInfo? {
679 guard let egoStableData = self.egoPeerStableInfo,
680 let egoStableSig = self.egoPeerStableInfoSig else {
684 return TPPeerStableInfo(data: egoStableData, sig: egoStableSig)
688 /// This maps to a Cuttlefish service backed by a CloudKit container,
689 /// and a corresponding local Core Data persistent container.
691 /// Methods may be invoked concurrently.
692 class Container: NSObject {
693 let name: ContainerName
695 private let cuttlefish: CuttlefishAPIAsync
697 // Only one request (from Client) is permitted to be in progress at a time.
698 // That includes while waiting for network, i.e. one request must complete
699 // before the next can begin. Otherwise two requests could in parallel be
700 // fetching updates from Cuttlefish, with ensuing changeToken overwrites etc.
701 // This applies for mutating requests -- requests that can only read the current
702 // state (on the moc queue) do not need to.
703 internal let semaphore = DispatchSemaphore(value: 1)
705 // All Core Data access happens through moc: NSManagedObjectContext. The
706 // moc insists on having its own queue, and all operations must happen on
708 internal let moc: NSManagedObjectContext
710 // To facilitate CoreData tear down, we need to keep the PersistentContainer around.
711 internal let persistentContainer: NSPersistentContainer
713 // Rather than Container having its own dispatch queue, we use moc's queue
714 // to synchronise access to our own state as well. So the following instance
715 // variables must only be accessed within blocks executed by calling
716 // moc.perform() or moc.performAndWait().
717 internal var containerMO: ContainerMO
718 internal var model: TPModel
719 internal var escrowCacheTimeout: TimeInterval
721 // Used in tests only. Set when an identity is prepared using a policy version override
722 internal var policyVersionOverride: TPPolicyVersion?
725 Construct a Container.
727 - Parameter name: The name the CloudKit container to which requests will be routed.
729 The "real" container that drives CKKS etc should be named `"com.apple.security.keychain"`.
730 Use other names for test containers such as for rawfish (rawhide-style testing for cuttlefish)
732 - Parameter persistentStoreURL: The location the local Core Data database for this container will be stored.
734 - Parameter cuttlefish: Interface to cuttlefish.
736 init(name: ContainerName, persistentStoreDescription: NSPersistentStoreDescription, cuttlefish: CuttlefishAPIAsync) throws {
737 var initError: Error?
738 var containerMO: ContainerMO?
741 // Set up Core Data stack
742 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
743 let mom = getOrMakeModel(url: url)
744 self.persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
745 self.persistentContainer.persistentStoreDescriptions = [persistentStoreDescription]
747 self.persistentContainer.loadPersistentStores { _, error in
750 if let initError = initError {
754 let moc = self.persistentContainer.newBackgroundContext()
755 moc.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
758 // Fetch an existing ContainerMO record if it exists, or create and save one
760 let containerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Container")
761 containerFetch.predicate = NSPredicate(format: "name == %@", name.asSingleString())
762 let fetchedContainers = try moc.fetch(containerFetch)
763 if let container = fetchedContainers.first as? ContainerMO {
764 containerMO = container
766 containerMO = ContainerMO(context: moc)
767 containerMO!.name = name.asSingleString()
770 // Perform upgrades as needed
771 Container.onqueueUpgradeMachineIDSetToModel(container: containerMO!, moc: moc)
772 Container.onqueueUpgradeMachineIDSetToUseStatus(container: containerMO!, moc: moc)
774 //remove duplicate vouchers on all the peers
775 Container.onqueueRemoveDuplicateVouchersPerPeer(container: containerMO!, moc: moc)
777 model = Container.loadModel(from: containerMO!)
778 Container.ensureEgoConsistency(from: containerMO!, model: model!)
785 if let initError = initError {
791 self.containerMO = containerMO!
792 self.cuttlefish = cuttlefish
794 self.escrowCacheTimeout = 60.0 * 15.0 //15 minutes
798 func deletePersistentStore() throws {
799 // Call this to entirely destroy the persistent store.
800 // This container should not be used after this event.
802 try self.persistentContainer.persistentStoreDescriptions.forEach { storeDescription in
803 if let url = storeDescription.url {
804 try self.moc.persistentStoreCoordinator?.destroyPersistentStore(at: url,
805 ofType: storeDescription.type,
811 // Must be on containerMO's moc queue to call this
812 internal static func loadModel(from containerMO: ContainerMO) -> TPModel {
813 // Populate model from persistent store
814 let model = TPModel(decrypter: Decrypter())
815 let keyFactory = TPECPublicKeyFactory()
816 let peers = containerMO.peers as? Set<PeerMO>
817 peers?.forEach { peer in
818 guard let permanentInfo = TPPeerPermanentInfo(peerID: peer.peerID!,
819 data: peer.permanentInfo! as Data,
820 sig: peer.permanentInfoSig! as Data,
821 keyFactory: keyFactory) else {
824 model.registerPeer(with: permanentInfo)
825 if let data = peer.stableInfo, let sig = peer.stableInfoSig {
826 if let stableInfo = TPPeerStableInfo(data: data as Data, sig: sig as Data) {
828 try model.update(stableInfo, forPeerWithID: permanentInfo.peerID)
830 os_log("loadModel unable to update stable info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg)
833 os_log("loadModel: peer %{public}@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
836 os_log("loadModel: peer %{public}@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
838 if let data = peer.dynamicInfo, let sig = peer.dynamicInfoSig {
839 if let dynamicInfo = TPPeerDynamicInfo(data: data as Data, sig: sig as Data) {
841 try model.update(dynamicInfo, forPeerWithID: permanentInfo.peerID)
843 os_log("loadModel unable to update dynamic info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg)
846 os_log("loadModel: peer %{public}@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
849 os_log("loadModel: peer %{public}@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
851 peer.vouchers?.forEach {
852 let v = $0 as! VoucherMO
853 if let data = v.voucherInfo, let sig = v.voucherInfoSig {
854 if let voucher = TPVoucher(infoWith: data, sig: sig) {
855 model.register(voucher)
861 os_log("loadModel: loaded %{public}d vouchers", log: tplogDebug, type: .default, model.allVouchers().count)
863 // Note: the containerMO objects are misnamed; they are key data, and not SPKI.
864 if let recoveryKeySigningKeyData = containerMO.recoveryKeySigningSPKI,
865 let recoveryKeyEncyryptionKeyData = containerMO.recoveryKeyEncryptionSPKI {
866 model.setRecoveryKeys(TPRecoveryKeyPair(signingKeyData: recoveryKeySigningKeyData, encryptionKeyData: recoveryKeyEncyryptionKeyData))
868 // If the ego peer has an RK set, tell the model to use that one
869 // This is a hack to work around TPH databases which don't have the RK set on the container due to previously running old software
870 if let egoStableInfo = containerMO.egoStableInfo(),
871 egoStableInfo.recoverySigningPublicKey.count > 0,
872 egoStableInfo.recoveryEncryptionPublicKey.count > 0 {
873 os_log("loadModel: recovery key not set in model, but is set on ego peer", log: tplogDebug, type: .default)
874 model.setRecoveryKeys(TPRecoveryKeyPair(signingKeyData: egoStableInfo.recoverySigningPublicKey, encryptionKeyData: egoStableInfo.recoveryEncryptionPublicKey))
878 // Register persisted policies (cached from cuttlefish)
879 let policies = containerMO.policies as? Set<PolicyMO>
880 policies?.forEach { policyMO in
881 if let policyHash = policyMO.policyHash,
882 let policyData = policyMO.policyData {
883 if let policyDoc = TPPolicyDocument.policyDoc(withHash: policyHash, data: policyData) {
884 model.register(policyDoc)
889 // Register built-in policies
890 builtInPolicyDocuments().forEach { policyDoc in
891 model.register(policyDoc)
894 let knownMachines = containerMO.machines as? Set<MachineMO> ?? Set()
895 let allowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.allowed.rawValue }.compactMap { $0.machineID })
896 let disallowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.disallowed.rawValue }.compactMap { $0.machineID })
898 os_log("loadModel: allowedMachineIDs: %{public}@", log: tplogDebug, type: .default, allowedMachineIDs)
899 os_log("loadModel: disallowedMachineIDs: %{public}@", log: tplogDebug, type: .default, disallowedMachineIDs)
901 if allowedMachineIDs.isEmpty {
902 os_log("loadModel: no allowedMachineIDs?", log: tplogDebug, type: .default)
908 // Must be on containerMO's moc queue to call this
909 internal static func ensureEgoConsistency(from containerMO: ContainerMO, model: TPModel) {
910 guard let egoPeerID = containerMO.egoPeerID,
911 let egoStableData = containerMO.egoPeerStableInfo,
912 let egoStableSig = containerMO.egoPeerStableInfoSig
914 os_log("ensureEgoConsistency failed to find ego peer information", log: tplogDebug, type: .error)
918 guard let containerEgoStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
919 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from container", log: tplogDebug, type: .error)
923 guard let modelStableInfo = model.getStableInfoForPeer(withID: egoPeerID) else {
924 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from model", log: tplogDebug, type: .error)
928 if modelStableInfo.clock > containerEgoStableInfo.clock {
929 containerMO.egoPeerStableInfo = modelStableInfo.data
930 containerMO.egoPeerStableInfoSig = modelStableInfo.sig
934 static func dictionaryRepresentation(bottle: BottleMO) -> [String: Any] {
935 var dict: [String: String] = [:]
937 dict["bottleID"] = bottle.bottleID
938 dict["peerID"] = bottle.peerID
939 dict["signingSPKI"] = bottle.escrowedSigningSPKI?.base64EncodedString()
940 dict["signatureUsingPeerKey"] = bottle.signatureUsingPeerKey?.base64EncodedString()
941 dict["signatureUsingSPKI"] = bottle.signatureUsingEscrowKey?.base64EncodedString()
942 // ignore the bottle contents; they're mostly unreadable
947 static func peerdictionaryRepresentation(peer: TPPeer) -> [String: Any] {
948 var peerDict: [String: Any] = [
949 "permanentInfo": peer.permanentInfo.dictionaryRepresentation(),
950 "peerID": peer.peerID,
952 if let stableInfo = peer.stableInfo {
953 peerDict["stableInfo"] = stableInfo.dictionaryRepresentation()
955 if let dynamicInfo = peer.dynamicInfo {
956 peerDict["dynamicInfo"] = dynamicInfo.dictionaryRepresentation()
962 func onQueueDetermineLocalTrustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
963 let viablePeerCountsByModelID = self.model.viablePeerCountsByModelID()
964 let peerCountsByMachineID = self.model.peerCountsByMachineID()
965 if let egoPeerID = self.containerMO.egoPeerID {
966 var status = self.model.statusOfPeer(withID: egoPeerID)
967 var isExcluded: Bool = (status == .excluded)
969 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, loadError in
970 var returnError = loadError
972 guard returnError == nil else {
974 if let error = (loadError as NSError?) {
975 os_log("trust status: Unable to load ego keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
976 if error.code == errSecItemNotFound && error.domain == NSOSStatusErrorDomain {
977 os_log("trust status: Lost the ego key pair, returning 'excluded' in hopes of fixing up the identity", log: tplogDebug, type: .debug)
980 } else if error.code == errSecInteractionNotAllowed && error.domain == NSOSStatusErrorDomain {
981 // we have an item, but can't read it, suppressing error
987 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
989 viablePeerCountsByModelID: viablePeerCountsByModelID,
990 peerCountsByMachineID: peerCountsByMachineID,
991 isExcluded: isExcluded,
993 reply(egoStatus, returnError)
997 //ensure egoPeerKeys are populated
998 guard egoPeerKeys != nil else {
999 os_log("trust status: No error but Ego Peer Keys are nil", log: tplogDebug, type: .default)
1000 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
1002 viablePeerCountsByModelID: viablePeerCountsByModelID,
1003 peerCountsByMachineID: peerCountsByMachineID,
1007 reply(egoStatus, loadError)
1011 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
1013 viablePeerCountsByModelID: viablePeerCountsByModelID,
1014 peerCountsByMachineID: peerCountsByMachineID,
1015 isExcluded: isExcluded,
1017 reply(egoStatus, nil)
1021 // With no ego peer ID, either return 'excluded' if there are extant peers, or 'unknown' to signal no peers at all
1022 if self.model.allPeerIDs().isEmpty {
1023 os_log("No existing peers in account", log: tplogDebug, type: .debug)
1024 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
1026 viablePeerCountsByModelID: viablePeerCountsByModelID,
1027 peerCountsByMachineID: peerCountsByMachineID,
1030 reply(egoStatus, nil)
1033 os_log("Existing peers in account, but we don't have a peer ID. We are excluded.", log: tplogDebug, type: .debug)
1034 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
1036 viablePeerCountsByModelID: viablePeerCountsByModelID,
1037 peerCountsByMachineID: peerCountsByMachineID,
1040 reply(egoStatus, nil)
1046 func trustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
1047 self.semaphore.wait()
1048 let reply: (TrustedPeersHelperEgoPeerStatus, Error?) -> Void = {
1049 // Suppress logging of successful replies here; it's not that useful
1050 let logType: OSLogType = $1 == nil ? .debug : .info
1051 os_log("trustStatus complete: %{public}@ %{public}@",
1052 log: tplogTrace, type: logType, TPPeerStatusToString($0.egoStatus), traceError($1))
1054 self.semaphore.signal()
1057 self.moc.performAndWait {
1058 // Knowledge of your peer status only exists if you know about other peers. If you haven't fetched, fetch.
1059 if self.containerMO.changeToken == nil {
1060 self.onqueueFetchAndPersistChanges { fetchError in
1061 guard fetchError == nil else {
1062 if let error = fetchError {
1063 os_log("Unable to fetch changes, trust status is unknown: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1066 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
1068 viablePeerCountsByModelID: [:],
1069 peerCountsByMachineID: [:],
1072 reply(egoStatus, fetchError)
1076 self.moc.performAndWait {
1077 self.onQueueDetermineLocalTrustStatus(reply: reply)
1081 self.onQueueDetermineLocalTrustStatus(reply: reply)
1086 func fetchTrustState(reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) {
1087 let reply: (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void = {
1088 os_log("fetch trust state complete: %{public}@ %{public}@",
1089 log: tplogTrace, type: .info, String(reflecting: $0), traceError($2))
1093 self.moc.performAndWait {
1094 if let egoPeerID = self.containerMO.egoPeerID,
1095 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1096 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig {
1097 let keyFactory = TPECPublicKeyFactory()
1098 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1099 os_log("fetchTrustState failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
1100 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
1104 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(permanentInfo.signingPubKey.spki())
1105 os_log("fetchTrustState: ego peer is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
1107 let egoStableInfo = self.model.getStableInfoForPeer(withID: egoPeerID)
1109 let egoPeerStatus = TrustedPeersHelperPeerState(peerID: egoPeerID,
1110 isPreapproved: isPreapproved,
1111 status: self.model.statusOfPeer(withID: egoPeerID),
1112 memberChanges: false,
1113 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
1114 osVersion: egoStableInfo?.osVersion)
1116 var tphPeers: [TrustedPeersHelperPeer] = []
1118 if let egoPeer = self.model.peer(withID: egoPeerID) {
1119 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
1120 if let peer = self.model.peer(withID: trustedPeerID) {
1121 let peerViews = try? self.model.getViewsForPeer(peer.permanentInfo,
1122 stableInfo: peer.stableInfo)
1124 tphPeers.append(TrustedPeersHelperPeer(peerID: trustedPeerID,
1125 signingSPKI: peer.permanentInfo.signingPubKey.spki(),
1126 encryptionSPKI: peer.permanentInfo.encryptionPubKey.spki(),
1127 viewList: peerViews ?? Set()))
1129 os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID)
1133 if let stableInfo = egoPeer.stableInfo, stableInfo.recoveryEncryptionPublicKey.count > 0, stableInfo.recoverySigningPublicKey.count > 0 {
1134 let recoveryKeyPair = TPRecoveryKeyPair(stableInfo: stableInfo)
1137 // The RK should have all views. So, claim that it should have all views that this peer has.
1138 let rkViews = try self.model.getViewsForPeer(egoPeer.permanentInfo,
1139 stableInfo: egoPeer.stableInfo)
1141 tphPeers.append(try RecoveryKey.asPeer(recoveryKeys: recoveryKeyPair,
1144 os_log("Unable to add RK as a trusted peer: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1148 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
1151 os_log("Returning trust state: %{public}@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers)
1152 reply(egoPeerStatus, tphPeers, nil)
1154 // With no ego peer ID, there are no trusted peers
1155 os_log("No peer ID => no trusted peers", log: tplogDebug, type: .debug)
1156 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: false, unknownMachineIDs: false, osVersion: nil), [], nil)
1161 func dump(reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1162 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1163 os_log("dump complete: %{public}@",
1164 log: tplogTrace, type: .info, traceError($1))
1167 self.moc.performAndWait {
1168 var d: [AnyHashable: Any] = [:]
1170 if let egoPeerID = self.containerMO.egoPeerID {
1171 if let peer = self.model.peer(withID: egoPeerID) {
1172 d["self"] = Container.peerdictionaryRepresentation(peer: peer)
1174 d["self"] = ["peerID": egoPeerID]
1180 d["peers"] = self.model.allPeers().filter { $0.peerID != self.containerMO.egoPeerID }.map { peer in
1181 Container.peerdictionaryRepresentation(peer: peer)
1184 d["vouchers"] = self.model.allVouchers().map { $0.dictionaryRepresentation() }
1186 if let bottles = self.containerMO.bottles as? Set<BottleMO> {
1187 d["bottles"] = bottles.map { Container.dictionaryRepresentation(bottle: $0) }
1192 let midList = self.onqueueCurrentMIDList()
1193 d["machineIDsAllowed"] = midList.machineIDs(in: .allowed).sorted()
1194 d["machineIDsDisallowed"] = midList.machineIDs(in: .disallowed).sorted()
1195 d["modelRecoverySigningPublicKey"] = self.model.recoverySigningPublicKey()
1196 d["modelRecoveryEncryptionPublicKey"] = self.model.recoveryEncryptionPublicKey()
1202 func dumpEgoPeer(reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) {
1203 let reply: (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void = {
1204 os_log("dumpEgoPeer complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
1205 reply($0, $1, $2, $3, $4)
1207 self.moc.performAndWait {
1208 guard let egoPeerID = self.containerMO.egoPeerID else {
1209 reply(nil, nil, nil, nil, ContainerError.noPreparedIdentity)
1213 guard let peer = self.model.peer(withID: egoPeerID) else {
1214 reply(egoPeerID, nil, nil, nil, nil)
1218 reply(egoPeerID, peer.permanentInfo, peer.stableInfo, peer.dynamicInfo, nil)
1222 func validatePeers(request: ValidatePeersRequest, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1223 self.semaphore.wait()
1224 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1225 os_log("validatePeers complete %{public}@", log: tplogTrace, type: .info, traceError($1))
1226 self.semaphore.signal()
1230 self.cuttlefish.validatePeers(request) { response, error in
1231 guard let response = response, error == nil else {
1232 os_log("validatePeers failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1233 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
1237 var info: [AnyHashable: Any] = [:]
1238 info["health"] = response.validatorsHealth as AnyObject
1239 info["results"] = try? JSONSerialization.jsonObject(with: response.jsonUTF8Data())
1245 func reset(resetReason: CuttlefishResetReason, reply: @escaping (Error?) -> Void) {
1246 self.semaphore.wait()
1247 let reply: (Error?) -> Void = {
1248 os_log("reset complete %{public}@", log: tplogTrace, type: .info, traceError($0))
1249 self.semaphore.signal()
1253 self.moc.performAndWait {
1254 let resetReason = ResetReason.from(cuttlefishResetReason: resetReason)
1255 let request = ResetRequest.with {
1256 $0.resetReason = resetReason
1258 self.cuttlefish.reset(request) { response, error in
1259 guard let response = response, error == nil else {
1260 os_log("reset failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1261 reply(error ?? ContainerError.cloudkitResponseMissing)
1265 // Erase container's persisted state
1266 self.moc.performAndWait {
1267 self.moc.delete(self.containerMO)
1268 self.containerMO = ContainerMO(context: self.moc)
1269 self.containerMO.name = self.name.asSingleString()
1270 self.model = Container.loadModel(from: self.containerMO)
1272 try self.onQueuePersist(changes: response.changes)
1273 os_log("reset succeded", log: tplogDebug, type: .default)
1276 os_log("reset persist failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1284 func localReset(reply: @escaping (Error?) -> Void) {
1285 self.semaphore.wait()
1286 let reply: (Error?) -> Void = {
1287 os_log("localReset complete %{public}@", log: tplogTrace, type: .info, traceError($0))
1288 self.semaphore.signal()
1292 self.moc.performAndWait {
1294 // Erase container's persisted state
1295 self.moc.delete(self.containerMO)
1296 self.containerMO = ContainerMO(context: self.moc)
1297 self.containerMO.name = self.name.asSingleString()
1298 self.model = Container.loadModel(from: self.containerMO)
1308 func loadOrCreateKeyPair(privateKeyPersistentRef: Data?) throws -> _SFECKeyPair {
1309 if let privateKeyPersistentRef = privateKeyPersistentRef {
1310 return try TPHObjectiveC.fetchKeyPair(withPrivateKeyPersistentRef: privateKeyPersistentRef)
1312 let keySpecifier = _SFECKeySpecifier(curve: SFEllipticCurve.nistp384)
1313 guard let keyPair = _SFECKeyPair(randomKeyPairWith: keySpecifier) else {
1314 throw ContainerError.unableToCreateKeyPair
1320 // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion.versionNumber
1321 func prepare(epoch: UInt64,
1326 deviceName: String?,
1327 serialNumber: String?,
1329 policyVersion: TPPolicyVersion?,
1330 policySecrets: [String: Data]?,
1331 syncUserControllableViews: TPPBPeerStableInfo_UserControllableViewStatus,
1332 signingPrivateKeyPersistentRef: Data?,
1333 encryptionPrivateKeyPersistentRef: Data?,
1334 reply: @escaping (String?, Data?, Data?, Data?, Data?, TPSyncingPolicy?, Error?) -> Void) {
1335 self.semaphore.wait()
1336 let reply: (String?, Data?, Data?, Data?, Data?, TPSyncingPolicy?, Error?) -> Void = {
1337 os_log("prepare complete peerID: %{public}@ %{public}@",
1338 log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($6))
1339 self.semaphore.signal()
1340 reply($0, $1, $2, $3, $4, $5, $6)
1343 // Create a new peer identity with random keys, and store the keys in keychain
1344 let permanentInfo: TPPeerPermanentInfo
1345 let signingKeyPair: _SFECKeyPair
1346 let encryptionKeyPair: _SFECKeyPair
1348 signingKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: signingPrivateKeyPersistentRef)
1349 encryptionKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: encryptionPrivateKeyPersistentRef)
1351 // <rdar://problem/56270219> Octagon: use epoch transmitted across pairing channel
1352 permanentInfo = try TPPeerPermanentInfo(machineID: machineID,
1355 signing: signingKeyPair,
1356 encryptionKeyPair: encryptionKeyPair,
1357 peerIDHashAlgo: TPHashAlgo.SHA256)
1359 reply(nil, nil, nil, nil, nil, nil, error)
1363 let peerID = permanentInfo.peerID
1365 let bottle: BottledPeer
1367 bottle = try BottledPeer(peerID: peerID,
1369 peerSigningKey: signingKeyPair,
1370 peerEncryptionKey: encryptionKeyPair,
1371 bottleSalt: bottleSalt)
1373 _ = try saveSecret(bottle.secret, label: peerID)
1375 os_log("bottle creation failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1376 reply(nil, nil, nil, nil, nil, nil, error)
1380 saveEgoKeyPair(signingKeyPair, identifier: signingKeyIdentifier(peerID: peerID)) { success, error in
1381 guard success else {
1382 os_log("Unable to save signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1383 reply(nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1386 saveEgoKeyPair(encryptionKeyPair, identifier: encryptionKeyIdentifier(peerID: peerID)) { success, error in
1387 guard success else {
1388 os_log("Unable to save encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1389 reply(nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1393 let actualPolicyVersion = policyVersion ?? prevailingPolicyVersion
1394 self.fetchPolicyDocumentWithSemaphore(version: actualPolicyVersion) { policyDoc, policyFetchError in
1395 guard let policyDoc = policyDoc, policyFetchError == nil else {
1396 os_log("Unable to fetch policy: %{public}@", log: tplogDebug, type: .default, (policyFetchError as CVarArg?) ?? "error missing")
1397 reply(nil, nil, nil, nil, nil, nil, error ?? ContainerError.unknownInternalError)
1401 if policyVersion != nil {
1402 self.policyVersionOverride = policyDoc.version
1405 // Save the prepared identity as containerMO.egoPeer* and its bottle
1406 self.moc.performAndWait {
1408 // Note: the client chooses for syncUserControllableViews here.
1409 // if they pass in UNKNOWN, we'll fix it later at join time, following the peers we trust.
1410 let syncUserViews = syncUserControllableViews.sanitizeForPlatform(permanentInfo: permanentInfo)
1412 let useFrozenPolicyVersion = policyDoc.version.versionNumber >= frozenPolicyVersion.versionNumber
1414 let stableInfo = try TPPeerStableInfo(clock: 1,
1415 frozenPolicyVersion: useFrozenPolicyVersion ? frozenPolicyVersion : policyDoc.version,
1416 flexiblePolicyVersion: useFrozenPolicyVersion ? policyDoc.version : nil,
1417 policySecrets: policySecrets,
1418 syncUserControllableViews: syncUserViews,
1419 deviceName: deviceName,
1420 serialNumber: serialNumber,
1421 osVersion: osVersion,
1422 signing: signingKeyPair,
1423 recoverySigningPubKey: nil,
1424 recoveryEncryptionPubKey: nil)
1425 self.containerMO.egoPeerID = permanentInfo.peerID
1426 self.containerMO.egoPeerPermanentInfo = permanentInfo.data
1427 self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig
1428 self.containerMO.egoPeerStableInfo = stableInfo.data
1429 self.containerMO.egoPeerStableInfoSig = stableInfo.sig
1431 let bottleMO = BottleMO(context: self.moc)
1432 bottleMO.peerID = bottle.peerID
1433 bottleMO.bottleID = bottle.bottleID
1434 bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI
1435 bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
1436 bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey
1437 bottleMO.contents = bottle.contents
1439 self.containerMO.addToBottles(bottleMO)
1441 let syncingPolicy = try self.syncingPolicyFor(modelID: permanentInfo.modelID, stableInfo: stableInfo)
1445 reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, syncingPolicy, nil)
1447 os_log("Unable to save identity: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1448 reply(nil, nil, nil, nil, nil, nil, error)
1455 func getEgoEpoch(reply: @escaping (UInt64, Error?) -> Void) {
1456 let reply: (UInt64, Error?) -> Void = {
1457 os_log("getEgoEpoch complete: %d %{public}@", log: tplogTrace, type: .info, $0, traceError($1))
1461 self.moc.performAndWait {
1462 guard let egoPeerID = self.containerMO.egoPeerID else {
1463 reply(0, ContainerError.noPreparedIdentity)
1466 guard let egoPeer = self.model.peer(withID: egoPeerID) else {
1467 reply(0, ContainerError.noPreparedIdentity)
1471 reply(egoPeer.permanentInfo.epoch, nil)
1474 func establish(ckksKeys: [CKKSKeychainBackedKeySet],
1475 tlkShares: [CKKSTLKShare],
1476 preapprovedKeys: [Data]?,
1477 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
1478 self.semaphore.wait()
1479 let reply: (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void = {
1480 os_log("establish complete peer: %{public}@ %{public}@",
1481 log: tplogTrace, type: .default, ($0 ?? "NULL") as CVarArg, traceError($3))
1482 self.semaphore.signal()
1483 reply($0, $1, $2, $3)
1486 self.moc.performAndWait {
1487 self.onqueueEstablish(ckksKeys: ckksKeys,
1488 tlkShares: tlkShares,
1489 preapprovedKeys: preapprovedKeys) { peerID, ckrecords, syncingPolicy, error in
1490 reply(peerID, ckrecords, syncingPolicy, error)
1495 func onqueueTTRUntrusted() {
1496 let ttr = SecTapToRadar(tapToRadar: "Device not IDMS trusted",
1497 description: "Device not IDMS trusted",
1502 func fetchAfterEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1503 tlkShares: [CKKSTLKShare],
1504 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
1505 self.moc.performAndWait {
1507 try self.deleteLocalCloudKitData()
1509 os_log("fetchAfterEstablish failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1510 reply(nil, [], nil, error)
1513 self.onqueueFetchAndPersistChanges { error in
1514 guard error == nil else {
1515 os_log("fetchAfterEstablish failed to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1516 reply(nil, [], nil, error)
1520 self.moc.performAndWait {
1521 guard let egoPeerID = self.containerMO.egoPeerID,
1522 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1523 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1524 let egoStableData = self.containerMO.egoPeerStableInfo,
1525 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1527 os_log("fetchAfterEstablish: failed to fetch egoPeerID", log: tplogDebug, type: .default)
1528 reply(nil, [], nil, ContainerError.noPreparedIdentity)
1531 guard self.model.hasPeer(withID: egoPeerID) else {
1532 os_log("fetchAfterEstablish: did not find peer %{public}@ in model", log: tplogDebug, type: .default, egoPeerID)
1533 reply(nil, [], nil, ContainerError.invalidPeerID)
1536 let keyFactory = TPECPublicKeyFactory()
1537 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1538 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
1541 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1542 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1543 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
1546 self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares) { ckrecords, error in
1547 guard error == nil else {
1548 os_log("fetchAfterEstablish failed to update TLKs: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1549 reply(nil, [], nil, error)
1554 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
1555 stableInfo: selfStableInfo)
1556 os_log("fetchAfterEstablish succeeded", log: tplogDebug, type: .default)
1557 reply(egoPeerID, ckrecords ?? [], syncingPolicy, nil)
1559 os_log("fetchAfterEstablish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1560 reply(nil, [], nil, error)
1568 func onqueueEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1569 tlkShares: [CKKSTLKShare],
1570 preapprovedKeys: [Data]?,
1571 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
1572 // Fetch ego peer identity from local storage.
1573 guard let egoPeerID = self.containerMO.egoPeerID,
1574 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1575 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1576 let egoStableData = self.containerMO.egoPeerStableInfo,
1577 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1579 reply(nil, [], nil, ContainerError.noPreparedIdentity)
1583 let keyFactory = TPECPublicKeyFactory()
1584 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1585 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
1588 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1589 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1590 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
1593 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
1594 os_log("establish: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
1595 self.onqueueTTRUntrusted()
1596 reply(nil, [], nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
1600 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
1601 guard let egoPeerKeys = egoPeerKeys else {
1602 os_log("Don't have my own peer keys; can't establish: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1603 reply(nil, [], nil, error)
1606 self.moc.performAndWait {
1607 let viewKeys: [ViewKeys] = ckksKeys.map(ViewKeys.convert)
1608 let allTLKShares: [TLKShare]
1610 let octagonShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk }, asPeer: egoPeerKeys, toPeer: egoPeerKeys, epoch: Int(selfPermanentInfo.epoch))
1611 let sosShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
1613 allTLKShares = octagonShares + sosShares
1615 os_log("Unable to make TLKShares for self: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1616 reply(nil, [], nil, error)
1621 let newDynamicInfo: TPPeerDynamicInfo
1623 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
1624 peerPermanentInfo: selfPermanentInfo,
1625 stableInfo: selfStableInfo,
1627 preapprovedKeys: preapprovedKeys,
1629 egoPeerKeys: egoPeerKeys)
1631 os_log("dynamic info: %{public}@", log: tplogDebug, type: .default, newDynamicInfo)
1633 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1634 reply(nil, [], nil, error)
1638 guard let newPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
1639 os_log("Unable to create new peer stable info for joining", log: tplogDebug, type: .default)
1640 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
1646 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
1648 reply(nil, [], nil, error)
1651 os_log("Beginning establish for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
1652 os_log("Establish permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
1653 os_log("Establish permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
1654 os_log("Establish stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
1655 os_log("Establish stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
1656 os_log("Establish dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
1657 os_log("Establish dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
1659 os_log("Establish introducing %d key sets, %d tlk shares", log: tplogDebug, type: .default, viewKeys.count, allTLKShares.count)
1662 os_log("Establish bottle: %{public}@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString())
1663 os_log("Establish peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
1665 os_log("Establish unable to encode bottle/peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
1668 let request = EstablishRequest.with {
1671 $0.viewKeys = viewKeys
1672 $0.tlkShares = allTLKShares
1674 self.cuttlefish.establish(request) { response, error in
1675 os_log("Establish: viewKeys: %{public}@", String(describing: viewKeys))
1676 guard let response = response, error == nil else {
1678 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.establishFailed):
1679 os_log("establish returned failed, trying fetch", log: tplogDebug, type: .default)
1680 self.fetchAfterEstablish(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply)
1683 os_log("establish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1684 reply(nil, [], nil, error ?? ContainerError.cloudkitResponseMissing)
1690 os_log("Establish returned changes: %{public}@", log: tplogDebug, type: .default, try response.changes.jsonString())
1692 os_log("Establish returned changes, but they can't be serialized", log: tplogDebug, type: .default)
1695 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
1698 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
1699 stableInfo: newPeerStableInfo)
1701 try self.persist(changes: response.changes)
1703 guard response.changes.more == false else {
1704 os_log("establish succeeded, but more changes need fetching...", log: tplogDebug, type: .default)
1706 self.fetchAndPersistChanges { fetchError in
1707 guard fetchError == nil else {
1708 // This is an odd error condition: we might be able to fetch again and be in a good state...
1709 os_log("fetch-after-establish failed: %{public}@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error")
1710 reply(nil, keyHierarchyRecords, nil, fetchError)
1714 os_log("fetch-after-establish succeeded", log: tplogDebug, type: .default)
1715 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
1720 os_log("establish succeeded", log: tplogDebug, type: .default)
1721 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
1723 os_log("establish handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1724 reply(nil, keyHierarchyRecords, nil, error)
1731 func setRecoveryKey(recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping ([CKRecord]?, Error?) -> Void) {
1732 self.semaphore.wait()
1733 let reply: ([CKRecord]?, Error?) -> Void = {
1734 os_log("setRecoveryKey complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
1735 self.semaphore.signal()
1739 os_log("beginning a setRecoveryKey", log: tplogDebug, type: .default)
1741 self.moc.performAndWait {
1742 guard let egoPeerID = self.containerMO.egoPeerID else {
1743 os_log("no prepared identity, cannot set recovery key", log: tplogDebug, type: .default)
1744 reply(nil, ContainerError.noPreparedIdentity)
1748 var recoveryKeys: RecoveryKey
1750 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
1752 os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1753 reply(nil, ContainerError.failedToCreateRecoveryKey)
1757 let signingPublicKey: Data = recoveryKeys.peerKeys.signingVerificationKey.keyData
1758 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionVerificationKey.keyData
1760 os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString())
1761 os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString())
1763 guard let stableInfoData = self.containerMO.egoPeerStableInfo else {
1764 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1765 reply(nil, ContainerError.nonMember)
1768 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1769 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1770 reply(nil, ContainerError.nonMember)
1773 guard let permInfoData = self.containerMO.egoPeerPermanentInfo else {
1774 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1775 reply(nil, ContainerError.nonMember)
1778 guard let permInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1779 os_log("permInfoSig does not exist", log: tplogDebug, type: .default)
1780 reply(nil, ContainerError.nonMember)
1783 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
1784 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1785 reply(nil, ContainerError.invalidStableInfoOrSig)
1788 let keyFactory = TPECPublicKeyFactory()
1789 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permInfoData, sig: permInfoSig, keyFactory: keyFactory) else {
1790 os_log("cannot create TPPeerPermanentInfo", log: tplogDebug, type: .default)
1791 reply(nil, ContainerError.invalidStableInfoOrSig)
1795 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
1796 guard let signingKeyPair = signingKeyPair else {
1797 os_log("handle: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1801 self.moc.performAndWait {
1803 let tlkShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
1804 asPeer: recoveryKeys.peerKeys,
1805 toPeer: recoveryKeys.peerKeys,
1806 epoch: Int(permanentInfo.epoch))
1808 let policyVersion = stableInfo.bestPolicyVersion()
1809 let policyDoc = try self.getPolicyDoc(policyVersion.versionNumber)
1811 let updatedStableInfo = try TPPeerStableInfo(clock: stableInfo.clock + 1,
1812 frozenPolicyVersion: frozenPolicyVersion,
1813 flexiblePolicyVersion: policyDoc.version,
1814 policySecrets: stableInfo.policySecrets,
1815 syncUserControllableViews: stableInfo.syncUserControllableViews,
1816 deviceName: stableInfo.deviceName,
1817 serialNumber: stableInfo.serialNumber,
1818 osVersion: stableInfo.osVersion,
1819 signing: signingKeyPair,
1820 recoverySigningPubKey: signingPublicKey,
1821 recoveryEncryptionPubKey: encryptionPublicKey)
1822 let signedStableInfo = SignedPeerStableInfo(updatedStableInfo)
1824 let request = SetRecoveryKeyRequest.with {
1825 $0.peerID = egoPeerID
1826 $0.recoverySigningPubKey = signingPublicKey
1827 $0.recoveryEncryptionPubKey = encryptionPublicKey
1828 $0.stableInfoAndSig = signedStableInfo
1829 $0.tlkShares = tlkShares
1830 $0.changeToken = self.containerMO.changeToken ?? ""
1833 self.cuttlefish.setRecoveryKey(request) { response, error in
1834 guard let response = response, error == nil else {
1835 os_log("setRecoveryKey failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1836 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
1840 self.moc.performAndWait {
1842 self.containerMO.egoPeerStableInfo = updatedStableInfo.data
1843 self.containerMO.egoPeerStableInfoSig = updatedStableInfo.sig
1844 try self.onQueuePersist(changes: response.changes)
1846 os_log("setRecoveryKey succeeded", log: tplogDebug, type: .default)
1848 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
1849 reply(keyHierarchyRecords, nil)
1851 os_log("setRecoveryKey handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1864 func vouchWithBottle(bottleID: String,
1867 tlkShares: [CKKSTLKShare],
1868 reply: @escaping (Data?, Data?, Int64, Int64, Error?) -> Void) {
1869 self.semaphore.wait()
1870 let reply: (Data?, Data?, Int64, Int64, Error?) -> Void = {
1871 os_log("vouchWithBottle complete: %{public}@",
1872 log: tplogTrace, type: .info, traceError($4))
1873 self.semaphore.signal()
1874 reply($0, $1, $2, $3, $4)
1877 // A preflight should have been successful before calling this function. So, we can assume that all required data is stored locally.
1879 self.moc.performAndWait {
1883 (bmo, _, _) = try self.onMOCQueuePerformPreflight(bottleID: bottleID)
1885 os_log("vouchWithBottle failed preflight: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1886 reply(nil, nil, 0, 0, error)
1890 guard let bottledContents = bmo.contents else {
1891 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainContents)
1894 guard let signatureUsingEscrowKey = bmo.signatureUsingEscrowKey else {
1895 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainEscrowKeySignature)
1899 guard let signatureUsingPeerKey = bmo.signatureUsingPeerKey else {
1900 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainerPeerKeySignature)
1903 guard let sponsorPeerID = bmo.peerID else {
1904 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainPeerID)
1908 //verify bottle signature using peer
1910 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1911 os_log("vouchWithBottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1912 reply(nil, nil, 0, 0, ContainerError.bottleCreatingPeerNotFound)
1915 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1916 os_log("vouchWithBottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1917 reply(nil, nil, 0, 0, ContainerError.signatureVerificationFailed)
1921 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1923 os_log("vouchWithBottle: Verification of bottled signature failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1924 reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer)
1928 //create bottled peer
1929 let bottledPeer: BottledPeer
1931 bottledPeer = try BottledPeer(contents: bottledContents,
1933 bottleSalt: bottleSalt,
1934 signatureUsingEscrow: signatureUsingEscrowKey,
1935 signatureUsingPeerKey: signatureUsingPeerKey)
1937 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1940 bottledPeer = try BottledPeer(contents: bottledContents,
1943 signatureUsingEscrow: signatureUsingEscrowKey,
1944 signatureUsingPeerKey: signatureUsingPeerKey)
1946 os_log("Creation of Bottled Peer failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1947 reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer)
1952 os_log("Have a bottle for peer %{public}@", log: tplogDebug, type: .default, bottledPeer.peerID)
1954 // Extract any TLKs we have been given
1955 let (uniqueTLKsRecovered, totalSharesRecovered) = extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys, model: self.model)
1957 self.moc.performAndWait {
1958 // I must have an ego identity in order to vouch using bottle
1959 guard let egoPeerID = self.containerMO.egoPeerID else {
1960 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
1961 reply(nil, nil, 0, 0, ContainerError.nonMember)
1964 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
1965 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1966 reply(nil, nil, 0, 0, ContainerError.nonMember)
1969 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1970 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
1971 reply(nil, nil, 0, 0, ContainerError.nonMember)
1974 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
1975 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1976 reply(nil, nil, 0, 0, ContainerError.nonMember)
1979 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1980 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1981 reply(nil, nil, 0, 0, ContainerError.nonMember)
1984 let keyFactory = TPECPublicKeyFactory()
1985 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
1986 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
1987 reply(nil, nil, 0, 0, ContainerError.invalidPermanentInfoOrSig)
1990 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
1991 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
1992 reply(nil, nil, 0, 0, ContainerError.invalidStableInfoOrSig)
1997 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
1998 stableInfo: beneficiaryStableInfo,
1999 withSponsorID: sponsorPeerID,
2000 reason: TPVoucherReason.restore,
2001 signing: bottledPeer.peerKeys.signingKey)
2002 reply(voucher.data, voucher.sig, uniqueTLKsRecovered, totalSharesRecovered, nil)
2005 os_log("Error creating voucher with bottle: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2006 reply(nil, nil, 0, 0, error)
2013 func vouchWithRecoveryKey(recoveryKey: String,
2015 tlkShares: [CKKSTLKShare],
2016 reply: @escaping (Data?, Data?, Error?) -> Void) {
2017 self.semaphore.wait()
2018 let reply: (Data?, Data?, Error?) -> Void = {
2019 os_log("vouchWithRecoveryKey complete: %{public}@",
2020 log: tplogTrace, type: .info, traceError($2))
2021 self.semaphore.signal()
2025 self.moc.performAndWait {
2026 os_log("beginning a vouchWithRecoveryKey", log: tplogDebug, type: .default)
2028 // I must have an ego identity in order to vouch using bottle
2029 guard let egoPeerID = self.containerMO.egoPeerID else {
2030 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2031 reply(nil, nil, ContainerError.nonMember)
2034 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
2035 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
2036 reply(nil, nil, ContainerError.nonMember)
2039 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
2040 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
2041 reply(nil, nil, ContainerError.nonMember)
2044 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
2045 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
2046 reply(nil, nil, ContainerError.nonMember)
2049 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2050 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
2051 reply(nil, nil, ContainerError.nonMember)
2054 let keyFactory = TPECPublicKeyFactory()
2055 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2056 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2057 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 //create recovery key set
2067 var recoveryKeys: RecoveryKey
2069 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
2071 os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2072 reply(nil, nil, ContainerError.failedToCreateRecoveryKey)
2076 let signingPublicKey: Data = recoveryKeys.peerKeys.signingKey.publicKey.keyData
2077 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionKey.publicKey.keyData
2079 os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString())
2080 os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString())
2082 guard self.model.isRecoveryKeyEnrolled() else {
2083 os_log("Recovery Key is not enrolled", log: tplogDebug, type: .default)
2084 reply(nil, nil, ContainerError.recoveryKeysNotEnrolled)
2088 //find matching peer containing recovery keys
2089 guard let sponsorPeerID = self.model.peerIDThatTrustsRecoveryKeys(TPRecoveryKeyPair(signingKeyData: signingPublicKey, encryptionKeyData: encryptionPublicKey)) else {
2090 os_log("Untrusted recovery key set", log: tplogDebug, type: .default)
2091 reply(nil, nil, ContainerError.untrustedRecoveryKeys)
2095 // We're going to end up trusting every peer that the sponsor peer trusts.
2096 // We might as well trust all TLKShares from those peers at this point.
2097 extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys, sponsorPeerID: sponsorPeerID, model: self.model)
2100 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2101 stableInfo: beneficiaryStableInfo,
2102 withSponsorID: sponsorPeerID,
2103 reason: TPVoucherReason.recoveryKey,
2104 signing: recoveryKeys.peerKeys.signingKey)
2105 reply(voucher.data, voucher.sig, nil)
2108 os_log("Error creating voucher using recovery key set: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2109 reply(nil, nil, error)
2115 func vouch(peerID: String,
2116 permanentInfo: Data,
2117 permanentInfoSig: Data,
2119 stableInfoSig: Data,
2120 ckksKeys: [CKKSKeychainBackedKeySet],
2121 reply: @escaping (Data?, Data?, Error?) -> Void) {
2122 self.semaphore.wait()
2123 let reply: (Data?, Data?, Error?) -> Void = {
2124 os_log("vouch complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2125 self.semaphore.signal()
2129 self.moc.performAndWait {
2130 // I must have an ego identity in order to vouch for someone else.
2131 guard let egoPeerID = self.containerMO.egoPeerID,
2132 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2133 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else {
2134 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2135 reply(nil, nil, ContainerError.nonMember)
2139 let keyFactory = TPECPublicKeyFactory()
2141 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2142 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2146 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: peerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2147 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2148 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2152 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2153 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2154 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2158 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2159 guard let egoPeerKeys = egoPeerKeys else {
2160 os_log("Don't have my own keys: can't vouch for %{public}@(%{public}@): %{public}@", log: tplogDebug, type: .default, peerID, beneficiaryPermanentInfo, (error as CVarArg?) ?? "no error")
2161 reply(nil, nil, error)
2165 self.fetchPolicyDocumentsWithSemaphore(versions: Set([beneficiaryStableInfo.bestPolicyVersion()])) { _, policyFetchError in
2166 guard policyFetchError == nil else {
2167 os_log("Unknown policy for beneficiary: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2168 reply(nil, nil, policyFetchError)
2172 self.moc.performAndWait {
2173 let voucher: TPVoucher
2175 voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2176 stableInfo: beneficiaryStableInfo,
2177 withSponsorID: egoPeerID,
2178 reason: TPVoucherReason.secureChannel,
2179 signing: egoPeerKeys.signingKey)
2181 os_log("Error creating voucher: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2182 reply(nil, nil, error)
2186 // And generate and upload any tlkShares
2187 let tlkShares: [TLKShare]
2189 // Note that this might not be the whole list, so filter some of them out
2190 let peerViews = try? self.model.getViewsForPeer(beneficiaryPermanentInfo,
2191 stableInfo: beneficiaryStableInfo)
2193 // Note: we only want to send up TLKs for uploaded ckks zones
2194 let ckksTLKs = ckksKeys
2195 .filter { !$0.newUpload }
2196 .filter { peerViews?.contains($0.tlk.zoneID.zoneName) ?? false }
2199 tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs,
2200 asPeer: egoPeerKeys,
2201 toPeer: beneficiaryPermanentInfo,
2202 epoch: Int(selfPermanentInfo.epoch))
2204 os_log("Unable to make TLKShares for beneficiary %{public}@(%{public}@): %{public}@", log: tplogDebug, type: .default, peerID, beneficiaryPermanentInfo, error as CVarArg)
2205 reply(nil, nil, error)
2209 guard !tlkShares.isEmpty else {
2210 os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default)
2211 reply(voucher.data, voucher.sig, nil)
2215 self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "",
2217 stableInfoAndSig: nil,
2218 dynamicInfoAndSig: nil,
2219 tlkShares: tlkShares,
2220 viewKeys: []) { response, error in
2221 guard let response = response, error == nil else {
2222 os_log("Unable to upload new tlkshares: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
2223 reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing)
2227 let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init)
2228 os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords)
2229 // We don't need to save these; CKKS will refetch them as needed
2231 reply(voucher.data, voucher.sig, nil)
2239 func departByDistrustingSelf(reply: @escaping (Error?) -> Void) {
2240 self.semaphore.wait()
2241 let reply: (Error?) -> Void = {
2242 os_log("departByDistrustingSelf complete: %{public}@", log: tplogTrace, type: .info, traceError($0))
2243 self.semaphore.signal()
2247 self.moc.performAndWait {
2248 guard let egoPeerID = self.containerMO.egoPeerID else {
2249 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2250 reply(ContainerError.noPreparedIdentity)
2254 self.onqueueDistrust(peerIDs: [egoPeerID], reply: reply)
2258 func distrust(peerIDs: Set<String>,
2259 reply: @escaping (Error?) -> Void) {
2260 self.semaphore.wait()
2261 let reply: (Error?) -> Void = {
2262 os_log("distrust complete: %{public}@", log: tplogTrace, type: .info, traceError($0))
2263 self.semaphore.signal()
2267 self.moc.performAndWait {
2268 guard let egoPeerID = self.containerMO.egoPeerID else {
2269 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2270 reply(ContainerError.noPreparedIdentity)
2274 guard !peerIDs.contains(egoPeerID) else {
2275 os_log("Self-distrust via peerID not allowed", log: tplogDebug, type: .default)
2276 reply(ContainerError.invalidPeerID)
2280 self.onqueueDistrust(peerIDs: peerIDs, reply: reply)
2284 func onqueueDistrust(peerIDs: Set<String>,
2285 reply: @escaping (Error?) -> Void) {
2286 guard let egoPeerID = self.containerMO.egoPeerID else {
2287 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2288 reply(ContainerError.noPreparedIdentity)
2292 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
2293 guard let signingKeyPair = signingKeyPair else {
2294 os_log("No longer have signing key pair; can't sign distrust: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2299 self.moc.performAndWait {
2300 let dynamicInfo: TPPeerDynamicInfo
2302 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
2304 removingPeerIDs: Array(peerIDs),
2305 preapprovedKeys: nil,
2306 signing: signingKeyPair,
2307 currentMachineIDs: self.onqueueCurrentMIDList())
2309 os_log("Error preparing dynamic info: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2314 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
2315 os_log("attempting distrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, peerIDs, dynamicInfo)
2317 let request = UpdateTrustRequest.with {
2318 $0.changeToken = self.containerMO.changeToken ?? ""
2319 $0.peerID = egoPeerID
2320 $0.dynamicInfoAndSig = signedDynamicInfo
2322 self.cuttlefish.updateTrust(request) { response, error in
2323 guard let response = response, error == nil else {
2324 os_log("updateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2325 reply(error ?? ContainerError.cloudkitResponseMissing)
2330 try self.persist(changes: response.changes)
2331 os_log("distrust succeeded", log: tplogDebug, type: .default)
2334 os_log("distrust handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
2342 func fetchEscrowContents(reply: @escaping (Data?, String?, Data?, Error?) -> Void) {
2343 self.semaphore.wait()
2344 let reply: (Data?, String?, Data?, Error?) -> Void = {
2345 os_log("fetchEscrowContents complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
2346 self.semaphore.signal()
2347 reply($0, $1, $2, $3)
2349 os_log("beginning a fetchEscrowContents", log: tplogDebug, type: .default)
2351 self.moc.performAndWait {
2352 guard let egoPeerID = self.containerMO.egoPeerID else {
2353 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2354 reply(nil, nil, nil, ContainerError.noPreparedIdentity)
2358 guard let bottles = self.containerMO.bottles as? Set<BottleMO> else {
2359 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2360 reply(nil, nil, nil, ContainerError.noBottleForPeer)
2364 guard let bmo = bottles.filter({ $0.peerID == egoPeerID }).first else {
2365 os_log("fetchEscrowContents no bottle matches peerID", log: tplogDebug, type: .default)
2366 reply(nil, nil, nil, ContainerError.noBottleForPeer)
2370 let bottleID = bmo.bottleID
2374 guard let loaded = try loadSecret(label: egoPeerID) else {
2375 os_log("fetchEscrowContents failed to load entropy", log: tplogDebug, type: .default)
2376 reply(nil, nil, nil, ContainerError.failedToFetchEscrowContents)
2381 os_log("fetchEscrowContents failed to load entropy: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2382 reply(nil, nil, nil, error)
2386 guard let signingPublicKey = bmo.escrowedSigningSPKI else {
2387 os_log("fetchEscrowContents no escrow signing spki", log: tplogDebug, type: .default)
2388 reply(nil, nil, nil, ContainerError.bottleDoesNotContainerEscrowKeySPKI)
2391 reply(entropy, bottleID, signingPublicKey, nil)
2395 func fetchViableBottles(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2396 self.semaphore.wait()
2397 let reply: ([String]?, [String]?, Error?) -> Void = {
2398 os_log("fetchViableBottles complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2399 self.semaphore.signal()
2403 self.fetchViableBottlesWithSemaphore(reply: reply)
2406 func handleFetchViableBottlesResponseWithSemaphore(response: FetchViableBottlesResponse?) {
2407 guard let escrowPairs = response?.viableBottles else {
2408 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2412 var partialPairs: [EscrowPair] = []
2413 if let partial = response?.partialBottles {
2414 partialPairs = partial
2416 os_log("fetchViableBottles returned no partially viable bottles, but that's ok", log: tplogDebug, type: .default)
2419 var legacyEscrowInformations: [EscrowInformation] = []
2420 if let legacy = response?.legacyRecords {
2421 legacyEscrowInformations = legacy
2423 os_log("fetchViableBottles returned no legacy escrow records", log: tplogDebug, type: .default)
2426 escrowPairs.forEach { pair in
2427 let bottle = pair.bottle
2428 let record = pair.record
2430 // Save this escrow record only if we don't already have it
2431 if let existingRecords = self.containerMO.fullyViableEscrowRecords as? Set<EscrowRecordMO> {
2432 let matchingRecords: Set<EscrowRecordMO> = existingRecords.filter { existing in existing.label == record.label
2433 && existing.escrowMetadata?.bottleID == record.escrowInformationMetadata.bottleID }
2434 if !matchingRecords.isEmpty {
2435 os_log("fetchViableBottles already knows about record, re-adding entry", log: tplogDebug, type: .default, record.label)
2436 self.containerMO.removeFromFullyViableEscrowRecords(matchingRecords as NSSet)
2438 self.setEscrowRecord(record: record, viability: .full)
2441 // Save this bottle only if we don't already have it
2442 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2443 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2444 existing.peerID == bottle.peerID &&
2445 existing.bottleID == bottle.bottleID &&
2446 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2447 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2448 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2449 existing.contents == bottle.contents
2451 if !matchingBottles.isEmpty {
2452 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2457 let bmo = BottleMO(context: self.moc)
2458 bmo.peerID = bottle.peerID
2459 bmo.bottleID = bottle.bottleID
2460 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2461 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2462 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2463 bmo.contents = bottle.contents
2465 os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo)
2466 self.containerMO.addToBottles(bmo)
2469 partialPairs.forEach { pair in
2470 let bottle = pair.bottle
2472 let record = pair.record
2473 // Save this escrow record only if we don't already have it
2475 if let existingRecords = self.containerMO.partiallyViableEscrowRecords as? Set<EscrowRecordMO> {
2476 let matchingRecords: Set<EscrowRecordMO> = existingRecords.filter { existing in existing.label == record.label
2477 && existing.escrowMetadata?.bottleID == record.escrowInformationMetadata.bottleID }
2478 if !matchingRecords.isEmpty {
2479 os_log("fetchViableBottles already knows about record, re-adding entry", log: tplogDebug, type: .default, record.label)
2480 self.containerMO.removeFromPartiallyViableEscrowRecords(matchingRecords as NSSet)
2482 self.setEscrowRecord(record: record, viability: Viability.partial)
2486 // Save this bottle only if we don't already have it
2487 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2488 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2489 existing.peerID == bottle.peerID &&
2490 existing.bottleID == bottle.bottleID &&
2491 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2492 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2493 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2494 existing.contents == bottle.contents
2496 if !matchingBottles.isEmpty {
2497 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2502 let bmo = BottleMO(context: self.moc)
2503 bmo.peerID = bottle.peerID
2504 bmo.bottleID = bottle.bottleID
2505 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2506 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2507 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2508 bmo.contents = bottle.contents
2510 os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo)
2511 self.containerMO.addToBottles(bmo)
2513 legacyEscrowInformations.forEach { record in
2514 // Save this escrow record only if we don't already have it
2515 if let existingRecords = self.containerMO.legacyEscrowRecords as? Set<EscrowRecordMO> {
2516 let matchingRecords: Set<EscrowRecordMO> = existingRecords.filter { existing in existing.label == record.label }
2517 if !matchingRecords.isEmpty {
2518 os_log("fetchViableBottles already knows about legacy record %@, re-adding entry", log: tplogDebug, type: .default, record.label)
2519 self.containerMO.removeFromLegacyEscrowRecords(matchingRecords as NSSet)
2521 if record.label.hasSuffix(".double") {
2522 os_log("ignoring double enrollment record %@", record.label)
2524 self.setEscrowRecord(record: record, viability: Viability.none)
2530 func fetchViableBottlesWithSemaphore(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2531 os_log("beginning a fetchViableBottles", log: tplogDebug, type: .default)
2533 self.moc.performAndWait {
2534 var cachedBottles = TPCachedViableBottles(viableBottles: [], partialBottles: [])
2536 if OctagonIsOptimizationEnabled() {
2537 if let lastDate = self.containerMO.escrowFetchDate {
2538 if Date() < lastDate.addingTimeInterval(escrowCacheTimeout) {
2539 os_log("escrow cache still valid", log: tplogDebug, type: .default)
2540 cachedBottles = onqueueCachedBottlesFromEscrowRecords()
2542 os_log("escrow cache no longer valid", log: tplogDebug, type: .default)
2543 if let records = self.containerMO.fullyViableEscrowRecords {
2544 self.containerMO.removeFromFullyViableEscrowRecords(records)
2546 if let records = self.containerMO.partiallyViableEscrowRecords {
2547 self.containerMO.removeFromPartiallyViableEscrowRecords(records)
2549 self.containerMO.escrowFetchDate = nil
2553 cachedBottles = self.model.currentCachedViableBottlesSet()
2556 if !cachedBottles.viableBottles.isEmpty || !cachedBottles.partialBottles.isEmpty {
2557 os_log("returning from fetchViableBottles, using cached bottles", log: tplogDebug, type: .default)
2558 reply(cachedBottles.viableBottles, cachedBottles.partialBottles, nil)
2562 self.cuttlefish.fetchViableBottles { response, error in
2563 guard error == nil else {
2564 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2565 reply(nil, nil, error)
2569 self.moc.performAndWait {
2570 guard let escrowPairs = response?.viableBottles else {
2571 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2576 var partialPairs: [EscrowPair] = []
2577 if let partial = response?.partialBottles {
2578 partialPairs = partial
2580 os_log("fetchViableBottles returned no partially viable bottles, but that's ok", log: tplogDebug, type: .default)
2583 let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID }
2584 os_log("fetchViableBottles returned viable bottles: %{public}@", log: tplogDebug, type: .default, viableBottleIDs)
2586 let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID }
2587 os_log("fetchViableBottles returned partial bottles: %{public}@", log: tplogDebug, type: .default, partialBottleIDs)
2589 self.handleFetchViableBottlesResponseWithSemaphore(response: response)
2593 os_log("fetchViableBottles saved bottles", log: tplogDebug, type: .default)
2594 let cached = TPCachedViableBottles(viableBottles: viableBottleIDs, partialBottles: partialBottleIDs)
2595 self.model.setViableBottles(cached)
2596 self.containerMO.escrowFetchDate = Date()
2597 reply(viableBottleIDs, partialBottleIDs, nil)
2599 os_log("fetchViableBottles unable to save bottles: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2600 reply(nil, nil, error)
2607 func removeEscrowCache(reply: @escaping (Error?) -> Void) {
2608 os_log("beginning a removeEscrowCache", log: tplogDebug, type: .default)
2610 self.semaphore.wait()
2611 let reply: (Error?) -> Void = {
2612 os_log("removeEscrowCache complete %{public}@", log: tplogTrace, type: .info, traceError($0))
2613 self.semaphore.signal()
2617 self.moc.performAndWait {
2618 self.onQueueRemoveEscrowCache()
2623 private func onQueueRemoveEscrowCache() {
2624 if let records = self.containerMO.fullyViableEscrowRecords {
2625 self.containerMO.removeFromFullyViableEscrowRecords(records)
2627 if let records = self.containerMO.partiallyViableEscrowRecords {
2628 self.containerMO.removeFromPartiallyViableEscrowRecords(records)
2630 if let records = self.containerMO.legacyEscrowRecords {
2631 self.containerMO.removeFromLegacyEscrowRecords(records)
2633 self.containerMO.escrowFetchDate = nil
2636 func fetchEscrowRecordsWithSemaphore(forceFetch: Bool, reply: @escaping ([Data]?, Error?) -> Void) {
2637 os_log("beginning a fetchEscrowRecords", log: tplogDebug, type: .default)
2639 self.moc.performAndWait {
2640 var cachedRecords: [OTEscrowRecord] = []
2642 if forceFetch == false {
2643 os_log("fetchEscrowRecords: force fetch flag is off", log: tplogDebug, type: .default)
2644 if let lastDate = self.containerMO.escrowFetchDate {
2645 if Date() < lastDate.addingTimeInterval(escrowCacheTimeout) {
2646 os_log("escrow cache still valid", log: tplogDebug, type: .default)
2647 cachedRecords = onqueueCachedEscrowRecords()
2649 os_log("escrow cache no longer valid", log: tplogDebug, type: .default)
2650 self.onQueueRemoveEscrowCache()
2654 os_log("fetchEscrowRecords: force fetch flag is on, removing escrow cache", log: tplogDebug, type: .default)
2655 self.onQueueRemoveEscrowCache()
2658 if !cachedRecords.isEmpty {
2659 os_log("returning from fetchEscrowRecords, using cached escrow records", log: tplogDebug, type: .default)
2660 let recordData: [Data] = cachedRecords.map { $0.data }
2661 reply(recordData, nil)
2665 self.cuttlefish.fetchViableBottles { response, error in
2666 guard error == nil else {
2667 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2672 self.moc.performAndWait {
2673 guard response?.viableBottles != nil else {
2674 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2679 self.handleFetchViableBottlesResponseWithSemaphore(response: response)
2684 os_log("fetchViableBottles saved bottles and records", log: tplogDebug, type: .default)
2685 self.containerMO.escrowFetchDate = Date()
2687 var allEscrowRecordData: [Data] = []
2688 if let fullyViableRecords = self.containerMO.fullyViableEscrowRecords as? Set<EscrowRecordMO> {
2689 for record in fullyViableRecords {
2690 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .full) {
2691 allEscrowRecordData.append(r.data)
2695 if let partiallyViableRecords = self.containerMO.partiallyViableEscrowRecords as? Set<EscrowRecordMO> {
2696 for record in partiallyViableRecords {
2697 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .partial) {
2698 allEscrowRecordData.append(r.data)
2702 if let legacyRecords = self.containerMO.legacyEscrowRecords as? Set<EscrowRecordMO> {
2703 for record in legacyRecords {
2704 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .none) {
2705 allEscrowRecordData.append(r.data)
2709 reply(allEscrowRecordData, nil)
2711 os_log("fetchViableBottles unable to save bottles and records: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2718 func fetchCurrentPolicy(modelIDOverride: String?, reply: @escaping (TPSyncingPolicy?, TPPBPeerStableInfo_UserControllableViewStatus, Error?) -> Void) {
2719 self.semaphore.wait()
2720 let reply: (TPSyncingPolicy?, TPPBPeerStableInfo_UserControllableViewStatus, Error?) -> Void = {
2721 os_log("fetchCurrentPolicy complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2722 self.semaphore.signal()
2726 self.moc.performAndWait {
2727 guard let egoPeerID = self.containerMO.egoPeerID,
2728 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2729 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2730 let stableInfoData = self.containerMO.egoPeerStableInfo,
2731 let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2732 os_log("fetchCurrentPolicy failed to find ego peer information", log: tplogDebug, type: .error)
2733 // This is technically an error, but we also need to know the prevailing syncing policy at CloudKit signin time, not just after we've started to join
2735 guard let modelID = modelIDOverride else {
2736 os_log("no model ID override; returning error", log: tplogDebug, type: .default)
2737 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2741 guard let policyDocument = self.model.policy(withVersion: prevailingPolicyVersion.versionNumber) else {
2742 os_log("prevailing policy is missing?", log: tplogDebug, type: .default)
2743 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2748 let prevailingPolicy = try policyDocument.policy(withSecrets: [:], decrypter: Decrypter())
2749 let syncingPolicy = try prevailingPolicy.syncingPolicy(forModel: modelID, syncUserControllableViews: .UNKNOWN)
2751 os_log("returning a policy for model ID %{public}@", log: tplogDebug, type: .default, modelID)
2752 reply(syncingPolicy, .UNKNOWN, nil)
2755 os_log("fetchCurrentPolicy failed to prevailing policy: %{public}@", log: tplogDebug, type: .error)
2756 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2761 let keyFactory = TPECPublicKeyFactory()
2762 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2763 os_log("fetchCurrentPolicy failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
2764 reply(nil, .UNKNOWN, ContainerError.invalidPermanentInfoOrSig)
2767 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
2768 os_log("fetchCurrentPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
2769 reply(nil, .UNKNOWN, ContainerError.invalidStableInfoOrSig)
2774 let syncingPolicy = try self.syncingPolicyFor(modelID: modelIDOverride ?? permanentInfo.modelID, stableInfo: stableInfo)
2776 guard let peer = self.model.peer(withID: permanentInfo.peerID), let dynamicInfo = peer.dynamicInfo else {
2777 os_log("fetchCurrentPolicy with no dynamic info", log: tplogDebug, type: .error)
2778 reply(syncingPolicy, .UNKNOWN, nil)
2782 // Note: we specifically do not want to sanitize this value for the platform: returning FOLLOWING here isn't that helpful
2783 let peersUserViewSyncability = self.model.userViewSyncabilityConsensusAmongTrustedPeers(dynamicInfo)
2784 reply(syncingPolicy, peersUserViewSyncability, nil)
2787 os_log("Fetching the syncing policy failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2788 reply(nil, .UNKNOWN, error)
2794 func syncingPolicyFor(modelID: String, stableInfo: TPPeerStableInfo) throws -> TPSyncingPolicy {
2795 let bestPolicyVersion : TPPolicyVersion
2797 let peerPolicyVersion = stableInfo.bestPolicyVersion()
2798 if peerPolicyVersion.versionNumber < frozenPolicyVersion.versionNumber {
2799 // This peer was from before CKKS4All, and we shouldn't listen to them when it comes to Syncing Policies
2800 bestPolicyVersion = prevailingPolicyVersion
2801 os_log("Ignoring policy version from pre-CKKS4All peer", log: tplogDebug, type: .default)
2804 bestPolicyVersion = peerPolicyVersion
2807 guard let policyDocument = self.model.policy(withVersion: bestPolicyVersion.versionNumber) else {
2808 os_log("best policy is missing?", log: tplogDebug, type: .default)
2809 throw ContainerError.unknownPolicyVersion(prevailingPolicyVersion.versionNumber)
2812 let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2813 return try policy.syncingPolicy(forModel: modelID, syncUserControllableViews: stableInfo.syncUserControllableViews)
2816 // All-or-nothing: return an error in case full list cannot be returned.
2817 // Completion handler data format: [version : [hash, data]]
2818 func fetchPolicyDocuments(versions: Set<TPPolicyVersion>,
2819 reply: @escaping ([TPPolicyVersion: Data]?, Error?) -> Void) {
2820 self.semaphore.wait()
2821 let reply: ([TPPolicyVersion: Data]?, Error?) -> Void = {
2822 os_log("fetchPolicyDocuments complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
2823 self.semaphore.signal()
2827 self.fetchPolicyDocumentsWithSemaphore(versions: versions) { policyDocuments, fetchError in
2828 reply(policyDocuments.flatMap { $0.mapValues { policyDoc in policyDoc.protobuf } }, fetchError)
2832 func fetchPolicyDocumentWithSemaphore(version: TPPolicyVersion,
2833 reply: @escaping (TPPolicyDocument?, Error?) -> Void) {
2834 self.fetchPolicyDocumentsWithSemaphore(versions: Set([version])) { versions, fetchError in
2835 guard fetchError == nil else {
2836 reply(nil, fetchError)
2840 guard let doc = versions?[version] else {
2841 os_log("fetchPolicyDocument: didn't return policy of version: %{public}@", log: tplogDebug, versions ?? "no versions")
2842 reply(nil, ContainerError.unknownPolicyVersion(version.versionNumber))
2850 func fetchPolicyDocumentsWithSemaphore(versions: Set<TPPolicyVersion>,
2851 reply: @escaping ([TPPolicyVersion: TPPolicyDocument]?, Error?) -> Void) {
2852 var remaining = versions
2853 var docs: [TPPolicyVersion: TPPolicyDocument] = [:]
2855 self.moc.performAndWait {
2856 for version in remaining {
2857 if let policydoc = try? self.getPolicyDoc(version.versionNumber), policydoc.version.policyHash == version.policyHash {
2858 docs[policydoc.version] = policydoc
2859 remaining.remove(version)
2864 guard !remaining.isEmpty else {
2869 let request = FetchPolicyDocumentsRequest.with {
2870 $0.keys = remaining.map { version in
2871 PolicyDocumentKey.with { $0.version = version.versionNumber; $0.hash = version.policyHash }}
2874 self.cuttlefish.fetchPolicyDocuments(request) { response, error in
2875 guard let response = response, error == nil else {
2876 os_log("FetchPolicyDocuments failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2877 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
2881 self.moc.performAndWait {
2882 for mapEntry in response.entries {
2883 // TODO: validate the policy's signature
2885 guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else {
2886 os_log("Can't make policy document with hash %{public}@ and data %{public}@",
2887 log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString())
2888 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2892 guard let expectedVersion = (remaining.first { $0.versionNumber == doc.version.versionNumber }) else {
2893 os_log("Received a policy version we didn't request: %d", log: tplogDebug, type: .default, doc.version.versionNumber)
2894 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2898 guard expectedVersion.policyHash == doc.version.policyHash else {
2899 os_log("Requested hash %{public}@ does not match fetched hash %{public}@", log: tplogDebug, type: .default,
2900 expectedVersion.policyHash, doc.version.policyHash)
2901 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2905 remaining.remove(expectedVersion) // Server responses should be unique, let's enforce
2907 docs[doc.version] = doc
2908 self.model.register(doc)
2912 try self.moc.save() // if this fails callers might make bad data assumptions
2918 // Determine if there's anything left to fetch
2919 guard let unfetchedVersion = remaining.first else {
2920 // Nothing remaining? Success!
2925 reply(nil, ContainerError.unknownPolicyVersion(unfetchedVersion.versionNumber))
2930 // Must be on moc queue to call this.
2931 // Caller is responsible for saving the moc afterwards.
2933 private func registerPeerMO(permanentInfo: TPPeerPermanentInfo,
2934 stableInfo: TPPeerStableInfo? = nil,
2935 dynamicInfo: TPPeerDynamicInfo? = nil,
2936 vouchers: [TPVoucher]? = nil,
2937 isEgoPeer: Bool = false) throws -> PeerMO {
2938 let peerID = permanentInfo.peerID
2940 let peer = PeerMO(context: self.moc)
2941 peer.peerID = peerID
2942 peer.permanentInfo = permanentInfo.data
2943 peer.permanentInfoSig = permanentInfo.sig
2944 peer.stableInfo = stableInfo?.data
2945 peer.stableInfoSig = stableInfo?.sig
2946 peer.dynamicInfo = dynamicInfo?.data
2947 peer.dynamicInfoSig = dynamicInfo?.sig
2948 peer.isEgoPeer = isEgoPeer
2949 self.containerMO.addToPeers(peer)
2951 self.model.registerPeer(with: permanentInfo)
2952 if let stableInfo = stableInfo {
2953 try self.model.update(stableInfo, forPeerWithID: peerID)
2955 if let dynamicInfo = dynamicInfo {
2956 try self.model.update(dynamicInfo, forPeerWithID: peerID)
2958 vouchers?.forEach { voucher in
2959 self.model.register(voucher)
2961 if (peer.vouchers as? Set<TPVoucher> ?? Set()).filter({ $0.data == voucher.data && $0.sig == voucher.sig }).isEmpty {
2962 let voucherMO = VoucherMO(context: self.moc)
2963 voucherMO.voucherInfo = voucher.data
2964 voucherMO.voucherInfoSig = voucher.sig
2965 peer.addToVouchers(voucherMO)
2971 /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */
2972 func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet],
2973 tlkShares: [CKKSTLKShare],
2974 egoPeerKeys: OctagonSelfPeerKeys,
2975 egoPeerDynamicInfo: TPPeerDynamicInfo,
2976 epoch: Int) throws -> ([ViewKeys], [TLKShare]) {
2977 let newCKKSKeys = ckksKeys.filter { $0.newUpload }
2978 let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert)
2980 let octagonSelfShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
2981 asPeer: egoPeerKeys,
2982 toPeer: egoPeerKeys,
2984 let extraShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
2986 var peerShares: [TLKShare] = []
2988 for keyset in newCKKSKeys {
2990 let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo,
2991 toAccessView: keyset.tlk.zoneID.zoneName)
2992 os_log("Planning to share %@ with peers %{public}@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess)
2994 let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) }
2995 let viewPeerShares = try peers.map { receivingPeer in
2996 TLKShare.convert(ckksTLKShare: try CKKSTLKShare(keyset.tlk,
2998 to: receivingPeer.permanentInfo,
3003 peerShares += viewPeerShares
3005 os_log("Unable to create TLKShares for keyset %@: %{public}@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg)
3009 return (newViewKeys, octagonSelfShares + peerShares + extraShares)
3012 func onqueuePreparePeerForJoining(egoPeerID: String,
3013 peerPermanentInfo: TPPeerPermanentInfo,
3014 stableInfo: TPPeerStableInfo,
3016 preapprovedKeys: [Data]?,
3017 vouchers: [SignedVoucher],
3018 egoPeerKeys: OctagonSelfPeerKeys) throws -> (Peer, TPPeerDynamicInfo) {
3019 let dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
3020 peerPermanentInfo: peerPermanentInfo,
3021 peerStableInfo: stableInfo,
3022 sponsorID: sponsorID,
3023 preapprovedKeys: preapprovedKeys,
3024 signing: egoPeerKeys.signingKey,
3025 currentMachineIDs: self.onqueueCurrentMIDList())
3027 let userViewSyncability: TPPBPeerStableInfo_UserControllableViewStatus?
3028 if [.ENABLED, .DISABLED].contains(stableInfo.syncUserControllableViews) {
3030 userViewSyncability = nil
3032 let newUserViewSyncability: TPPBPeerStableInfo_UserControllableViewStatus
3034 if peerPermanentInfo.modelID.hasPrefix("AppleTV") ||
3035 peerPermanentInfo.modelID.hasPrefix("AudioAccessory") ||
3036 peerPermanentInfo.modelID.hasPrefix("Watch") {
3037 // Watches, TVs, and AudioAccessories always join as FOLLOWING.
3038 newUserViewSyncability = .FOLLOWING
3040 // All other platforms select what the other devices say to do
3041 newUserViewSyncability = self.model.userViewSyncabilityConsensusAmongTrustedPeers(dynamicInfo)
3044 os_log("join: setting 'user view sync' control as: %{public}@", log: tplogDebug, type: .default,
3045 TPPBPeerStableInfo_UserControllableViewStatusAsString(newUserViewSyncability))
3046 userViewSyncability = newUserViewSyncability
3049 let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: StableChanges.change(viewStatus: userViewSyncability),
3050 permanentInfo: peerPermanentInfo,
3051 existingStableInfo: stableInfo,
3052 dynamicInfo: dynamicInfo,
3053 signingKeyPair: egoPeerKeys.signingKey)
3055 let peer = Peer.with {
3056 $0.peerID = egoPeerID
3057 $0.permanentInfoAndSig = SignedPeerPermanentInfo(peerPermanentInfo)
3058 $0.stableInfoAndSig = SignedPeerStableInfo(newStableInfo ?? stableInfo)
3059 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3060 $0.vouchers = vouchers
3063 return (peer, dynamicInfo)
3066 func join(voucherData: Data,
3068 ckksKeys: [CKKSKeychainBackedKeySet],
3069 tlkShares: [CKKSTLKShare],
3070 preapprovedKeys: [Data]?,
3071 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
3072 self.semaphore.wait()
3073 let reply: (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void = {
3074 os_log("join complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
3075 self.semaphore.signal()
3076 reply($0, $1, $2, $3)
3079 self.fetchAndPersistChanges { error in
3080 guard error == nil else {
3081 reply(nil, [], nil, error)
3085 // To join, you must know all policies that exist
3086 let allPolicyVersions = self.model.allPolicyVersions()
3087 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3088 if let error = policyFetchError {
3089 os_log("join: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3092 self.moc.performAndWait {
3093 guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else {
3094 reply(nil, [], nil, ContainerError.invalidVoucherOrSig)
3097 guard let sponsor = self.model.peer(withID: voucher.sponsorID) else {
3098 reply(nil, [], nil, ContainerError.sponsorNotRegistered(voucher.sponsorID))
3102 // Fetch ego peer identity from local storage.
3103 guard let egoPeerID = self.containerMO.egoPeerID,
3104 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3105 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3106 let egoStableData = self.containerMO.egoPeerStableInfo,
3107 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3109 reply(nil, [], nil, ContainerError.noPreparedIdentity)
3113 let keyFactory = TPECPublicKeyFactory()
3114 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3115 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
3118 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3119 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3122 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3123 os_log("join: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3124 self.onqueueTTRUntrusted()
3125 reply(nil, [], nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3129 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3130 guard let egoPeerKeys = egoPeerKeys else {
3131 os_log("Don't have my own peer keys; can't join: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3132 reply(nil, [], nil, error)
3135 self.moc.performAndWait {
3137 let newDynamicInfo: TPPeerDynamicInfo
3139 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3140 peerPermanentInfo: selfPermanentInfo,
3141 stableInfo: selfStableInfo,
3142 sponsorID: sponsor.peerID,
3143 preapprovedKeys: preapprovedKeys,
3144 vouchers: [SignedVoucher.with {
3145 $0.voucher = voucher.data
3146 $0.sig = voucher.sig
3148 egoPeerKeys: egoPeerKeys)
3150 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3151 reply(nil, [], nil, error)
3155 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3156 os_log("Unable to create new peer stable info for joining", log: tplogDebug, type: .default)
3157 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3161 let allTLKShares: [TLKShare]
3162 let viewKeys: [ViewKeys]
3164 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3165 tlkShares: tlkShares,
3166 egoPeerKeys: egoPeerKeys,
3167 egoPeerDynamicInfo: newDynamicInfo,
3168 epoch: Int(selfPermanentInfo.epoch))
3170 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3171 reply(nil, [], nil, error)
3176 try self.model.checkIntroduction(forCandidate: selfPermanentInfo,
3177 stableInfo: peer.stableInfoAndSig.toStableInfo(),
3178 withSponsorID: sponsor.peerID)
3180 os_log("Error checking introduction: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3181 reply(nil, [], nil, error)
3187 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3189 reply(nil, [], nil, error)
3193 os_log("Beginning join for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3194 os_log("Join permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3195 os_log("Join permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3196 os_log("Join stableInfo: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString())
3197 os_log("Join stableInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString())
3198 os_log("Join dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3199 os_log("Join dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3201 os_log("Join vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3202 os_log("Join voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3204 os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3207 os_log("Join peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3209 os_log("Join unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3212 let changeToken = self.containerMO.changeToken ?? ""
3213 let request = JoinWithVoucherRequest.with {
3214 $0.changeToken = changeToken
3217 $0.tlkShares = allTLKShares
3218 $0.viewKeys = viewKeys
3220 self.cuttlefish.joinWithVoucher(request) { response, error in
3221 guard let response = response, error == nil else {
3222 os_log("joinWithVoucher failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3223 reply(nil, [], nil, error ?? ContainerError.cloudkitResponseMissing)
3227 self.moc.performAndWait {
3229 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3230 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3232 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
3233 stableInfo: peerStableInfo)
3235 try self.onQueuePersist(changes: response.changes)
3236 os_log("JoinWithVoucher succeeded", log: tplogDebug)
3238 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3239 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
3241 os_log("JoinWithVoucher failed: %{public}@", log: tplogDebug, String(describing: error))
3242 reply(nil, [], nil, error)
3253 func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Bool, Error?) -> Void) {
3254 self.semaphore.wait()
3255 let reply: (Bool, Bool, Bool, Bool, Error?) -> Void = {
3256 os_log("health check complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
3257 self.semaphore.signal()
3258 reply($0, $1, $2, $3, $4)
3261 os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck)
3263 self.moc.performAndWait {
3264 guard let egoPeerID = self.containerMO.egoPeerID else {
3265 // No identity, nothing to do
3266 os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default)
3267 reply(false, false, false, false, ContainerError.noPreparedIdentity)
3270 let request = GetRepairActionRequest.with {
3271 $0.peerID = egoPeerID
3272 $0.requiresEscrowCheck = requiresEscrowCheck
3275 self.cuttlefish.getRepairAction(request) { response, error in
3276 guard error == nil else {
3277 reply(false, false, false, false, error)
3280 guard let action = response?.repairAction else {
3281 os_log("repair response is empty, returning false", log: tplogDebug, type: .default)
3282 reply(false, false, false, false, nil)
3285 var postRepairAccount: Bool = false
3286 var postRepairEscrow: Bool = false
3287 var resetOctagon: Bool = false
3288 var leaveTrust: Bool = false
3293 case .postRepairAccount:
3294 postRepairAccount = true
3295 case .postRepairEscrow:
3296 postRepairEscrow = true
3304 reply(postRepairAccount, postRepairEscrow, resetOctagon, leaveTrust, nil)
3309 func getSupportAppInfo(reply: @escaping (Data?, Error?) -> Void) {
3310 self.semaphore.wait()
3311 let reply: (Data?, Error?) -> Void = {
3312 os_log("getSupportAppInfo complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3313 self.semaphore.signal()
3317 self.cuttlefish.getSupportAppInfo { response, error in
3318 guard let response = response, error == nil else {
3319 os_log("getSupportAppInfo failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3320 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3324 guard let data = try? response.serializedData() else {
3325 reply(nil, ContainerError.failedToSerializeData)
3333 func preflightPreapprovedJoin(preapprovedKeys: [Data]?,
3334 reply: @escaping (Bool, Error?) -> Void) {
3335 self.semaphore.wait()
3336 let reply: (Bool, Error?) -> Void = {
3337 os_log("preflightPreapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3338 self.semaphore.signal()
3342 self.fetchAndPersistChanges { error in
3343 guard error == nil else {
3344 os_log("preflightPreapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3349 // We need to try to have all policy versions that our peers claim to behave
3350 let allPolicyVersions = self.model.allPolicyVersions()
3351 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3352 if let error = policyFetchError {
3353 os_log("preflightPreapprovedJoin: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3356 // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us?
3358 guard !self.model.allPeerIDs().isEmpty else {
3359 // If, after fetch and handle changes, there's no peers, then we can likely establish.
3364 guard let egoPeerID = self.containerMO.egoPeerID,
3365 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3366 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3368 os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3369 reply(false, ContainerError.noPreparedIdentity)
3373 let keyFactory = TPECPublicKeyFactory()
3374 guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3375 os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug)
3376 reply(false, ContainerError.invalidPermanentInfoOrSig)
3380 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else {
3381 os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3382 reply(false, ContainerError.noPeersPreapprovePreparedIdentity)
3386 let keysApprovingPeers = preapprovedKeys?.filter { key in
3387 self.model.hasPotentiallyTrustedPeer(withSigningKey: key)
3390 guard (keysApprovingPeers?.count ?? 0) > 0 else {
3391 os_log("preflightPreapprovedJoin: no reciprocal trust for existing peers", log: tplogDebug, type: .debug)
3392 reply(false, ContainerError.noPeersPreapprovedBySelf)
3401 func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet],
3402 tlkShares: [CKKSTLKShare],
3403 preapprovedKeys: [Data]?,
3404 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
3405 self.semaphore.wait()
3406 let reply: (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void = {
3407 os_log("preapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
3408 self.semaphore.signal()
3409 reply($0, $1, $2, $3)
3412 self.fetchAndPersistChangesIfNeeded { error in
3413 guard error == nil else {
3414 os_log("preapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3415 reply(nil, [], nil, error)
3418 self.moc.performAndWait {
3419 // If, after fetch and handle changes, there's no peers, then fire off an establish
3420 // Note that if the establish fails, retrying this call might work.
3421 // That's up to the caller.
3422 if self.model.allPeerIDs().isEmpty {
3423 os_log("preapprovedJoin but no existing peers, attempting establish", log: tplogDebug, type: .debug)
3425 self.onqueueEstablish(ckksKeys: ckksKeys,
3426 tlkShares: tlkShares,
3427 preapprovedKeys: preapprovedKeys,
3432 // Fetch ego peer identity from local storage.
3433 guard let egoPeerID = self.containerMO.egoPeerID,
3434 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3435 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3436 let egoStableData = self.containerMO.egoPeerStableInfo,
3437 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3439 os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3440 reply(nil, [], nil, ContainerError.noPreparedIdentity)
3444 let keyFactory = TPECPublicKeyFactory()
3445 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3446 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
3449 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3450 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3454 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3455 os_log("preapprovedJoin: self machineID %{public}@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3456 self.onqueueTTRUntrusted()
3457 reply(nil, [], nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3460 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3461 guard let egoPeerKeys = egoPeerKeys else {
3462 os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3463 reply(nil, [], nil, error)
3467 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else {
3468 os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3469 reply(nil, [], nil, ContainerError.noPeersPreapprovePreparedIdentity)
3473 self.moc.performAndWait {
3475 let newDynamicInfo: TPPeerDynamicInfo
3477 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3478 peerPermanentInfo: selfPermanentInfo,
3479 stableInfo: selfStableInfo,
3481 preapprovedKeys: preapprovedKeys,
3483 egoPeerKeys: egoPeerKeys)
3485 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3486 reply(nil, [], nil, error)
3490 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3491 os_log("Unable to create new peer stable info for joining", log: tplogDebug, type: .default)
3492 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3496 let allTLKShares: [TLKShare]
3497 let viewKeys: [ViewKeys]
3499 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3500 tlkShares: tlkShares,
3501 egoPeerKeys: egoPeerKeys,
3502 egoPeerDynamicInfo: newDynamicInfo,
3503 epoch: Int(selfPermanentInfo.epoch))
3505 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3506 reply(nil, [], nil, error)
3512 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3514 reply(nil, [], nil, error)
3518 os_log("Beginning preapprovedJoin for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3519 os_log("preapprovedJoin permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3520 os_log("preapprovedJoin permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3521 os_log("preapprovedJoin stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
3522 os_log("preapprovedJoin stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
3523 os_log("preapprovedJoin dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3524 os_log("preapprovedJoin dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3526 os_log("preapprovedJoin vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3527 os_log("preapprovedJoin voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3529 os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3532 os_log("preapprovedJoin peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3534 os_log("preapprovedJoin unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3537 let changeToken = self.containerMO.changeToken ?? ""
3538 let request = JoinWithVoucherRequest.with {
3539 $0.changeToken = changeToken
3542 $0.tlkShares = allTLKShares
3543 $0.viewKeys = viewKeys
3545 self.cuttlefish.joinWithVoucher(request) { response, error in
3546 guard let response = response, error == nil else {
3547 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3548 reply(nil, [], nil, error ?? ContainerError.cloudkitResponseMissing)
3552 self.moc.performAndWait {
3554 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3555 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3557 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
3558 stableInfo: peerStableInfo)
3560 try self.onQueuePersist(changes: response.changes)
3561 os_log("preapprovedJoin succeeded", log: tplogDebug)
3563 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3564 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
3566 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, String(describing: error))
3567 reply(nil, [], nil, error)
3577 func update(deviceName: String?,
3578 serialNumber: String?,
3580 policyVersion: UInt64?,
3581 policySecrets: [String: Data]?,
3582 syncUserControllableViews: TPPBPeerStableInfo_UserControllableViewStatus?,
3583 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3584 self.semaphore.wait()
3585 let reply: (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void = {
3586 os_log("update complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
3587 self.semaphore.signal()
3591 // Get (and save) the latest from cuttlefish
3592 let stableChanges = StableChanges(deviceName: deviceName,
3593 serialNumber: serialNumber,
3594 osVersion: osVersion,
3595 policyVersion: policyVersion,
3596 policySecrets: policySecrets,
3597 setSyncUserControllableViews: syncUserControllableViews)
3598 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply)
3601 func set(preapprovedKeys: [Data],
3602 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3603 self.semaphore.wait()
3604 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3605 os_log("setPreapprovedKeys complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3606 self.semaphore.signal()
3610 self.moc.performAndWait {
3611 os_log("setPreapprovedKeys: %@", log: tplogDebug, type: .default, preapprovedKeys)
3613 guard let egoPeerID = self.containerMO.egoPeerID else {
3614 // No identity, nothing to do
3615 os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default)
3616 reply(nil, ContainerError.noPreparedIdentity)
3619 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3620 guard let signingKeyPair = signingKeyPair else {
3621 os_log("setPreapprovedKeys: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3622 reply(nil, error ?? ContainerError.unableToCreateKeyPair)
3626 self.moc.performAndWait {
3627 let dynamicInfo: TPPeerDynamicInfo
3629 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3631 removingPeerIDs: nil,
3632 preapprovedKeys: preapprovedKeys,
3633 signing: signingKeyPair,
3634 currentMachineIDs: self.onqueueCurrentMIDList())
3636 os_log("setPreapprovedKeys: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3641 os_log("setPreapprovedKeys: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
3643 if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo {
3644 os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default)
3646 // Calling this will fill in the peer status
3647 self.updateTrustIfNeeded { status, _, error in
3648 reply(status, error)
3653 os_log("setPreapprovedKeys: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3654 let request = UpdateTrustRequest.with {
3655 $0.changeToken = self.containerMO.changeToken ?? ""
3656 $0.peerID = egoPeerID
3657 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3660 self.perform(updateTrust: request) { state, _, error in
3661 guard error == nil else {
3662 os_log("setPreapprovedKeys: failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
3667 os_log("setPreapprovedKeys: updateTrust succeeded", log: tplogDebug, type: .default)
3675 func updateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3676 tlkShares: [CKKSTLKShare],
3677 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3678 self.semaphore.wait()
3679 let reply: ([CKRecord]?, Error?) -> Void = {
3680 os_log("updateTLKs complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3681 self.semaphore.signal()
3685 os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys)
3687 self.moc.performAndWait {
3688 self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply)
3692 func onqueueUpdateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3693 tlkShares: [CKKSTLKShare],
3694 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3695 guard let egoPeerID = self.containerMO.egoPeerID,
3696 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3697 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3699 os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default)
3700 reply(nil, ContainerError.noPreparedIdentity)
3704 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else {
3705 os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys)
3706 reply(nil, ContainerError.invalidPermanentInfoOrSig)
3710 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3711 guard let egoPeerKeys = egoPeerKeys else {
3712 os_log("Don't have my own peer keys; can't upload new TLKs: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3716 self.moc.performAndWait {
3717 guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else {
3718 os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default)
3719 reply(nil, ContainerError.missingDynamicInfo)
3723 let allTLKShares: [TLKShare]
3724 let viewKeys: [ViewKeys]
3726 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3727 tlkShares: tlkShares,
3728 egoPeerKeys: egoPeerKeys,
3729 egoPeerDynamicInfo: egoPeerDynamicInfo,
3730 epoch: Int(selfPermanentInfo.epoch))
3732 os_log("Unable to process keys before uploading: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3737 let request = UpdateTrustRequest.with {
3738 $0.changeToken = self.containerMO.changeToken ?? ""
3739 $0.peerID = egoPeerID
3740 $0.tlkShares = allTLKShares
3741 $0.viewKeys = viewKeys
3744 self.cuttlefish.updateTrust(request) { response, error in
3745 guard error == nil else {
3750 let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? []
3751 os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count)
3752 reply(keyHierarchyRecords, nil)
3758 func getState(reply: @escaping (ContainerState) -> Void) {
3759 self.semaphore.wait()
3760 let reply: (ContainerState) -> Void = {
3761 os_log("getState complete: %{public}@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
3762 self.semaphore.signal()
3766 self.moc.performAndWait {
3767 var state = ContainerState()
3768 state.egoPeerID = self.containerMO.egoPeerID
3770 if self.containerMO.bottles != nil {
3771 self.containerMO.bottles!.forEach { bottle in
3772 state.bottles.insert(bottle as! BottleMO)
3776 if self.containerMO.fullyViableEscrowRecords != nil {
3777 self.containerMO.fullyViableEscrowRecords!.forEach { record in
3778 state.escrowRecords.insert(record as! EscrowRecordMO)
3782 if self.containerMO.partiallyViableEscrowRecords != nil {
3783 self.containerMO.partiallyViableEscrowRecords!.forEach { record in
3784 state.escrowRecords.insert(record as! EscrowRecordMO)
3788 self.model.allPeers().forEach { peer in
3789 state.peers[peer.peerID] = peer
3791 state.vouchers = Array(self.model.allVouchers())
3796 // This will only fetch changes if no changes have ever been fetched before
3797 func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) {
3798 self.moc.performAndWait {
3799 if self.containerMO.changeToken == nil {
3800 self.onqueueFetchAndPersistChanges(reply: reply)
3807 func fetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3808 self.moc.performAndWait {
3809 self.onqueueFetchAndPersistChanges(reply: reply)
3813 private func onqueueFetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3814 let request = FetchChangesRequest.with {
3815 $0.changeToken = self.containerMO.changeToken ?? ""
3817 os_log("Fetching with change token: %{public}@", log: tplogDebug, type: .default, !request.changeToken.isEmpty ? request.changeToken : "empty")
3819 self.cuttlefish.fetchChanges(request) { response, error in
3820 guard let response = response, error == nil else {
3822 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired):
3823 os_log("change token is expired; resetting local CK storage", log: tplogDebug, type: .default)
3825 self.moc.performAndWait {
3827 try self.deleteLocalCloudKitData()
3829 os_log("Failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3834 self.fetchAndPersistChanges(reply: reply)
3839 os_log("Fetch error is an unknown error: %{public}@", log: tplogDebug, type: .default, String(describing: error))
3842 os_log("Could not fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3848 try self.persist(changes: response.changes)
3850 os_log("Could not persist changes: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3855 if response.changes.more {
3856 os_log("persist: More changes indicated. Fetching...", log: tplogDebug, type: .default)
3857 self.fetchAndPersistChanges(reply: reply)
3860 os_log("persist: no more changes!", log: tplogDebug, type: .default)
3867 // Check for delta update in trust lists, that should lead to update of TLKShares
3869 private func haveTrustMemberChanges(newDynamicInfo: TPPeerDynamicInfo, oldDynamicInfo: TPPeerDynamicInfo?) -> Bool {
3870 guard let oldDynamicInfo = oldDynamicInfo else {
3873 if newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs {
3876 if newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs {
3879 if newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals {
3885 // Fetch and persist changes from Cuttlefish. If this results
3886 // in us calculating a new dynamicInfo then give the new dynamicInfo
3887 // to Cuttlefish. If Cuttlefish returns more changes then persist
3888 // them locally, update dynamicInfo if needed, and keep doing that
3889 // until dynamicInfo is unchanged and there are no more changes to fetch.
3891 // Must be holding the semaphore to call this, and it remains
3892 // the caller's responsibility to release it after it completes
3893 // (i.e. after reply is invoked).
3894 internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil,
3895 peerChanges: Bool = false,
3896 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3897 self.fetchAndPersistChanges { error in
3898 if let error = error {
3899 os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3900 reply(nil, nil, error)
3904 self.updateTrustIfNeeded(stableChanges: stableChanges, peerChanges: peerChanges, reply: reply)
3908 // If this results in us calculating a new dynamicInfo then,
3909 // upload the new dynamicInfo
3910 // to Cuttlefish. If Cuttlefish returns more changes then persist
3911 // them locally, update dynamicInfo if needed, and keep doing that
3912 // until dynamicInfo is unchanged and there are no more changes to fetch.
3914 // Must be holding the semaphore to call this, and it remains
3915 // the caller's responsibility to release it after it completes
3916 // (i.e. after reply is invoked).
3917 private func updateTrustIfNeeded(stableChanges: StableChanges? = nil,
3918 peerChanges: Bool = false,
3919 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3920 self.moc.performAndWait {
3921 guard let egoPeerID = self.containerMO.egoPeerID else {
3922 // No identity, nothing to do
3923 os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default)
3924 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil),
3929 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3930 guard let signingKeyPair = signingKeyPair else {
3931 os_log("updateTrustIfNeeded: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3932 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil),
3937 guard let currentSelfInModel = self.model.peer(withID: egoPeerID) else {
3938 // Not in circle, nothing to do
3939 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(signingKeyPair.publicKey().spki())
3940 os_log("updateTrustIfNeeded: ego peer is not in model, is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
3941 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3942 isPreapproved: isPreapproved,
3944 memberChanges: peerChanges,
3945 unknownMachineIDs: false,
3952 // We need to try to have all policy versions that our peers claim to behave
3953 let allPolicyVersions = self.model.allPolicyVersions()
3954 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3955 if let error = policyFetchError {
3956 os_log("updateTrustIfNeeded: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3959 self.moc.performAndWait {
3960 let dynamicInfo: TPPeerDynamicInfo
3961 var stableInfo: TPPeerStableInfo?
3963 // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair,
3964 // and then only load the key if it has changed and we need to sign a new one. This would also
3965 // help make our detection of change immune from non-canonical serialization of dynamicInfo.
3966 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3968 removingPeerIDs: nil,
3969 preapprovedKeys: nil,
3970 signing: signingKeyPair,
3971 currentMachineIDs: self.onqueueCurrentMIDList())
3973 stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges,
3974 permanentInfo: currentSelfInModel.permanentInfo,
3975 existingStableInfo: currentSelfInModel.stableInfo,
3976 dynamicInfo: dynamicInfo,
3977 signingKeyPair: signingKeyPair)
3979 os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3980 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3981 isPreapproved: false,
3982 status: self.model.statusOfPeer(withID: egoPeerID),
3983 memberChanges: peerChanges,
3984 unknownMachineIDs: false,
3991 os_log("updateTrustIfNeeded: produced a stableInfo: %{public}@", log: tplogDebug, type: .default, String(describing: stableInfo))
3992 os_log("updateTrustIfNeeded: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
3994 let peer = self.model.peer(withID: egoPeerID)
3995 if (stableInfo == nil || stableInfo == peer?.stableInfo) &&
3996 dynamicInfo == peer?.dynamicInfo {
3997 os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default)
3998 // No change to the dynamicInfo: update the MID list now that we've reached a steady state
4000 self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo)
4003 os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4006 let syncingPolicy: TPSyncingPolicy?
4008 if let peer = self.model.peer(withID: egoPeerID), let stableInfo = peer.stableInfo {
4009 syncingPolicy = try self.syncingPolicyFor(modelID: peer.permanentInfo.modelID, stableInfo: stableInfo)
4014 os_log("updateTrustIfNeeded: unable to compute a new syncing policy: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4018 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
4019 isPreapproved: false,
4020 status: self.model.statusOfPeer(withID: egoPeerID),
4021 memberChanges: peerChanges,
4022 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
4023 osVersion: peer?.stableInfo?.osVersion),
4028 // Check if we change that should trigger a notification that should trigger TLKShare updates
4029 let havePeerChanges = peerChanges || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo)
4031 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
4032 os_log("updateTrustIfNeeded: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
4033 var request = UpdateTrustRequest.with {
4034 $0.changeToken = self.containerMO.changeToken ?? ""
4035 $0.peerID = egoPeerID
4036 $0.dynamicInfoAndSig = signedDynamicInfo
4038 if let stableInfo = stableInfo {
4039 request.stableInfoAndSig = SignedPeerStableInfo(stableInfo)
4042 self.perform(updateTrust: request, stableChanges: stableChanges, peerChanges: havePeerChanges, reply: reply)
4049 private func perform(updateTrust request: UpdateTrustRequest,
4050 stableChanges: StableChanges? = nil,
4051 peerChanges: Bool = false,
4052 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
4053 self.cuttlefish.updateTrust(request) { response, error in
4054 guard let response = response, error == nil else {
4055 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
4056 reply(nil, nil, error ?? ContainerError.cloudkitResponseMissing)
4061 try self.persist(changes: response.changes)
4063 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, String(describing: error))
4064 reply(nil, nil, error)
4068 if response.changes.more {
4069 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges,
4070 peerChanges: peerChanges,
4073 self.updateTrustIfNeeded(stableChanges: stableChanges,
4074 peerChanges: peerChanges,
4080 private func persist(changes: Changes) throws {
4081 // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait.
4082 // So, do it ourself
4083 var outsideBlockError: Error?
4085 self.moc.performAndWait {
4086 os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default,
4087 changes.differences.count,
4089 os_log("persist: New change token: %{public}@", log: tplogDebug, type: .default, changes.changeToken)
4092 try self.onQueuePersist(changes: changes)
4094 outsideBlockError = error
4098 if let outsideBlockError = outsideBlockError {
4099 throw outsideBlockError
4103 // Must be on moc queue to call this.
4104 // Changes are registered in the model and stored in moc.
4105 private func onQueuePersist(changes: Changes) throws {
4106 self.containerMO.changeToken = changes.changeToken
4107 self.containerMO.moreChanges = changes.more
4109 if !changes.differences.isEmpty {
4110 self.model.clearViableBottles()
4111 os_log("escrow cache and viable bottles are no longer valid", log: tplogDebug, type: .default)
4112 self.onQueueRemoveEscrowCache()
4115 try changes.differences.forEach { peerDifference in
4116 if let operation = peerDifference.operation {
4118 case .add(let peer), .update(let peer):
4119 try self.addOrUpdate(peer: peer)
4120 // Update containerMO ego data if it has changed.
4121 if peer.peerID == self.containerMO.egoPeerID {
4122 guard let stableInfoAndSig: TPPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
4125 self.containerMO.egoPeerStableInfo = stableInfoAndSig.data
4126 self.containerMO.egoPeerStableInfoSig = stableInfoAndSig.sig
4129 case .remove(let peer):
4130 self.model.deletePeer(withID: peer.peerID)
4131 if let peerMO = try self.fetchPeerMO(peerID: peer.peerID) {
4132 self.moc.delete(peerMO)
4138 let signingKey = changes.recoverySigningPubKey
4139 let encryptionKey = changes.recoveryEncryptionPubKey
4141 if !signingKey.isEmpty && !encryptionKey.isEmpty {
4142 self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey)
4147 // Must be on moc queue to call this
4148 // Deletes all things that should come back from CloudKit. Keeps the egoPeer data, though.
4149 private func deleteLocalCloudKitData() throws {
4150 os_log("Deleting all CloudKit data", log: tplogDebug, type: .default)
4153 let peerRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
4154 peerRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
4155 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: peerRequest))
4157 // If we have an ego peer ID, keep the bottle associated with it
4158 if let peerID = self.containerMO.egoPeerID, let bottles = self.containerMO.bottles {
4159 let nonPeerBottles = NSSet(array: bottles.filter {
4161 case let bottleMO as BottleMO:
4162 return bottleMO.peerID != peerID
4167 self.containerMO.removeFromBottles(nonPeerBottles as NSSet)
4169 let bottleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Bottle")
4170 bottleRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
4171 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: bottleRequest))
4172 self.containerMO.bottles = nil
4175 self.containerMO.peers = nil
4176 self.containerMO.changeToken = nil
4177 self.containerMO.moreChanges = false
4179 self.model = Container.loadModel(from: self.containerMO)
4182 os_log("Local delete failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4186 os_log("Saved model with %d peers", log: tplogDebug, type: .default, self.model.allPeerIDs().count)
4189 // Must be on moc queue to call this.
4190 private func addOrUpdate(signingKey: Data, encryptionKey: Data) {
4191 self.model.setRecoveryKeys(
4192 TPRecoveryKeyPair(signingKeyData: signingKey, encryptionKeyData: encryptionKey))
4194 self.containerMO.recoveryKeySigningSPKI = signingKey
4195 self.containerMO.recoveryKeyEncryptionSPKI = encryptionKey
4198 // Must be on moc queue to call this.
4199 private func addOrUpdate(peer: Peer) throws {
4200 if !self.model.hasPeer(withID: peer.peerID) {
4202 guard let permanentInfo = peer.permanentInfoAndSig.toPermanentInfo(peerID: peer.peerID) else {
4203 // Ignoring bad peer
4206 let stableInfo = peer.stableInfoAndSig.toStableInfo()
4207 let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo()
4208 let vouchers = peer.vouchers.compactMap {
4209 TPVoucher(infoWith: $0.voucher, sig: $0.sig)
4211 let isEgoPeer = peer.peerID == self.containerMO.egoPeerID
4212 try self.registerPeerMO(permanentInfo: permanentInfo,
4213 stableInfo: stableInfo,
4214 dynamicInfo: dynamicInfo,
4216 isEgoPeer: isEgoPeer)
4219 // The assertion here is that every peer registered in model is also present in containerMO
4220 guard let peerMO = try self.fetchPeerMO(peerID: peer.peerID) else {
4221 throw ContainerError.peerRegisteredButNotStored(peer.peerID)
4224 if let stableInfo = peer.stableInfoAndSig.toStableInfo() {
4225 try self.model.update(stableInfo, forPeerWithID: peer.peerID)
4226 // Pull the stableInfo back out of the model, and persist that.
4227 // The model checks signatures and clocks to prevent replay attacks.
4228 let modelPeer = self.model.peer(withID: peer.peerID)
4229 peerMO.stableInfo = modelPeer?.stableInfo?.data
4230 peerMO.stableInfoSig = modelPeer?.stableInfo?.sig
4232 if let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo() {
4233 try self.model.update(dynamicInfo, forPeerWithID: peer.peerID)
4234 // Pull the dynamicInfo back out of the model, and persist that.
4235 // The model checks signatures and clocks to prevent replay attacks.
4236 let modelPeer = self.model.peer(withID: peer.peerID)
4237 peerMO.dynamicInfo = modelPeer?.dynamicInfo?.data
4238 peerMO.dynamicInfoSig = modelPeer?.dynamicInfo?.sig
4240 peer.vouchers.forEach {
4241 if let voucher = TPVoucher(infoWith: $0.voucher, sig: $0.sig) {
4242 self.model.register(voucher)
4243 if peer.vouchers.filter({ $0.voucher == voucher.data && $0.sig == voucher.sig }).isEmpty {
4244 let voucherMO = VoucherMO(context: self.moc)
4245 voucherMO.voucherInfo = voucher.data
4246 voucherMO.voucherInfoSig = voucher.sig
4247 peerMO.addToVouchers(voucherMO)
4254 // Must be on moc queue to call this.
4255 private func fetchPeerMO(peerID: String) throws -> PeerMO? {
4256 let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
4257 fetch.predicate = NSPredicate(format: "peerID == %@ && container == %@", peerID, self.containerMO)
4258 let peers = try self.moc.fetch(fetch)
4259 return peers.first as? PeerMO
4262 // Must be on moc queue to call this.
4263 private func getPolicyDoc(_ policyVersion: UInt64) throws -> TPPolicyDocument {
4264 guard let policyDoc = self.model.policy(withVersion: policyVersion) else {
4265 throw ContainerError.unknownPolicyVersion(policyVersion)
4267 assert(policyVersion == policyDoc.version.versionNumber)
4268 if policyVersion == prevailingPolicyVersion.versionNumber {
4269 assert(policyDoc.version.policyHash == prevailingPolicyVersion.policyHash)
4274 // Must be on moc queue to call this.
4275 private func createNewStableInfoIfNeeded(stableChanges: StableChanges?,
4276 permanentInfo: TPPeerPermanentInfo,
4277 existingStableInfo: TPPeerStableInfo?,
4278 dynamicInfo: TPPeerDynamicInfo,
4279 signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? {
4280 func noChange<T: Equatable>(_ change: T?, _ existing: T?) -> Bool {
4281 return (nil == change) || change == existing
4284 let policyOfPeers = try? self.model.policy(forPeerIDs: dynamicInfo.includedPeerIDs,
4285 candidatePeerID: permanentInfo.peerID,
4286 candidateStableInfo: existingStableInfo)
4288 // Pick the best version of:
4289 // 1. The policy version asked for by the client
4290 // 2. The policy override set on this object (tests only)
4291 // 3. The max of our existing policyVersion, the highest policy used by our trusted peers, and the compile-time prevailing policy version
4292 let optimalPolicyVersionNumber = stableChanges?.policyVersion ??
4293 self.policyVersionOverride?.versionNumber ??
4294 max(existingStableInfo?.bestPolicyVersion().versionNumber ?? prevailingPolicyVersion.versionNumber,
4295 policyOfPeers?.version.versionNumber ?? prevailingPolicyVersion.versionNumber,
4296 prevailingPolicyVersion.versionNumber)
4298 // Determine which recovery key we'd like to be using, given our current idea of who to trust
4299 let optimalRecoveryKey = self.model.bestRecoveryKey(for: existingStableInfo, dynamicInfo: dynamicInfo)
4301 let intendedSyncUserControllableViews = stableChanges?.setSyncUserControllableViews?.sanitizeForPlatform(permanentInfo: permanentInfo)
4303 if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) &&
4304 noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) &&
4305 noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) &&
4306 noChange(optimalPolicyVersionNumber, existingStableInfo?.bestPolicyVersion().versionNumber) &&
4307 noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) &&
4308 noChange(optimalRecoveryKey?.signingKeyData, existingStableInfo?.recoverySigningPublicKey) &&
4309 noChange(optimalRecoveryKey?.encryptionKeyData, existingStableInfo?.recoveryEncryptionPublicKey) &&
4310 noChange(intendedSyncUserControllableViews, existingStableInfo?.syncUserControllableViews) {
4314 // If a test has asked a policy version before we froze this policy, then don't set a flexible version--it's trying to build a peer from before the policy was frozen
4315 let optimalPolicyVersion = try self.getPolicyDoc(optimalPolicyVersionNumber).version
4316 let useFrozenPolicyVersion = optimalPolicyVersion.versionNumber >= frozenPolicyVersion.versionNumber
4318 if let intendedSyncUserControllableViews = intendedSyncUserControllableViews {
4319 os_log("Intending to set user-controllable views to %{public}@", log: tplogTrace, type: .info, TPPBPeerStableInfo_UserControllableViewStatusAsString(intendedSyncUserControllableViews))
4322 return try self.model.createStableInfo(withFrozenPolicyVersion: useFrozenPolicyVersion ? frozenPolicyVersion : optimalPolicyVersion,
4323 flexiblePolicyVersion: useFrozenPolicyVersion ? optimalPolicyVersion : nil,
4324 policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets,
4325 syncUserControllableViews: intendedSyncUserControllableViews ?? existingStableInfo?.syncUserControllableViews ?? .UNKNOWN,
4326 deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "",
4327 serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "",
4328 osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "",
4329 signing: signingKeyPair,
4330 recoverySigningPubKey: optimalRecoveryKey?.signingKeyData,
4331 recoveryEncryptionPubKey: optimalRecoveryKey?.encryptionKeyData)
4334 private func assembleBottle(egoPeerID: String) throws -> Bottle {
4335 let bottleMoSet = containerMO.bottles as? Set<BottleMO>
4337 var bottleMOs = bottleMoSet?.filter {
4338 $0.peerID == egoPeerID
4341 if let count = bottleMOs?.count {
4343 throw ContainerError.tooManyBottlesForPeer
4344 // swiftlint:disable empty_count
4345 } else if count == 0 {
4346 // swiftlint:enable empty_count
4347 throw ContainerError.noBottleForPeer
4350 throw ContainerError.failedToAssembleBottle
4353 let BM: BottleMO? = (bottleMOs?.removeFirst())
4355 let bottle = try Bottle.with {
4356 $0.peerID = egoPeerID
4358 if let bottledPeerData = BM?.contents {
4359 $0.contents = bottledPeerData
4361 throw ContainerError.failedToAssembleBottle
4364 if let bottledPeerEscrowSigningSPKI = BM?.escrowedSigningSPKI {
4365 $0.escrowedSigningSpki = bottledPeerEscrowSigningSPKI
4367 throw ContainerError.failedToAssembleBottle
4370 if let bottledPeerSignatureUsingEscrowKey = BM?.signatureUsingEscrowKey {
4371 $0.signatureUsingEscrowKey = bottledPeerSignatureUsingEscrowKey
4373 throw ContainerError.failedToAssembleBottle
4376 if let bottledPeerSignatureUsingPeerKey = BM?.signatureUsingPeerKey {
4377 $0.signatureUsingPeerKey = bottledPeerSignatureUsingPeerKey
4379 throw ContainerError.failedToAssembleBottle
4382 if let bID = BM?.bottleID {
4385 throw ContainerError.failedToAssembleBottle
4392 func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) {
4393 self.semaphore.wait()
4394 let reply: (Error?) -> Void = {
4395 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4396 self.semaphore.signal()
4400 var updatedRequest = request
4402 if let egoPeerID = self.containerMO.egoPeerID {
4403 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, _ in
4404 updatedRequest.octagonEgoIdentity = (egoPeerKeys != nil)
4408 self.moc.performAndWait {
4409 self.cuttlefish.reportHealth(updatedRequest) { _, error in
4410 guard error == nil else {
4419 func pushHealthInquiry(reply: @escaping (Error?) -> Void) {
4420 self.semaphore.wait()
4421 let reply: (Error?) -> Void = {
4422 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4423 self.semaphore.signal()
4427 self.moc.performAndWait {
4428 self.cuttlefish.pushHealthInquiry(PushHealthInquiryRequest()) { _, error in
4429 guard error == nil else {