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 let request = FetchViableBottlesRequest.with {
2563 $0.filterRequest = OctagonPlatformSupportsSOS() ? .unknown : .byOctagonOnly
2565 if request.filterRequest == .byOctagonOnly {
2566 os_log("Requesting Cuttlefish sort records by Octagon Only", log: tplogDebug, type: .default)
2569 self.cuttlefish.fetchViableBottles(request) { response, error in
2570 guard error == nil else {
2571 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2572 reply(nil, nil, error)
2576 self.moc.performAndWait {
2577 guard let escrowPairs = response?.viableBottles else {
2578 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2583 var partialPairs: [EscrowPair] = []
2584 if let partial = response?.partialBottles {
2585 partialPairs = partial
2587 os_log("fetchViableBottles returned no partially viable bottles, but that's ok", log: tplogDebug, type: .default)
2590 let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID }
2591 os_log("fetchViableBottles returned viable bottles: %{public}@", log: tplogDebug, type: .default, viableBottleIDs)
2593 let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID }
2594 os_log("fetchViableBottles returned partial bottles: %{public}@", log: tplogDebug, type: .default, partialBottleIDs)
2596 self.handleFetchViableBottlesResponseWithSemaphore(response: response)
2600 os_log("fetchViableBottles saved bottles", log: tplogDebug, type: .default)
2601 let cached = TPCachedViableBottles(viableBottles: viableBottleIDs, partialBottles: partialBottleIDs)
2602 self.model.setViableBottles(cached)
2603 self.containerMO.escrowFetchDate = Date()
2604 reply(viableBottleIDs, partialBottleIDs, nil)
2606 os_log("fetchViableBottles unable to save bottles: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2607 reply(nil, nil, error)
2614 func removeEscrowCache(reply: @escaping (Error?) -> Void) {
2615 os_log("beginning a removeEscrowCache", log: tplogDebug, type: .default)
2617 self.semaphore.wait()
2618 let reply: (Error?) -> Void = {
2619 os_log("removeEscrowCache complete %{public}@", log: tplogTrace, type: .info, traceError($0))
2620 self.semaphore.signal()
2624 self.moc.performAndWait {
2625 self.onQueueRemoveEscrowCache()
2630 private func onQueueRemoveEscrowCache() {
2631 if let records = self.containerMO.fullyViableEscrowRecords {
2632 self.containerMO.removeFromFullyViableEscrowRecords(records)
2634 if let records = self.containerMO.partiallyViableEscrowRecords {
2635 self.containerMO.removeFromPartiallyViableEscrowRecords(records)
2637 if let records = self.containerMO.legacyEscrowRecords {
2638 self.containerMO.removeFromLegacyEscrowRecords(records)
2640 self.containerMO.escrowFetchDate = nil
2643 func fetchEscrowRecordsWithSemaphore(forceFetch: Bool, reply: @escaping ([Data]?, Error?) -> Void) {
2644 os_log("beginning a fetchEscrowRecords", log: tplogDebug, type: .default)
2646 self.moc.performAndWait {
2647 var cachedRecords: [OTEscrowRecord] = []
2649 if forceFetch == false {
2650 os_log("fetchEscrowRecords: force fetch flag is off", log: tplogDebug, type: .default)
2651 if let lastDate = self.containerMO.escrowFetchDate {
2652 if Date() < lastDate.addingTimeInterval(escrowCacheTimeout) {
2653 os_log("escrow cache still valid", log: tplogDebug, type: .default)
2654 cachedRecords = onqueueCachedEscrowRecords()
2656 os_log("escrow cache no longer valid", log: tplogDebug, type: .default)
2657 self.onQueueRemoveEscrowCache()
2661 os_log("fetchEscrowRecords: force fetch flag is on, removing escrow cache", log: tplogDebug, type: .default)
2662 self.onQueueRemoveEscrowCache()
2665 if !cachedRecords.isEmpty {
2666 os_log("returning from fetchEscrowRecords, using cached escrow records", log: tplogDebug, type: .default)
2667 let recordData: [Data] = cachedRecords.map { $0.data }
2668 reply(recordData, nil)
2672 let request = FetchViableBottlesRequest.with {
2673 $0.filterRequest = OctagonPlatformSupportsSOS() ? .unknown : .byOctagonOnly
2675 if request.filterRequest == .byOctagonOnly {
2676 os_log("Requesting Cuttlefish sort records by Octagon Only", log: tplogDebug, type: .default)
2679 self.cuttlefish.fetchViableBottles(request) { response, error in
2680 guard error == nil else {
2681 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2686 self.moc.performAndWait {
2687 guard response?.viableBottles != nil else {
2688 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2693 self.handleFetchViableBottlesResponseWithSemaphore(response: response)
2698 os_log("fetchViableBottles saved bottles and records", log: tplogDebug, type: .default)
2699 self.containerMO.escrowFetchDate = Date()
2701 var allEscrowRecordData: [Data] = []
2702 if let fullyViableRecords = self.containerMO.fullyViableEscrowRecords as? Set<EscrowRecordMO> {
2703 for record in fullyViableRecords {
2704 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .full) {
2705 allEscrowRecordData.append(r.data)
2709 if let partiallyViableRecords = self.containerMO.partiallyViableEscrowRecords as? Set<EscrowRecordMO> {
2710 for record in partiallyViableRecords {
2711 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .partial) {
2712 allEscrowRecordData.append(r.data)
2716 if let legacyRecords = self.containerMO.legacyEscrowRecords as? Set<EscrowRecordMO> {
2717 for record in legacyRecords {
2718 if let r = self.escrowRecordMOToEscrowRecords(record: record, viability: .none) {
2719 allEscrowRecordData.append(r.data)
2723 reply(allEscrowRecordData, nil)
2725 os_log("fetchViableBottles unable to save bottles and records: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2732 func fetchCurrentPolicy(modelIDOverride: String?, reply: @escaping (TPSyncingPolicy?, TPPBPeerStableInfo_UserControllableViewStatus, Error?) -> Void) {
2733 self.semaphore.wait()
2734 let reply: (TPSyncingPolicy?, TPPBPeerStableInfo_UserControllableViewStatus, Error?) -> Void = {
2735 os_log("fetchCurrentPolicy complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2736 self.semaphore.signal()
2740 self.moc.performAndWait {
2741 guard let egoPeerID = self.containerMO.egoPeerID,
2742 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2743 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2744 let stableInfoData = self.containerMO.egoPeerStableInfo,
2745 let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2746 os_log("fetchCurrentPolicy failed to find ego peer information", log: tplogDebug, type: .error)
2747 // 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
2749 guard let modelID = modelIDOverride else {
2750 os_log("no model ID override; returning error", log: tplogDebug, type: .default)
2751 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2755 guard let policyDocument = self.model.policy(withVersion: prevailingPolicyVersion.versionNumber) else {
2756 os_log("prevailing policy is missing?", log: tplogDebug, type: .default)
2757 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2762 let prevailingPolicy = try policyDocument.policy(withSecrets: [:], decrypter: Decrypter())
2763 let syncingPolicy = try prevailingPolicy.syncingPolicy(forModel: modelID, syncUserControllableViews: .UNKNOWN)
2765 os_log("returning a policy for model ID %{public}@", log: tplogDebug, type: .default, modelID)
2766 reply(syncingPolicy, .UNKNOWN, nil)
2769 os_log("fetchCurrentPolicy failed to prevailing policy: %{public}@", log: tplogDebug, type: .error)
2770 reply(nil, .UNKNOWN, ContainerError.noPreparedIdentity)
2775 let keyFactory = TPECPublicKeyFactory()
2776 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2777 os_log("fetchCurrentPolicy failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
2778 reply(nil, .UNKNOWN, ContainerError.invalidPermanentInfoOrSig)
2781 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
2782 os_log("fetchCurrentPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
2783 reply(nil, .UNKNOWN, ContainerError.invalidStableInfoOrSig)
2788 let syncingPolicy = try self.syncingPolicyFor(modelID: modelIDOverride ?? permanentInfo.modelID, stableInfo: stableInfo)
2790 guard let peer = self.model.peer(withID: permanentInfo.peerID), let dynamicInfo = peer.dynamicInfo else {
2791 os_log("fetchCurrentPolicy with no dynamic info", log: tplogDebug, type: .error)
2792 reply(syncingPolicy, .UNKNOWN, nil)
2796 // Note: we specifically do not want to sanitize this value for the platform: returning FOLLOWING here isn't that helpful
2797 let peersUserViewSyncability = self.model.userViewSyncabilityConsensusAmongTrustedPeers(dynamicInfo)
2798 reply(syncingPolicy, peersUserViewSyncability, nil)
2801 os_log("Fetching the syncing policy failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2802 reply(nil, .UNKNOWN, error)
2808 func syncingPolicyFor(modelID: String, stableInfo: TPPeerStableInfo) throws -> TPSyncingPolicy {
2809 let bestPolicyVersion : TPPolicyVersion
2811 let peerPolicyVersion = stableInfo.bestPolicyVersion()
2812 if peerPolicyVersion.versionNumber < frozenPolicyVersion.versionNumber {
2813 // This peer was from before CKKS4All, and we shouldn't listen to them when it comes to Syncing Policies
2814 bestPolicyVersion = prevailingPolicyVersion
2815 os_log("Ignoring policy version from pre-CKKS4All peer", log: tplogDebug, type: .default)
2818 bestPolicyVersion = peerPolicyVersion
2821 guard let policyDocument = self.model.policy(withVersion: bestPolicyVersion.versionNumber) else {
2822 os_log("best policy is missing?", log: tplogDebug, type: .default)
2823 throw ContainerError.unknownPolicyVersion(prevailingPolicyVersion.versionNumber)
2826 let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2827 return try policy.syncingPolicy(forModel: modelID, syncUserControllableViews: stableInfo.syncUserControllableViews)
2830 // All-or-nothing: return an error in case full list cannot be returned.
2831 // Completion handler data format: [version : [hash, data]]
2832 func fetchPolicyDocuments(versions: Set<TPPolicyVersion>,
2833 reply: @escaping ([TPPolicyVersion: Data]?, Error?) -> Void) {
2834 self.semaphore.wait()
2835 let reply: ([TPPolicyVersion: Data]?, Error?) -> Void = {
2836 os_log("fetchPolicyDocuments complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
2837 self.semaphore.signal()
2841 self.fetchPolicyDocumentsWithSemaphore(versions: versions) { policyDocuments, fetchError in
2842 reply(policyDocuments.flatMap { $0.mapValues { policyDoc in policyDoc.protobuf } }, fetchError)
2846 func fetchPolicyDocumentWithSemaphore(version: TPPolicyVersion,
2847 reply: @escaping (TPPolicyDocument?, Error?) -> Void) {
2848 self.fetchPolicyDocumentsWithSemaphore(versions: Set([version])) { versions, fetchError in
2849 guard fetchError == nil else {
2850 reply(nil, fetchError)
2854 guard let doc = versions?[version] else {
2855 os_log("fetchPolicyDocument: didn't return policy of version: %{public}@", log: tplogDebug, versions ?? "no versions")
2856 reply(nil, ContainerError.unknownPolicyVersion(version.versionNumber))
2864 func fetchPolicyDocumentsWithSemaphore(versions: Set<TPPolicyVersion>,
2865 reply: @escaping ([TPPolicyVersion: TPPolicyDocument]?, Error?) -> Void) {
2866 var remaining = versions
2867 var docs: [TPPolicyVersion: TPPolicyDocument] = [:]
2869 self.moc.performAndWait {
2870 for version in remaining {
2871 if let policydoc = try? self.getPolicyDoc(version.versionNumber), policydoc.version.policyHash == version.policyHash {
2872 docs[policydoc.version] = policydoc
2873 remaining.remove(version)
2878 guard !remaining.isEmpty else {
2883 let request = FetchPolicyDocumentsRequest.with {
2884 $0.keys = remaining.map { version in
2885 PolicyDocumentKey.with { $0.version = version.versionNumber; $0.hash = version.policyHash }}
2888 self.cuttlefish.fetchPolicyDocuments(request) { response, error in
2889 guard let response = response, error == nil else {
2890 os_log("FetchPolicyDocuments failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2891 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
2895 self.moc.performAndWait {
2896 for mapEntry in response.entries {
2897 // TODO: validate the policy's signature
2899 guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else {
2900 os_log("Can't make policy document with hash %{public}@ and data %{public}@",
2901 log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString())
2902 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2906 guard let expectedVersion = (remaining.first { $0.versionNumber == doc.version.versionNumber }) else {
2907 os_log("Received a policy version we didn't request: %d", log: tplogDebug, type: .default, doc.version.versionNumber)
2908 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2912 guard expectedVersion.policyHash == doc.version.policyHash else {
2913 os_log("Requested hash %{public}@ does not match fetched hash %{public}@", log: tplogDebug, type: .default,
2914 expectedVersion.policyHash, doc.version.policyHash)
2915 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2919 remaining.remove(expectedVersion) // Server responses should be unique, let's enforce
2921 docs[doc.version] = doc
2922 self.model.register(doc)
2926 try self.moc.save() // if this fails callers might make bad data assumptions
2932 // Determine if there's anything left to fetch
2933 guard let unfetchedVersion = remaining.first else {
2934 // Nothing remaining? Success!
2939 reply(nil, ContainerError.unknownPolicyVersion(unfetchedVersion.versionNumber))
2944 // Must be on moc queue to call this.
2945 // Caller is responsible for saving the moc afterwards.
2947 private func registerPeerMO(permanentInfo: TPPeerPermanentInfo,
2948 stableInfo: TPPeerStableInfo? = nil,
2949 dynamicInfo: TPPeerDynamicInfo? = nil,
2950 vouchers: [TPVoucher]? = nil,
2951 isEgoPeer: Bool = false) throws -> PeerMO {
2952 let peerID = permanentInfo.peerID
2954 let peer = PeerMO(context: self.moc)
2955 peer.peerID = peerID
2956 peer.permanentInfo = permanentInfo.data
2957 peer.permanentInfoSig = permanentInfo.sig
2958 peer.stableInfo = stableInfo?.data
2959 peer.stableInfoSig = stableInfo?.sig
2960 peer.dynamicInfo = dynamicInfo?.data
2961 peer.dynamicInfoSig = dynamicInfo?.sig
2962 peer.isEgoPeer = isEgoPeer
2963 self.containerMO.addToPeers(peer)
2965 self.model.registerPeer(with: permanentInfo)
2966 if let stableInfo = stableInfo {
2967 try self.model.update(stableInfo, forPeerWithID: peerID)
2969 if let dynamicInfo = dynamicInfo {
2970 try self.model.update(dynamicInfo, forPeerWithID: peerID)
2972 vouchers?.forEach { voucher in
2973 self.model.register(voucher)
2975 if (peer.vouchers as? Set<TPVoucher> ?? Set()).filter({ $0.data == voucher.data && $0.sig == voucher.sig }).isEmpty {
2976 let voucherMO = VoucherMO(context: self.moc)
2977 voucherMO.voucherInfo = voucher.data
2978 voucherMO.voucherInfoSig = voucher.sig
2979 peer.addToVouchers(voucherMO)
2985 /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */
2986 func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet],
2987 tlkShares: [CKKSTLKShare],
2988 egoPeerKeys: OctagonSelfPeerKeys,
2989 egoPeerDynamicInfo: TPPeerDynamicInfo,
2990 epoch: Int) throws -> ([ViewKeys], [TLKShare]) {
2991 let newCKKSKeys = ckksKeys.filter { $0.newUpload }
2992 let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert)
2994 let octagonSelfShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
2995 asPeer: egoPeerKeys,
2996 toPeer: egoPeerKeys,
2998 let extraShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
3000 var peerShares: [TLKShare] = []
3002 for keyset in newCKKSKeys {
3004 let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo,
3005 toAccessView: keyset.tlk.zoneID.zoneName)
3006 os_log("Planning to share %@ with peers %{public}@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess)
3008 let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) }
3009 let viewPeerShares = try peers.map { receivingPeer in
3010 TLKShare.convert(ckksTLKShare: try CKKSTLKShare(keyset.tlk,
3012 to: receivingPeer.permanentInfo,
3017 peerShares += viewPeerShares
3019 os_log("Unable to create TLKShares for keyset %@: %{public}@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg)
3023 return (newViewKeys, octagonSelfShares + peerShares + extraShares)
3026 func onqueuePreparePeerForJoining(egoPeerID: String,
3027 peerPermanentInfo: TPPeerPermanentInfo,
3028 stableInfo: TPPeerStableInfo,
3030 preapprovedKeys: [Data]?,
3031 vouchers: [SignedVoucher],
3032 egoPeerKeys: OctagonSelfPeerKeys) throws -> (Peer, TPPeerDynamicInfo) {
3033 let dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
3034 peerPermanentInfo: peerPermanentInfo,
3035 peerStableInfo: stableInfo,
3036 sponsorID: sponsorID,
3037 preapprovedKeys: preapprovedKeys,
3038 signing: egoPeerKeys.signingKey,
3039 currentMachineIDs: self.onqueueCurrentMIDList())
3041 let userViewSyncability: TPPBPeerStableInfo_UserControllableViewStatus?
3042 if [.ENABLED, .DISABLED].contains(stableInfo.syncUserControllableViews) {
3044 userViewSyncability = nil
3046 let newUserViewSyncability: TPPBPeerStableInfo_UserControllableViewStatus
3048 if peerPermanentInfo.modelID.hasPrefix("AppleTV") ||
3049 peerPermanentInfo.modelID.hasPrefix("AudioAccessory") ||
3050 peerPermanentInfo.modelID.hasPrefix("Watch") {
3051 // Watches, TVs, and AudioAccessories always join as FOLLOWING.
3052 newUserViewSyncability = .FOLLOWING
3054 // All other platforms select what the other devices say to do
3055 newUserViewSyncability = self.model.userViewSyncabilityConsensusAmongTrustedPeers(dynamicInfo)
3058 os_log("join: setting 'user view sync' control as: %{public}@", log: tplogDebug, type: .default,
3059 TPPBPeerStableInfo_UserControllableViewStatusAsString(newUserViewSyncability))
3060 userViewSyncability = newUserViewSyncability
3063 let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: StableChanges.change(viewStatus: userViewSyncability),
3064 permanentInfo: peerPermanentInfo,
3065 existingStableInfo: stableInfo,
3066 dynamicInfo: dynamicInfo,
3067 signingKeyPair: egoPeerKeys.signingKey)
3069 let peer = Peer.with {
3070 $0.peerID = egoPeerID
3071 $0.permanentInfoAndSig = SignedPeerPermanentInfo(peerPermanentInfo)
3072 $0.stableInfoAndSig = SignedPeerStableInfo(newStableInfo ?? stableInfo)
3073 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3074 $0.vouchers = vouchers
3077 return (peer, dynamicInfo)
3080 func join(voucherData: Data,
3082 ckksKeys: [CKKSKeychainBackedKeySet],
3083 tlkShares: [CKKSTLKShare],
3084 preapprovedKeys: [Data]?,
3085 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
3086 self.semaphore.wait()
3087 let reply: (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void = {
3088 os_log("join complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
3089 self.semaphore.signal()
3090 reply($0, $1, $2, $3)
3093 self.fetchAndPersistChanges { error in
3094 guard error == nil else {
3095 reply(nil, [], nil, error)
3099 // To join, you must know all policies that exist
3100 let allPolicyVersions = self.model.allPolicyVersions()
3101 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3102 if let error = policyFetchError {
3103 os_log("join: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3106 self.moc.performAndWait {
3107 guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else {
3108 reply(nil, [], nil, ContainerError.invalidVoucherOrSig)
3111 guard let sponsor = self.model.peer(withID: voucher.sponsorID) else {
3112 reply(nil, [], nil, ContainerError.sponsorNotRegistered(voucher.sponsorID))
3116 // Fetch ego peer identity from local storage.
3117 guard let egoPeerID = self.containerMO.egoPeerID,
3118 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3119 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3120 let egoStableData = self.containerMO.egoPeerStableInfo,
3121 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3123 reply(nil, [], nil, ContainerError.noPreparedIdentity)
3127 let keyFactory = TPECPublicKeyFactory()
3128 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3129 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
3132 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3133 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3136 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3137 os_log("join: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3138 self.onqueueTTRUntrusted()
3139 reply(nil, [], nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3143 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3144 guard let egoPeerKeys = egoPeerKeys else {
3145 os_log("Don't have my own peer keys; can't join: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3146 reply(nil, [], nil, error)
3149 self.moc.performAndWait {
3151 let newDynamicInfo: TPPeerDynamicInfo
3153 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3154 peerPermanentInfo: selfPermanentInfo,
3155 stableInfo: selfStableInfo,
3156 sponsorID: sponsor.peerID,
3157 preapprovedKeys: preapprovedKeys,
3158 vouchers: [SignedVoucher.with {
3159 $0.voucher = voucher.data
3160 $0.sig = voucher.sig
3162 egoPeerKeys: egoPeerKeys)
3164 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3165 reply(nil, [], nil, error)
3169 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3170 os_log("Unable to create new peer stable info for joining", log: tplogDebug, type: .default)
3171 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3175 let allTLKShares: [TLKShare]
3176 let viewKeys: [ViewKeys]
3178 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3179 tlkShares: tlkShares,
3180 egoPeerKeys: egoPeerKeys,
3181 egoPeerDynamicInfo: newDynamicInfo,
3182 epoch: Int(selfPermanentInfo.epoch))
3184 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3185 reply(nil, [], nil, error)
3190 try self.model.checkIntroduction(forCandidate: selfPermanentInfo,
3191 stableInfo: peer.stableInfoAndSig.toStableInfo(),
3192 withSponsorID: sponsor.peerID)
3194 os_log("Error checking introduction: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3195 reply(nil, [], nil, error)
3201 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3203 reply(nil, [], nil, error)
3207 os_log("Beginning join for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3208 os_log("Join permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3209 os_log("Join permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3210 os_log("Join stableInfo: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString())
3211 os_log("Join stableInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString())
3212 os_log("Join dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3213 os_log("Join dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3215 os_log("Join vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3216 os_log("Join voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3218 os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3221 os_log("Join peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3223 os_log("Join unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3226 let changeToken = self.containerMO.changeToken ?? ""
3227 let request = JoinWithVoucherRequest.with {
3228 $0.changeToken = changeToken
3231 $0.tlkShares = allTLKShares
3232 $0.viewKeys = viewKeys
3234 self.cuttlefish.joinWithVoucher(request) { response, error in
3235 guard let response = response, error == nil else {
3236 os_log("joinWithVoucher failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3237 reply(nil, [], nil, error ?? ContainerError.cloudkitResponseMissing)
3241 self.moc.performAndWait {
3243 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3244 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3246 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
3247 stableInfo: peerStableInfo)
3249 try self.onQueuePersist(changes: response.changes)
3250 os_log("JoinWithVoucher succeeded", log: tplogDebug)
3252 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3253 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
3255 os_log("JoinWithVoucher failed: %{public}@", log: tplogDebug, String(describing: error))
3256 reply(nil, [], nil, error)
3267 func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Bool, Error?) -> Void) {
3268 self.semaphore.wait()
3269 let reply: (Bool, Bool, Bool, Bool, Error?) -> Void = {
3270 os_log("health check complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
3271 self.semaphore.signal()
3272 reply($0, $1, $2, $3, $4)
3275 os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck)
3277 self.moc.performAndWait {
3278 guard let egoPeerID = self.containerMO.egoPeerID else {
3279 // No identity, nothing to do
3280 os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default)
3281 reply(false, false, false, false, ContainerError.noPreparedIdentity)
3284 let request = GetRepairActionRequest.with {
3285 $0.peerID = egoPeerID
3286 $0.requiresEscrowCheck = requiresEscrowCheck
3289 self.cuttlefish.getRepairAction(request) { response, error in
3290 guard error == nil else {
3291 reply(false, false, false, false, error)
3294 guard let action = response?.repairAction else {
3295 os_log("repair response is empty, returning false", log: tplogDebug, type: .default)
3296 reply(false, false, false, false, nil)
3299 var postRepairAccount: Bool = false
3300 var postRepairEscrow: Bool = false
3301 var resetOctagon: Bool = false
3302 var leaveTrust: Bool = false
3307 case .postRepairAccount:
3308 postRepairAccount = true
3309 case .postRepairEscrow:
3310 postRepairEscrow = true
3318 reply(postRepairAccount, postRepairEscrow, resetOctagon, leaveTrust, nil)
3323 func getSupportAppInfo(reply: @escaping (Data?, Error?) -> Void) {
3324 self.semaphore.wait()
3325 let reply: (Data?, Error?) -> Void = {
3326 os_log("getSupportAppInfo complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3327 self.semaphore.signal()
3331 self.cuttlefish.getSupportAppInfo { response, error in
3332 guard let response = response, error == nil else {
3333 os_log("getSupportAppInfo failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3334 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3338 guard let data = try? response.serializedData() else {
3339 reply(nil, ContainerError.failedToSerializeData)
3347 func preflightPreapprovedJoin(preapprovedKeys: [Data]?,
3348 reply: @escaping (Bool, Error?) -> Void) {
3349 self.semaphore.wait()
3350 let reply: (Bool, Error?) -> Void = {
3351 os_log("preflightPreapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3352 self.semaphore.signal()
3356 self.fetchAndPersistChanges { error in
3357 guard error == nil else {
3358 os_log("preflightPreapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3363 // We need to try to have all policy versions that our peers claim to behave
3364 let allPolicyVersions = self.model.allPolicyVersions()
3365 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3366 if let error = policyFetchError {
3367 os_log("preflightPreapprovedJoin: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3370 // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us?
3372 guard !self.model.allPeerIDs().isEmpty else {
3373 // If, after fetch and handle changes, there's no peers, then we can likely establish.
3378 guard let egoPeerID = self.containerMO.egoPeerID,
3379 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3380 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3382 os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3383 reply(false, ContainerError.noPreparedIdentity)
3387 let keyFactory = TPECPublicKeyFactory()
3388 guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3389 os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug)
3390 reply(false, ContainerError.invalidPermanentInfoOrSig)
3394 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else {
3395 os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3396 reply(false, ContainerError.noPeersPreapprovePreparedIdentity)
3400 let keysApprovingPeers = preapprovedKeys?.filter { key in
3401 self.model.hasPotentiallyTrustedPeer(withSigningKey: key)
3404 guard (keysApprovingPeers?.count ?? 0) > 0 else {
3405 os_log("preflightPreapprovedJoin: no reciprocal trust for existing peers", log: tplogDebug, type: .debug)
3406 reply(false, ContainerError.noPeersPreapprovedBySelf)
3415 func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet],
3416 tlkShares: [CKKSTLKShare],
3417 preapprovedKeys: [Data]?,
3418 reply: @escaping (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void) {
3419 self.semaphore.wait()
3420 let reply: (String?, [CKRecord], TPSyncingPolicy?, Error?) -> Void = {
3421 os_log("preapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
3422 self.semaphore.signal()
3423 reply($0, $1, $2, $3)
3426 self.fetchAndPersistChangesIfNeeded { error in
3427 guard error == nil else {
3428 os_log("preapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3429 reply(nil, [], nil, error)
3432 self.moc.performAndWait {
3433 // If, after fetch and handle changes, there's no peers, then fire off an establish
3434 // Note that if the establish fails, retrying this call might work.
3435 // That's up to the caller.
3436 if self.model.allPeerIDs().isEmpty {
3437 os_log("preapprovedJoin but no existing peers, attempting establish", log: tplogDebug, type: .debug)
3439 self.onqueueEstablish(ckksKeys: ckksKeys,
3440 tlkShares: tlkShares,
3441 preapprovedKeys: preapprovedKeys,
3446 // Fetch ego peer identity from local storage.
3447 guard let egoPeerID = self.containerMO.egoPeerID,
3448 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3449 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3450 let egoStableData = self.containerMO.egoPeerStableInfo,
3451 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3453 os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3454 reply(nil, [], nil, ContainerError.noPreparedIdentity)
3458 let keyFactory = TPECPublicKeyFactory()
3459 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3460 reply(nil, [], nil, ContainerError.invalidPermanentInfoOrSig)
3463 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3464 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3468 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3469 os_log("preapprovedJoin: self machineID %{public}@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3470 self.onqueueTTRUntrusted()
3471 reply(nil, [], nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3474 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3475 guard let egoPeerKeys = egoPeerKeys else {
3476 os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3477 reply(nil, [], nil, error)
3481 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else {
3482 os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3483 reply(nil, [], nil, ContainerError.noPeersPreapprovePreparedIdentity)
3487 self.moc.performAndWait {
3489 let newDynamicInfo: TPPeerDynamicInfo
3491 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3492 peerPermanentInfo: selfPermanentInfo,
3493 stableInfo: selfStableInfo,
3495 preapprovedKeys: preapprovedKeys,
3497 egoPeerKeys: egoPeerKeys)
3499 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3500 reply(nil, [], nil, error)
3504 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3505 os_log("Unable to create new peer stable info for joining", log: tplogDebug, type: .default)
3506 reply(nil, [], nil, ContainerError.invalidStableInfoOrSig)
3510 let allTLKShares: [TLKShare]
3511 let viewKeys: [ViewKeys]
3513 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3514 tlkShares: tlkShares,
3515 egoPeerKeys: egoPeerKeys,
3516 egoPeerDynamicInfo: newDynamicInfo,
3517 epoch: Int(selfPermanentInfo.epoch))
3519 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3520 reply(nil, [], nil, error)
3526 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3528 reply(nil, [], nil, error)
3532 os_log("Beginning preapprovedJoin for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3533 os_log("preapprovedJoin permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3534 os_log("preapprovedJoin permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3535 os_log("preapprovedJoin stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
3536 os_log("preapprovedJoin stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
3537 os_log("preapprovedJoin dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3538 os_log("preapprovedJoin dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3540 os_log("preapprovedJoin vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3541 os_log("preapprovedJoin voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3543 os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3546 os_log("preapprovedJoin peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3548 os_log("preapprovedJoin unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3551 let changeToken = self.containerMO.changeToken ?? ""
3552 let request = JoinWithVoucherRequest.with {
3553 $0.changeToken = changeToken
3556 $0.tlkShares = allTLKShares
3557 $0.viewKeys = viewKeys
3559 self.cuttlefish.joinWithVoucher(request) { response, error in
3560 guard let response = response, error == nil else {
3561 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3562 reply(nil, [], nil, error ?? ContainerError.cloudkitResponseMissing)
3566 self.moc.performAndWait {
3568 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3569 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3571 let syncingPolicy = try self.syncingPolicyFor(modelID: selfPermanentInfo.modelID,
3572 stableInfo: peerStableInfo)
3574 try self.onQueuePersist(changes: response.changes)
3575 os_log("preapprovedJoin succeeded", log: tplogDebug)
3577 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3578 reply(egoPeerID, keyHierarchyRecords, syncingPolicy, nil)
3580 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, String(describing: error))
3581 reply(nil, [], nil, error)
3591 func update(deviceName: String?,
3592 serialNumber: String?,
3594 policyVersion: UInt64?,
3595 policySecrets: [String: Data]?,
3596 syncUserControllableViews: TPPBPeerStableInfo_UserControllableViewStatus?,
3597 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3598 self.semaphore.wait()
3599 let reply: (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void = {
3600 os_log("update complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
3601 self.semaphore.signal()
3605 // Get (and save) the latest from cuttlefish
3606 let stableChanges = StableChanges(deviceName: deviceName,
3607 serialNumber: serialNumber,
3608 osVersion: osVersion,
3609 policyVersion: policyVersion,
3610 policySecrets: policySecrets,
3611 setSyncUserControllableViews: syncUserControllableViews)
3612 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply)
3615 func set(preapprovedKeys: [Data],
3616 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3617 self.semaphore.wait()
3618 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3619 os_log("setPreapprovedKeys complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3620 self.semaphore.signal()
3624 self.moc.performAndWait {
3625 os_log("setPreapprovedKeys: %@", log: tplogDebug, type: .default, preapprovedKeys)
3627 guard let egoPeerID = self.containerMO.egoPeerID else {
3628 // No identity, nothing to do
3629 os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default)
3630 reply(nil, ContainerError.noPreparedIdentity)
3633 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3634 guard let signingKeyPair = signingKeyPair else {
3635 os_log("setPreapprovedKeys: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3636 reply(nil, error ?? ContainerError.unableToCreateKeyPair)
3640 self.moc.performAndWait {
3641 let dynamicInfo: TPPeerDynamicInfo
3643 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3645 removingPeerIDs: nil,
3646 preapprovedKeys: preapprovedKeys,
3647 signing: signingKeyPair,
3648 currentMachineIDs: self.onqueueCurrentMIDList())
3650 os_log("setPreapprovedKeys: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3655 os_log("setPreapprovedKeys: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
3657 if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo {
3658 os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default)
3660 // Calling this will fill in the peer status
3661 self.updateTrustIfNeeded { status, _, error in
3662 reply(status, error)
3667 os_log("setPreapprovedKeys: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3668 let request = UpdateTrustRequest.with {
3669 $0.changeToken = self.containerMO.changeToken ?? ""
3670 $0.peerID = egoPeerID
3671 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3674 self.perform(updateTrust: request) { state, _, error in
3675 guard error == nil else {
3676 os_log("setPreapprovedKeys: failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
3681 os_log("setPreapprovedKeys: updateTrust succeeded", log: tplogDebug, type: .default)
3689 func updateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3690 tlkShares: [CKKSTLKShare],
3691 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3692 self.semaphore.wait()
3693 let reply: ([CKRecord]?, Error?) -> Void = {
3694 os_log("updateTLKs complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3695 self.semaphore.signal()
3699 os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys)
3701 self.moc.performAndWait {
3702 self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply)
3706 func onqueueUpdateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3707 tlkShares: [CKKSTLKShare],
3708 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3709 guard let egoPeerID = self.containerMO.egoPeerID,
3710 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3711 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3713 os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default)
3714 reply(nil, ContainerError.noPreparedIdentity)
3718 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else {
3719 os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys)
3720 reply(nil, ContainerError.invalidPermanentInfoOrSig)
3724 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3725 guard let egoPeerKeys = egoPeerKeys else {
3726 os_log("Don't have my own peer keys; can't upload new TLKs: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3730 self.moc.performAndWait {
3731 guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else {
3732 os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default)
3733 reply(nil, ContainerError.missingDynamicInfo)
3737 let allTLKShares: [TLKShare]
3738 let viewKeys: [ViewKeys]
3740 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3741 tlkShares: tlkShares,
3742 egoPeerKeys: egoPeerKeys,
3743 egoPeerDynamicInfo: egoPeerDynamicInfo,
3744 epoch: Int(selfPermanentInfo.epoch))
3746 os_log("Unable to process keys before uploading: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3751 let request = UpdateTrustRequest.with {
3752 $0.changeToken = self.containerMO.changeToken ?? ""
3753 $0.peerID = egoPeerID
3754 $0.tlkShares = allTLKShares
3755 $0.viewKeys = viewKeys
3758 self.cuttlefish.updateTrust(request) { response, error in
3759 guard error == nil else {
3764 let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? []
3765 os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count)
3766 reply(keyHierarchyRecords, nil)
3772 func getState(reply: @escaping (ContainerState) -> Void) {
3773 self.semaphore.wait()
3774 let reply: (ContainerState) -> Void = {
3775 os_log("getState complete: %{public}@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
3776 self.semaphore.signal()
3780 self.moc.performAndWait {
3781 var state = ContainerState()
3782 state.egoPeerID = self.containerMO.egoPeerID
3784 if self.containerMO.bottles != nil {
3785 self.containerMO.bottles!.forEach { bottle in
3786 state.bottles.insert(bottle as! BottleMO)
3790 if self.containerMO.fullyViableEscrowRecords != nil {
3791 self.containerMO.fullyViableEscrowRecords!.forEach { record in
3792 state.escrowRecords.insert(record as! EscrowRecordMO)
3796 if self.containerMO.partiallyViableEscrowRecords != nil {
3797 self.containerMO.partiallyViableEscrowRecords!.forEach { record in
3798 state.escrowRecords.insert(record as! EscrowRecordMO)
3802 self.model.allPeers().forEach { peer in
3803 state.peers[peer.peerID] = peer
3805 state.vouchers = Array(self.model.allVouchers())
3810 // This will only fetch changes if no changes have ever been fetched before
3811 func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) {
3812 self.moc.performAndWait {
3813 if self.containerMO.changeToken == nil {
3814 self.onqueueFetchAndPersistChanges(reply: reply)
3821 func fetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3822 self.moc.performAndWait {
3823 self.onqueueFetchAndPersistChanges(reply: reply)
3827 private func onqueueFetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3828 let request = FetchChangesRequest.with {
3829 $0.changeToken = self.containerMO.changeToken ?? ""
3831 os_log("Fetching with change token: %{public}@", log: tplogDebug, type: .default, !request.changeToken.isEmpty ? request.changeToken : "empty")
3833 self.cuttlefish.fetchChanges(request) { response, error in
3834 guard let response = response, error == nil else {
3836 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired):
3837 os_log("change token is expired; resetting local CK storage", log: tplogDebug, type: .default)
3839 self.moc.performAndWait {
3841 try self.deleteLocalCloudKitData()
3843 os_log("Failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3848 self.fetchAndPersistChanges(reply: reply)
3853 os_log("Fetch error is an unknown error: %{public}@", log: tplogDebug, type: .default, String(describing: error))
3856 os_log("Could not fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3862 try self.persist(changes: response.changes)
3864 os_log("Could not persist changes: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3869 if response.changes.more {
3870 os_log("persist: More changes indicated. Fetching...", log: tplogDebug, type: .default)
3871 self.fetchAndPersistChanges(reply: reply)
3874 os_log("persist: no more changes!", log: tplogDebug, type: .default)
3881 // Check for delta update in trust lists, that should lead to update of TLKShares
3883 private func haveTrustMemberChanges(newDynamicInfo: TPPeerDynamicInfo, oldDynamicInfo: TPPeerDynamicInfo?) -> Bool {
3884 guard let oldDynamicInfo = oldDynamicInfo else {
3887 if newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs {
3890 if newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs {
3893 if newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals {
3899 // Fetch and persist changes from Cuttlefish. If this results
3900 // in us calculating a new dynamicInfo then give the new dynamicInfo
3901 // to Cuttlefish. If Cuttlefish returns more changes then persist
3902 // them locally, update dynamicInfo if needed, and keep doing that
3903 // until dynamicInfo is unchanged and there are no more changes to fetch.
3905 // Must be holding the semaphore to call this, and it remains
3906 // the caller's responsibility to release it after it completes
3907 // (i.e. after reply is invoked).
3908 internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil,
3909 peerChanges: Bool = false,
3910 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3911 self.fetchAndPersistChanges { error in
3912 if let error = error {
3913 os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3914 reply(nil, nil, error)
3918 self.updateTrustIfNeeded(stableChanges: stableChanges, peerChanges: peerChanges, reply: reply)
3922 // If this results in us calculating a new dynamicInfo then,
3923 // upload the new dynamicInfo
3924 // to Cuttlefish. If Cuttlefish returns more changes then persist
3925 // them locally, update dynamicInfo if needed, and keep doing that
3926 // until dynamicInfo is unchanged and there are no more changes to fetch.
3928 // Must be holding the semaphore to call this, and it remains
3929 // the caller's responsibility to release it after it completes
3930 // (i.e. after reply is invoked).
3931 private func updateTrustIfNeeded(stableChanges: StableChanges? = nil,
3932 peerChanges: Bool = false,
3933 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
3934 self.moc.performAndWait {
3935 guard let egoPeerID = self.containerMO.egoPeerID else {
3936 // No identity, nothing to do
3937 os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default)
3938 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil),
3943 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3944 guard let signingKeyPair = signingKeyPair else {
3945 os_log("updateTrustIfNeeded: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3946 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil),
3951 guard let currentSelfInModel = self.model.peer(withID: egoPeerID) else {
3952 // Not in circle, nothing to do
3953 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(signingKeyPair.publicKey().spki())
3954 os_log("updateTrustIfNeeded: ego peer is not in model, is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
3955 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3956 isPreapproved: isPreapproved,
3958 memberChanges: peerChanges,
3959 unknownMachineIDs: false,
3966 // We need to try to have all policy versions that our peers claim to behave
3967 let allPolicyVersions = self.model.allPolicyVersions()
3968 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3969 if let error = policyFetchError {
3970 os_log("updateTrustIfNeeded: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3973 self.moc.performAndWait {
3974 let dynamicInfo: TPPeerDynamicInfo
3975 var stableInfo: TPPeerStableInfo?
3977 // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair,
3978 // and then only load the key if it has changed and we need to sign a new one. This would also
3979 // help make our detection of change immune from non-canonical serialization of dynamicInfo.
3980 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3982 removingPeerIDs: nil,
3983 preapprovedKeys: nil,
3984 signing: signingKeyPair,
3985 currentMachineIDs: self.onqueueCurrentMIDList())
3987 stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges,
3988 permanentInfo: currentSelfInModel.permanentInfo,
3989 existingStableInfo: currentSelfInModel.stableInfo,
3990 dynamicInfo: dynamicInfo,
3991 signingKeyPair: signingKeyPair)
3993 os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3994 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3995 isPreapproved: false,
3996 status: self.model.statusOfPeer(withID: egoPeerID),
3997 memberChanges: peerChanges,
3998 unknownMachineIDs: false,
4005 os_log("updateTrustIfNeeded: produced a stableInfo: %{public}@", log: tplogDebug, type: .default, String(describing: stableInfo))
4006 os_log("updateTrustIfNeeded: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
4008 let peer = self.model.peer(withID: egoPeerID)
4009 if (stableInfo == nil || stableInfo == peer?.stableInfo) &&
4010 dynamicInfo == peer?.dynamicInfo {
4011 os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default)
4012 // No change to the dynamicInfo: update the MID list now that we've reached a steady state
4014 self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo)
4017 os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4020 let syncingPolicy: TPSyncingPolicy?
4022 if let peer = self.model.peer(withID: egoPeerID), let stableInfo = peer.stableInfo {
4023 syncingPolicy = try self.syncingPolicyFor(modelID: peer.permanentInfo.modelID, stableInfo: stableInfo)
4028 os_log("updateTrustIfNeeded: unable to compute a new syncing policy: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4032 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
4033 isPreapproved: false,
4034 status: self.model.statusOfPeer(withID: egoPeerID),
4035 memberChanges: peerChanges,
4036 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
4037 osVersion: peer?.stableInfo?.osVersion),
4042 // Check if we change that should trigger a notification that should trigger TLKShare updates
4043 let havePeerChanges = peerChanges || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo)
4045 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
4046 os_log("updateTrustIfNeeded: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
4047 var request = UpdateTrustRequest.with {
4048 $0.changeToken = self.containerMO.changeToken ?? ""
4049 $0.peerID = egoPeerID
4050 $0.dynamicInfoAndSig = signedDynamicInfo
4052 if let stableInfo = stableInfo {
4053 request.stableInfoAndSig = SignedPeerStableInfo(stableInfo)
4056 self.perform(updateTrust: request, stableChanges: stableChanges, peerChanges: havePeerChanges, reply: reply)
4063 private func perform(updateTrust request: UpdateTrustRequest,
4064 stableChanges: StableChanges? = nil,
4065 peerChanges: Bool = false,
4066 reply: @escaping (TrustedPeersHelperPeerState?, TPSyncingPolicy?, Error?) -> Void) {
4067 self.cuttlefish.updateTrust(request) { response, error in
4068 guard let response = response, error == nil else {
4069 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
4070 reply(nil, nil, error ?? ContainerError.cloudkitResponseMissing)
4075 try self.persist(changes: response.changes)
4077 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, String(describing: error))
4078 reply(nil, nil, error)
4082 if response.changes.more {
4083 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges,
4084 peerChanges: peerChanges,
4087 self.updateTrustIfNeeded(stableChanges: stableChanges,
4088 peerChanges: peerChanges,
4094 private func persist(changes: Changes) throws {
4095 // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait.
4096 // So, do it ourself
4097 var outsideBlockError: Error?
4099 self.moc.performAndWait {
4100 os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default,
4101 changes.differences.count,
4103 os_log("persist: New change token: %{public}@", log: tplogDebug, type: .default, changes.changeToken)
4106 try self.onQueuePersist(changes: changes)
4108 outsideBlockError = error
4112 if let outsideBlockError = outsideBlockError {
4113 throw outsideBlockError
4117 // Must be on moc queue to call this.
4118 // Changes are registered in the model and stored in moc.
4119 private func onQueuePersist(changes: Changes) throws {
4120 self.containerMO.changeToken = changes.changeToken
4121 self.containerMO.moreChanges = changes.more
4123 if !changes.differences.isEmpty {
4124 self.model.clearViableBottles()
4125 os_log("escrow cache and viable bottles are no longer valid", log: tplogDebug, type: .default)
4126 self.onQueueRemoveEscrowCache()
4129 try changes.differences.forEach { peerDifference in
4130 if let operation = peerDifference.operation {
4132 case .add(let peer), .update(let peer):
4133 try self.addOrUpdate(peer: peer)
4134 // Update containerMO ego data if it has changed.
4135 if peer.peerID == self.containerMO.egoPeerID {
4136 guard let stableInfoAndSig: TPPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
4139 self.containerMO.egoPeerStableInfo = stableInfoAndSig.data
4140 self.containerMO.egoPeerStableInfoSig = stableInfoAndSig.sig
4143 case .remove(let peer):
4144 self.model.deletePeer(withID: peer.peerID)
4145 if let peerMO = try self.fetchPeerMO(peerID: peer.peerID) {
4146 self.moc.delete(peerMO)
4152 let signingKey = changes.recoverySigningPubKey
4153 let encryptionKey = changes.recoveryEncryptionPubKey
4155 if !signingKey.isEmpty && !encryptionKey.isEmpty {
4156 self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey)
4161 // Must be on moc queue to call this
4162 // Deletes all things that should come back from CloudKit. Keeps the egoPeer data, though.
4163 private func deleteLocalCloudKitData() throws {
4164 os_log("Deleting all CloudKit data", log: tplogDebug, type: .default)
4167 let peerRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
4168 peerRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
4169 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: peerRequest))
4171 // If we have an ego peer ID, keep the bottle associated with it
4172 if let peerID = self.containerMO.egoPeerID, let bottles = self.containerMO.bottles {
4173 let nonPeerBottles = NSSet(array: bottles.filter {
4175 case let bottleMO as BottleMO:
4176 return bottleMO.peerID != peerID
4181 self.containerMO.removeFromBottles(nonPeerBottles as NSSet)
4183 let bottleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Bottle")
4184 bottleRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
4185 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: bottleRequest))
4186 self.containerMO.bottles = nil
4189 self.containerMO.peers = nil
4190 self.containerMO.changeToken = nil
4191 self.containerMO.moreChanges = false
4193 self.model = Container.loadModel(from: self.containerMO)
4196 os_log("Local delete failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
4200 os_log("Saved model with %d peers", log: tplogDebug, type: .default, self.model.allPeerIDs().count)
4203 // Must be on moc queue to call this.
4204 private func addOrUpdate(signingKey: Data, encryptionKey: Data) {
4205 self.model.setRecoveryKeys(
4206 TPRecoveryKeyPair(signingKeyData: signingKey, encryptionKeyData: encryptionKey))
4208 self.containerMO.recoveryKeySigningSPKI = signingKey
4209 self.containerMO.recoveryKeyEncryptionSPKI = encryptionKey
4212 // Must be on moc queue to call this.
4213 private func addOrUpdate(peer: Peer) throws {
4214 if !self.model.hasPeer(withID: peer.peerID) {
4216 guard let permanentInfo = peer.permanentInfoAndSig.toPermanentInfo(peerID: peer.peerID) else {
4217 // Ignoring bad peer
4220 let stableInfo = peer.stableInfoAndSig.toStableInfo()
4221 let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo()
4222 let vouchers = peer.vouchers.compactMap {
4223 TPVoucher(infoWith: $0.voucher, sig: $0.sig)
4225 let isEgoPeer = peer.peerID == self.containerMO.egoPeerID
4226 try self.registerPeerMO(permanentInfo: permanentInfo,
4227 stableInfo: stableInfo,
4228 dynamicInfo: dynamicInfo,
4230 isEgoPeer: isEgoPeer)
4233 // The assertion here is that every peer registered in model is also present in containerMO
4234 guard let peerMO = try self.fetchPeerMO(peerID: peer.peerID) else {
4235 throw ContainerError.peerRegisteredButNotStored(peer.peerID)
4238 if let stableInfo = peer.stableInfoAndSig.toStableInfo() {
4239 try self.model.update(stableInfo, forPeerWithID: peer.peerID)
4240 // Pull the stableInfo back out of the model, and persist that.
4241 // The model checks signatures and clocks to prevent replay attacks.
4242 let modelPeer = self.model.peer(withID: peer.peerID)
4243 peerMO.stableInfo = modelPeer?.stableInfo?.data
4244 peerMO.stableInfoSig = modelPeer?.stableInfo?.sig
4246 if let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo() {
4247 try self.model.update(dynamicInfo, forPeerWithID: peer.peerID)
4248 // Pull the dynamicInfo back out of the model, and persist that.
4249 // The model checks signatures and clocks to prevent replay attacks.
4250 let modelPeer = self.model.peer(withID: peer.peerID)
4251 peerMO.dynamicInfo = modelPeer?.dynamicInfo?.data
4252 peerMO.dynamicInfoSig = modelPeer?.dynamicInfo?.sig
4254 peer.vouchers.forEach {
4255 if let voucher = TPVoucher(infoWith: $0.voucher, sig: $0.sig) {
4256 self.model.register(voucher)
4257 if peer.vouchers.filter({ $0.voucher == voucher.data && $0.sig == voucher.sig }).isEmpty {
4258 let voucherMO = VoucherMO(context: self.moc)
4259 voucherMO.voucherInfo = voucher.data
4260 voucherMO.voucherInfoSig = voucher.sig
4261 peerMO.addToVouchers(voucherMO)
4268 // Must be on moc queue to call this.
4269 private func fetchPeerMO(peerID: String) throws -> PeerMO? {
4270 let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
4271 fetch.predicate = NSPredicate(format: "peerID == %@ && container == %@", peerID, self.containerMO)
4272 let peers = try self.moc.fetch(fetch)
4273 return peers.first as? PeerMO
4276 // Must be on moc queue to call this.
4277 private func getPolicyDoc(_ policyVersion: UInt64) throws -> TPPolicyDocument {
4278 guard let policyDoc = self.model.policy(withVersion: policyVersion) else {
4279 throw ContainerError.unknownPolicyVersion(policyVersion)
4281 assert(policyVersion == policyDoc.version.versionNumber)
4282 if policyVersion == prevailingPolicyVersion.versionNumber {
4283 assert(policyDoc.version.policyHash == prevailingPolicyVersion.policyHash)
4288 // Must be on moc queue to call this.
4289 private func createNewStableInfoIfNeeded(stableChanges: StableChanges?,
4290 permanentInfo: TPPeerPermanentInfo,
4291 existingStableInfo: TPPeerStableInfo?,
4292 dynamicInfo: TPPeerDynamicInfo,
4293 signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? {
4294 func noChange<T: Equatable>(_ change: T?, _ existing: T?) -> Bool {
4295 return (nil == change) || change == existing
4298 let policyOfPeers = try? self.model.policy(forPeerIDs: dynamicInfo.includedPeerIDs,
4299 candidatePeerID: permanentInfo.peerID,
4300 candidateStableInfo: existingStableInfo)
4302 // Pick the best version of:
4303 // 1. The policy version asked for by the client
4304 // 2. The policy override set on this object (tests only)
4305 // 3. The max of our existing policyVersion, the highest policy used by our trusted peers, and the compile-time prevailing policy version
4306 let optimalPolicyVersionNumber = stableChanges?.policyVersion ??
4307 self.policyVersionOverride?.versionNumber ??
4308 max(existingStableInfo?.bestPolicyVersion().versionNumber ?? prevailingPolicyVersion.versionNumber,
4309 policyOfPeers?.version.versionNumber ?? prevailingPolicyVersion.versionNumber,
4310 prevailingPolicyVersion.versionNumber)
4312 // Determine which recovery key we'd like to be using, given our current idea of who to trust
4313 let optimalRecoveryKey = self.model.bestRecoveryKey(for: existingStableInfo, dynamicInfo: dynamicInfo)
4315 let intendedSyncUserControllableViews = stableChanges?.setSyncUserControllableViews?.sanitizeForPlatform(permanentInfo: permanentInfo)
4317 if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) &&
4318 noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) &&
4319 noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) &&
4320 noChange(optimalPolicyVersionNumber, existingStableInfo?.bestPolicyVersion().versionNumber) &&
4321 noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) &&
4322 noChange(optimalRecoveryKey?.signingKeyData, existingStableInfo?.recoverySigningPublicKey) &&
4323 noChange(optimalRecoveryKey?.encryptionKeyData, existingStableInfo?.recoveryEncryptionPublicKey) &&
4324 noChange(intendedSyncUserControllableViews, existingStableInfo?.syncUserControllableViews) {
4328 // 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
4329 let optimalPolicyVersion = try self.getPolicyDoc(optimalPolicyVersionNumber).version
4330 let useFrozenPolicyVersion = optimalPolicyVersion.versionNumber >= frozenPolicyVersion.versionNumber
4332 if let intendedSyncUserControllableViews = intendedSyncUserControllableViews {
4333 os_log("Intending to set user-controllable views to %{public}@", log: tplogTrace, type: .info, TPPBPeerStableInfo_UserControllableViewStatusAsString(intendedSyncUserControllableViews))
4336 return try self.model.createStableInfo(withFrozenPolicyVersion: useFrozenPolicyVersion ? frozenPolicyVersion : optimalPolicyVersion,
4337 flexiblePolicyVersion: useFrozenPolicyVersion ? optimalPolicyVersion : nil,
4338 policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets,
4339 syncUserControllableViews: intendedSyncUserControllableViews ?? existingStableInfo?.syncUserControllableViews ?? .UNKNOWN,
4340 deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "",
4341 serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "",
4342 osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "",
4343 signing: signingKeyPair,
4344 recoverySigningPubKey: optimalRecoveryKey?.signingKeyData,
4345 recoveryEncryptionPubKey: optimalRecoveryKey?.encryptionKeyData)
4348 private func assembleBottle(egoPeerID: String) throws -> Bottle {
4349 let bottleMoSet = containerMO.bottles as? Set<BottleMO>
4351 var bottleMOs = bottleMoSet?.filter {
4352 $0.peerID == egoPeerID
4355 if let count = bottleMOs?.count {
4357 throw ContainerError.tooManyBottlesForPeer
4358 // swiftlint:disable empty_count
4359 } else if count == 0 {
4360 // swiftlint:enable empty_count
4361 throw ContainerError.noBottleForPeer
4364 throw ContainerError.failedToAssembleBottle
4367 let BM: BottleMO? = (bottleMOs?.removeFirst())
4369 let bottle = try Bottle.with {
4370 $0.peerID = egoPeerID
4372 if let bottledPeerData = BM?.contents {
4373 $0.contents = bottledPeerData
4375 throw ContainerError.failedToAssembleBottle
4378 if let bottledPeerEscrowSigningSPKI = BM?.escrowedSigningSPKI {
4379 $0.escrowedSigningSpki = bottledPeerEscrowSigningSPKI
4381 throw ContainerError.failedToAssembleBottle
4384 if let bottledPeerSignatureUsingEscrowKey = BM?.signatureUsingEscrowKey {
4385 $0.signatureUsingEscrowKey = bottledPeerSignatureUsingEscrowKey
4387 throw ContainerError.failedToAssembleBottle
4390 if let bottledPeerSignatureUsingPeerKey = BM?.signatureUsingPeerKey {
4391 $0.signatureUsingPeerKey = bottledPeerSignatureUsingPeerKey
4393 throw ContainerError.failedToAssembleBottle
4396 if let bID = BM?.bottleID {
4399 throw ContainerError.failedToAssembleBottle
4406 func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) {
4407 self.semaphore.wait()
4408 let reply: (Error?) -> Void = {
4409 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4410 self.semaphore.signal()
4414 var updatedRequest = request
4416 if let egoPeerID = self.containerMO.egoPeerID {
4417 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, _ in
4418 updatedRequest.octagonEgoIdentity = (egoPeerKeys != nil)
4422 self.moc.performAndWait {
4423 self.cuttlefish.reportHealth(updatedRequest) { _, error in
4424 guard error == nil else {
4433 func pushHealthInquiry(reply: @escaping (Error?) -> Void) {
4434 self.semaphore.wait()
4435 let reply: (Error?) -> Void = {
4436 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4437 self.semaphore.signal()
4441 self.moc.performAndWait {
4442 self.cuttlefish.pushHealthInquiry(PushHealthInquiryRequest()) { _, error in
4443 guard error == nil else {