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@
29 import SecurityFoundation
31 let tplogDebug = OSLog(subsystem: "com.apple.security.trustedpeers", category: "debug")
32 let tplogTrace = OSLog(subsystem: "com.apple.security.trustedpeers", category: "trace")
34 let egoIdentitiesAccessGroup = "com.apple.security.egoIdentities"
36 extension ResetReason {
37 static func from(cuttlefishResetReason: CuttlefishResetReason) -> ResetReason {
38 switch cuttlefishResetReason {
40 return ResetReason.unknown
41 case .userInitiatedReset:
42 return ResetReason.userInitiatedReset
44 return ResetReason.healthCheck
45 case .noBottleDuringEscrowRecovery:
46 return ResetReason.noBottleDuringEscrowRecovery
47 case .legacyJoinCircle:
48 return ResetReason.legacyJoinCircle
50 return ResetReason.recoveryKey
52 return ResetReason.testGenerated
59 public enum ContainerError: Error {
60 case unableToCreateKeyPair
61 case noPreparedIdentity
62 case failedToStoreIdentity
63 case needsAuthentication
64 case missingStableInfo
65 case missingDynamicInfo
67 case invalidPermanentInfoOrSig
68 case invalidStableInfoOrSig
69 case invalidVoucherOrSig
70 case sponsorNotRegistered(String)
71 case unknownPolicyVersion(UInt64)
72 case preparedIdentityNotOnAllowedList(String)
73 case couldNotLoadAllowedList
74 case noPeersPreapprovePreparedIdentity
75 case policyDocumentDoesNotValidate
76 case tooManyBottlesForPeer
78 case restoreBottleFailed
79 case noBottlesForEscrowRecordID
80 case bottleDoesNotContainContents
81 case bottleDoesNotContainEscrowKeySignature
82 case bottleDoesNotContainerPeerKeySignature
83 case bottleDoesNotContainPeerID
84 case failedToCreateBottledPeer
85 case signatureVerificationFailed
86 case bottleDoesNotContainerEscrowKeySPKI
87 case failedToFetchEscrowContents
88 case failedToCreateRecoveryKey
89 case untrustedRecoveryKeys
91 case recoveryKeysNotEnrolled
92 case bottleCreatingPeerNotFound
93 case unknownCloudKitError
94 case cloudkitResponseMissing
95 case failedToLoadSecret(errorCode: Int)
96 case failedToLoadSecretDueToType
97 case failedToAssembleBottle
99 case failedToStoreSecret(errorCode: Int)
100 case unknownSecurityFoundationError
101 case failedToSerializeData
102 case unknownInternalError
105 extension ContainerError: LocalizedError {
106 public var errorDescription: String? {
108 case .unableToCreateKeyPair:
109 return "unable to create key pair"
110 case .noPreparedIdentity:
111 return "no prepared identity"
112 case .failedToStoreIdentity:
113 return "failed to stored identity"
114 case .needsAuthentication:
115 return "needs authentication"
116 case .missingStableInfo:
117 return "missing stable info"
118 case .missingDynamicInfo:
119 return "missing dynamic info"
122 case .invalidPermanentInfoOrSig:
123 return "invalid permanent info or signature"
124 case .invalidStableInfoOrSig:
125 return "invalid stable info or signature"
126 case .invalidVoucherOrSig:
127 return "invalid voucher or signature"
128 case .sponsorNotRegistered(let s):
129 return "sponsor not registered: \(s)"
130 case .unknownPolicyVersion(let v):
131 return "unknown policy version: \(v)"
132 case .preparedIdentityNotOnAllowedList(let id):
133 return "prepared identity (\(id)) not on allowed machineID list"
134 case .couldNotLoadAllowedList:
135 return "could not load allowed machineID list"
136 case .noPeersPreapprovePreparedIdentity:
137 return "no peers preapprove prepared identity"
138 case .policyDocumentDoesNotValidate:
139 return "policy document from server doesn't validate"
140 case .tooManyBottlesForPeer:
141 return "too many bottles exist for peer"
142 case .noBottleForPeer:
143 return "no bottle exists for peer"
144 case .restoreBottleFailed:
145 return "failed to restore bottle"
146 case .noBottlesForEscrowRecordID:
147 return "0 bottles exist for escrow record id"
148 case .bottleDoesNotContainContents:
149 return "bottle does not contain encrypted contents"
150 case .bottleDoesNotContainEscrowKeySignature:
151 return "bottle does not contain escrow signature"
152 case .bottleDoesNotContainerPeerKeySignature:
153 return "bottle does not contain peer signature"
154 case .bottleDoesNotContainPeerID:
155 return "bottle does not contain peer id"
156 case .failedToCreateBottledPeer:
157 return "failed to create a bottled peer"
158 case .signatureVerificationFailed:
159 return "failed to verify signature"
160 case .bottleDoesNotContainerEscrowKeySPKI:
161 return "bottle does not contain escrowed key spki"
162 case .failedToFetchEscrowContents:
163 return "failed to fetch escrow contents"
164 case .failedToCreateRecoveryKey:
165 return "failed to create recovery keys"
166 case .untrustedRecoveryKeys:
167 return "untrusted recovery keys"
168 case .noBottlesPresent:
169 return "no bottle present"
170 case .recoveryKeysNotEnrolled:
171 return "recovery key is not enrolled with octagon"
172 case .bottleCreatingPeerNotFound:
173 return "The peer that created the bottle was not found"
174 case .unknownCloudKitError:
175 return "unknown error from cloudkit"
176 case .cloudkitResponseMissing:
177 return "Response missing from CloudKit"
178 case .failedToLoadSecret(errorCode: let errorCode):
179 return "failed to load secret: \(errorCode)"
180 case .failedToLoadSecretDueToType:
181 return "Failed to load secret due to type mismatch (value was not dictionary)"
182 case .failedToAssembleBottle:
183 return "failed to assemble bottle for peer"
185 return "peerID is invalid"
186 case .failedToStoreSecret(errorCode: let errorCode):
187 return "failed to store the secret in the keychain \(errorCode)"
188 case .unknownSecurityFoundationError:
189 return "SecurityFoundation returned an unknown type"
190 case .failedToSerializeData:
191 return "Failed to encode protobuf data"
192 case .unknownInternalError:
193 return "Internal code failed, but didn't return error"
198 extension ContainerError: CustomNSError {
200 public static var errorDomain: String {
201 return "com.apple.security.trustedpeers.container"
204 public var errorCode: Int {
206 case .unableToCreateKeyPair:
208 case .noPreparedIdentity:
210 case .failedToStoreIdentity:
212 case .needsAuthentication:
214 case .missingStableInfo:
216 case .missingDynamicInfo:
220 case .invalidPermanentInfoOrSig:
222 case .invalidVoucherOrSig:
224 case .invalidStableInfoOrSig:
226 case .sponsorNotRegistered:
228 case .unknownPolicyVersion:
230 case .preparedIdentityNotOnAllowedList:
232 case .noPeersPreapprovePreparedIdentity:
234 case .policyDocumentDoesNotValidate:
236 // 16 was invalidPeerID and failedToAssembleBottle
237 case .tooManyBottlesForPeer:
239 case .noBottleForPeer:
241 case .restoreBottleFailed:
243 case .noBottlesForEscrowRecordID:
245 case .bottleDoesNotContainContents:
247 case .bottleDoesNotContainEscrowKeySignature:
249 case .bottleDoesNotContainerPeerKeySignature:
251 case .bottleDoesNotContainPeerID:
253 case .failedToCreateBottledPeer:
255 case .signatureVerificationFailed:
257 // 27 was failedToLoadSecret*
258 case .bottleDoesNotContainerEscrowKeySPKI:
260 case .failedToFetchEscrowContents:
262 case .couldNotLoadAllowedList:
264 case .failedToCreateRecoveryKey:
266 case .untrustedRecoveryKeys:
268 case .noBottlesPresent:
270 case .recoveryKeysNotEnrolled:
272 case .bottleCreatingPeerNotFound:
274 case .unknownCloudKitError:
276 case .cloudkitResponseMissing:
278 case .failedToLoadSecret:
280 case .failedToLoadSecretDueToType:
282 case .failedToStoreSecret:
284 case .failedToAssembleBottle:
288 case .unknownSecurityFoundationError:
290 case .failedToSerializeData:
292 case .unknownInternalError:
297 public var underlyingError: NSError? {
299 case .failedToLoadSecret(errorCode: let errorCode):
300 return NSError(domain: "securityd", code: errorCode)
301 case .failedToStoreSecret(errorCode: let errorCode):
302 return NSError(domain: "securityd", code: errorCode)
308 public var errorUserInfo: [String: Any] {
309 var ret = [String: Any]()
310 if let desc = self.errorDescription {
311 ret[NSLocalizedDescriptionKey] = desc
313 if let underlyingError = self.underlyingError {
314 ret[NSUnderlyingErrorKey] = underlyingError
320 internal func traceError(_ error: Error?) -> String {
321 if let error = error {
322 return "error: \(String(describing: error))"
328 func saveSecret(_ secret: Data, label: String) throws {
330 let query: [CFString: Any] = [
331 kSecClass: kSecClassInternetPassword,
332 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
333 kSecUseDataProtectionKeychain: true,
334 kSecAttrAccessGroup: "com.apple.security.octagon",
335 kSecAttrSynchronizable: false,
336 kSecAttrDescription: label,
338 kSecValueData: secret,
341 var results: CFTypeRef?
342 var status = SecItemAdd(query as CFDictionary, &results)
344 if status == errSecSuccess {
348 if status == errSecDuplicateItem {
349 // Add every primary key attribute to this find dictionary
350 var findQuery: [CFString: Any] = [:]
351 findQuery[kSecClass] = query[kSecClass]
352 findQuery[kSecAttrSynchronizable] = query[kSecAttrSynchronizable]
353 findQuery[kSecAttrAccessGroup] = query[kSecAttrAccessGroup]
354 findQuery[kSecAttrServer] = query[kSecAttrDescription]
355 findQuery[kSecAttrPath] = query[kSecAttrPath]
356 findQuery[kSecUseDataProtectionKeychain] = query[kSecUseDataProtectionKeychain]
358 var updateQuery: [CFString: Any] = query
359 updateQuery[kSecClass] = nil
361 status = SecItemUpdate(findQuery as CFDictionary, updateQuery as CFDictionary)
363 if status != errSecSuccess {
364 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
367 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
371 func loadSecret(label: String) throws -> (Data?) {
374 let query: [CFString: Any] = [
375 kSecClass: kSecClassInternetPassword,
376 kSecAttrAccessGroup: "com.apple.security.octagon",
377 kSecAttrDescription: label,
378 kSecReturnAttributes: true,
379 kSecReturnData: true,
380 kSecAttrSynchronizable: false,
381 kSecMatchLimit: kSecMatchLimitOne,
384 var result: CFTypeRef?
385 let status = SecItemCopyMatching(query as CFDictionary, &result)
387 if status != errSecSuccess || result == nil {
388 throw ContainerError.failedToLoadSecret(errorCode: Int(status))
392 if let dictionary = result as? [CFString: Any] {
393 secret = dictionary[kSecValueData] as? Data
395 throw ContainerError.failedToLoadSecretDueToType
401 func saveEgoKeyPair(_ keyPair: _SFECKeyPair, identifier: String, resultHandler: @escaping (Bool, Error?) -> Void) {
402 let keychainManager = _SFKeychainManager.default()
403 let signingIdentity = _SFIdentity(keyPair: keyPair)
404 let accessibility = SFAccessibilityMakeWithMode(SFAccessibilityMode.accessibleWhenUnlocked) // class A
405 let accessPolicy = _SFAccessPolicy(accessibility: accessibility,
406 sharingPolicy: SFSharingPolicy.thisDeviceOnly)
407 accessPolicy.accessGroup = egoIdentitiesAccessGroup
408 keychainManager.setIdentity(signingIdentity,
409 forIdentifier: identifier,
410 accessPolicy: accessPolicy,
411 resultHandler: resultHandler)
414 func loadEgoKeyPair(identifier: String, resultHandler: @escaping (_SFECKeyPair?, Error?) -> Void) {
415 let keychainManager = _SFKeychainManager.default()
417 // FIXME constrain to egoIdentitiesAccessGroup, <rdar://problem/39597940>
418 keychainManager.identity(forIdentifier: identifier) { result in
419 switch result.resultType {
420 case .valueAvailable:
421 resultHandler(result.value?.keyPair as? _SFECKeyPair, nil)
422 case .needsAuthentication:
423 resultHandler(nil, ContainerError.needsAuthentication)
425 resultHandler(nil, result.error)
427 resultHandler(nil, ContainerError.unknownSecurityFoundationError)
432 func loadEgoKeys(peerID: String, resultHandler: @escaping (OctagonSelfPeerKeys?, Error?) -> Void) {
433 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: peerID)) { signingKey, error in
434 guard let signingKey = signingKey else {
435 os_log("Unable to load signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
436 resultHandler(nil, error)
440 loadEgoKeyPair(identifier: encryptionKeyIdentifier(peerID: peerID)) { encryptionKey, error in
441 guard let encryptionKey = encryptionKey else {
442 os_log("Unable to load encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
443 resultHandler(nil, error)
448 resultHandler(try OctagonSelfPeerKeys(peerID: peerID, signingKey: signingKey, encryptionKey: encryptionKey), nil)
450 resultHandler(nil, error)
456 func removeEgoKeysSync(peerID: String) throws -> Bool {
457 var resultSema = DispatchSemaphore(value: 0)
459 let keychainManager = _SFKeychainManager.default()
461 var retresultForSigningDeletion: Bool = false
462 var reterrorForSigningDeletion: Error?
464 //remove signing keys
465 keychainManager.removeItem(withIdentifier: signingKeyIdentifier(peerID: peerID)) { result, error in
466 retresultForSigningDeletion = result
467 reterrorForSigningDeletion = error
473 if let error = reterrorForSigningDeletion {
477 if retresultForSigningDeletion == false {
478 return retresultForSigningDeletion
481 // now let's do the same thing with the encryption keys
482 resultSema = DispatchSemaphore(value: 0)
483 var retresultForEncryptionDeletion: Bool = false
484 var reterrorForEncryptionDeletion: Error?
486 keychainManager.removeItem(withIdentifier: encryptionKeyIdentifier(peerID: peerID)) { result, error in
487 retresultForEncryptionDeletion = result
488 reterrorForEncryptionDeletion = error
493 if let error = reterrorForEncryptionDeletion {
497 return retresultForEncryptionDeletion && retresultForSigningDeletion
500 func loadEgoKeysSync(peerID: String) throws -> OctagonSelfPeerKeys {
501 // Gotta promote to synchronous; 'antipattern' ahoy
502 let resultSema = DispatchSemaphore(value: 0)
504 var result: OctagonSelfPeerKeys?
507 loadEgoKeys(peerID: peerID) { keys, error in
514 if let error = reserror {
518 if let result = result {
525 func signingKeyIdentifier(peerID: String) -> String {
526 return "signing-key " + peerID
529 func encryptionKeyIdentifier(peerID: String) -> String {
530 return "encryption-key " + peerID
533 func makeTLKShares(ckksTLKs: [CKKSKeychainBackedKey]?, asPeer: CKKSSelfPeer, toPeer: CKKSPeer, epoch: Int) throws -> [TLKShare] {
534 return try (ckksTLKs ?? []).map { tlk in
535 os_log("Making TLKShare for %@ for key %@", log: tplogDebug, type: .default, toPeer.description, tlk)
536 // Not being able to convert a TLK to a TLKShare is a failure, but not having a TLK is only half bad
538 return TLKShare.convert(ckksTLKShare: try CKKSTLKShare(tlk, as: asPeer, to: toPeer, epoch: epoch, poisoned: 0))
540 let nserror = error as NSError
541 if nserror.domain == "securityd" && nserror.code == errSecItemNotFound {
542 os_log("No TLK contents for %@, no TLK share possible", log: tplogDebug, type: .default, tlk)
552 func extract(tlkShares: [CKKSTLKShare], peer: OctagonSelfPeerKeys, model: TPModel) -> (Int64, Int64) {
553 os_log("Attempting to recover %d TLK shares for peer %{public}@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID)
554 var tlksRecovered: Set<String> = Set()
555 var sharesRecovered: Int64 = 0
557 for share in tlkShares {
558 guard share.receiverPeerID == peer.peerID else {
559 os_log("Skipping %@ (wrong peerID)", log: tplogDebug, type: .default, share)
564 var trustedPeers: [AnyHashable] = [peer]
566 if let egoPeer = model.peer(withID: peer.peerID) {
567 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
568 if let peer = model.peer(withID: trustedPeerID) {
569 let peerObj = CKKSActualPeer(peerID: trustedPeerID,
570 encryptionPublicKey: (peer.permanentInfo.encryptionPubKey as! _SFECPublicKey),
571 signing: (peer.permanentInfo.signingPubKey as! _SFECPublicKey),
574 trustedPeers.append(peerObj)
576 os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID)
580 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
583 let key = try share.recoverTLK(peer,
584 trustedPeers: Set(trustedPeers),
587 try key.saveMaterialToKeychain()
588 tlksRecovered.insert(key.uuid)
590 os_log("Recovered %@ (from %@)", log: tplogDebug, type: .default, key, share)
592 os_log("Failed to recover share %@: %{public}@", log: tplogDebug, type: .default, share, error as CVarArg)
596 return (Int64(tlksRecovered.count), sharesRecovered)
599 struct ContainerState {
600 var egoPeerID: String?
601 var peers: [String: TPPeer] = [:]
602 var vouchers: [TPVoucher] = []
603 var bottles = Set<BottleMO>()
604 var recoverySigningKey: Data?
605 var recoveryEncryptionKey: Data?
608 internal struct StableChanges {
609 let deviceName: String?
610 let serialNumber: String?
611 let osVersion: String?
612 let policyVersion: UInt64?
613 let policySecrets: [String: Data]?
616 // CoreData doesn't handle creating an identical model from an identical URL. Help it out.
617 private var nsObjectModels: [URL: NSManagedObjectModel] = [:]
618 private let nsObjectModelsQueue = DispatchQueue(label: "com.apple.security.TrustedPeersHelper.nsObjectModels")
619 func getOrMakeModel(url: URL) -> NSManagedObjectModel {
620 return nsObjectModelsQueue.sync {
621 if let model = nsObjectModels[url] {
624 let newModel = NSManagedObjectModel(contentsOf: url)!
625 nsObjectModels[url] = newModel
630 struct ContainerName: Hashable, CustomStringConvertible {
631 let container: String
634 func asSingleString() -> String {
635 // Just to be nice, hide the fact that multiple contexts exist on most machines
636 if self.context == OTDefaultContext {
637 return self.container
639 return self.container + "-" + self.context
643 var description: String {
644 return "Container(\(self.container),\(self.context))"
648 extension ContainerMO {
649 func egoStableInfo() -> TPPeerStableInfo? {
650 guard let egoStableData = self.egoPeerStableInfo,
651 let egoStableSig = self.egoPeerStableInfoSig else {
655 return TPPeerStableInfo(data: egoStableData, sig: egoStableSig)
659 /// This maps to a Cuttlefish service backed by a CloudKit container,
660 /// and a corresponding local Core Data persistent container.
662 /// Methods may be invoked concurrently.
663 class Container: NSObject {
664 let name: ContainerName
666 private let cuttlefish: CuttlefishAPIAsync
668 // Only one request (from Client) is permitted to be in progress at a time.
669 // That includes while waiting for network, i.e. one request must complete
670 // before the next can begin. Otherwise two requests could in parallel be
671 // fetching updates from Cuttlefish, with ensuing changeToken overwrites etc.
672 // This applies for mutating requests -- requests that can only read the current
673 // state (on the moc queue) do not need to.
674 internal let semaphore = DispatchSemaphore(value: 1)
676 // All Core Data access happens through moc: NSManagedObjectContext. The
677 // moc insists on having its own queue, and all operations must happen on
679 internal let moc: NSManagedObjectContext
681 // Rather than Container having its own dispatch queue, we use moc's queue
682 // to synchronise access to our own state as well. So the following instance
683 // variables must only be accessed within blocks executed by calling
684 // moc.perform() or moc.performAndWait().
685 internal var containerMO: ContainerMO
686 internal var model: TPModel
688 Construct a Container.
690 - Parameter name: The name the CloudKit container to which requests will be routed.
692 The "real" container that drives CKKS etc should be named `"com.apple.security.keychain"`.
693 Use other names for test containers such as for rawfish (rawhide-style testing for cuttlefish)
695 - Parameter persistentStoreURL: The location the local Core Data database for this container will be stored.
697 - Parameter cuttlefish: Interface to cuttlefish.
699 init(name: ContainerName, persistentStoreDescription: NSPersistentStoreDescription, cuttlefish: CuttlefishAPIAsync) throws {
700 var initError: Error?
701 var containerMO: ContainerMO?
704 // Set up Core Data stack
705 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
706 let mom = getOrMakeModel(url: url)
707 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
708 persistentContainer.persistentStoreDescriptions = [persistentStoreDescription]
710 persistentContainer.loadPersistentStores { _, error in
713 if let initError = initError {
717 let moc = persistentContainer.newBackgroundContext()
718 moc.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
721 // Fetch an existing ContainerMO record if it exists, or create and save one
723 let containerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Container")
724 containerFetch.predicate = NSPredicate(format: "name == %@", name.asSingleString())
725 let fetchedContainers = try moc.fetch(containerFetch)
726 if let container = fetchedContainers.first as? ContainerMO {
727 containerMO = container
729 containerMO = ContainerMO(context: moc)
730 containerMO!.name = name.asSingleString()
733 // Perform upgrades as needed
734 Container.onqueueUpgradeMachineIDSetToModel(container: containerMO!, moc: moc)
735 Container.onqueueUpgradeMachineIDSetToUseStatus(container: containerMO!, moc: moc)
737 model = Container.loadModel(from: containerMO!)
738 Container.ensureEgoConsistency(from: containerMO!, model: model!)
745 if let initError = initError {
751 self.containerMO = containerMO!
752 self.cuttlefish = cuttlefish
757 // Must be on containerMO's moc queue to call this
758 internal static func loadModel(from containerMO: ContainerMO) -> TPModel {
759 // Populate model from persistent store
760 let model = TPModel(decrypter: Decrypter())
761 let keyFactory = TPECPublicKeyFactory()
762 let peers = containerMO.peers as? Set<PeerMO>
763 peers?.forEach { peer in
764 guard let permanentInfo = TPPeerPermanentInfo(peerID: peer.peerID!,
765 data: peer.permanentInfo! as Data,
766 sig: peer.permanentInfoSig! as Data,
767 keyFactory: keyFactory) else {
770 model.registerPeer(with: permanentInfo)
771 if let data = peer.stableInfo, let sig = peer.stableInfoSig {
772 if let stableInfo = TPPeerStableInfo(data: data as Data, sig: sig as Data) {
774 try model.update(stableInfo, forPeerWithID: permanentInfo.peerID)
776 os_log("loadModel unable to update stable info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg)
779 os_log("loadModel: peer %{public}@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
782 os_log("loadModel: peer %{public}@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
784 if let data = peer.dynamicInfo, let sig = peer.dynamicInfoSig {
785 if let dynamicInfo = TPPeerDynamicInfo(data: data as Data, sig: sig as Data) {
787 try model.update(dynamicInfo, forPeerWithID: permanentInfo.peerID)
789 os_log("loadModel unable to update dynamic info for peer(%{public}@): %{public}@", log: tplogDebug, type: .default, peer, error as CVarArg)
792 os_log("loadModel: peer %{public}@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
795 os_log("loadModel: peer %{public}@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
797 peer.vouchers?.forEach {
798 let v = $0 as! VoucherMO
799 if let data = v.voucherInfo, let sig = v.voucherInfoSig {
800 if let voucher = TPVoucher(infoWith: data, sig: sig) {
801 model.register(voucher)
807 if let recoveryKeySigningSPKI = containerMO.recoveryKeySigningSPKI,
808 let recoveryKeyEncyryptionSPKI = containerMO.recoveryKeyEncryptionSPKI {
809 model.setRecoveryKeys(TPRecoveryKeyPair(signingSPKI: recoveryKeySigningSPKI, encryptionSPKI: recoveryKeyEncyryptionSPKI))
811 // If the ego peer has an RK set, tell the model to use that one
812 // 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
813 if let egoStableInfo = containerMO.egoStableInfo(),
814 egoStableInfo.recoverySigningPublicKey.count > 0,
815 egoStableInfo.recoveryEncryptionPublicKey.count > 0 {
816 os_log("loadModel: recovery key not set in model, but is set on ego peer", log: tplogDebug, type: .default)
817 model.setRecoveryKeys(TPRecoveryKeyPair(signingSPKI: egoStableInfo.recoverySigningPublicKey, encryptionSPKI: egoStableInfo.recoveryEncryptionPublicKey))
821 // Register persisted policies (cached from cuttlefish)
822 let policies = containerMO.policies as? Set<PolicyMO>
823 policies?.forEach { policyMO in
824 if let policyHash = policyMO.policyHash,
825 let policyData = policyMO.policyData {
826 if let policyDoc = TPPolicyDocument.policyDoc(withHash: policyHash, data: policyData) {
827 model.register(policyDoc)
832 // Register built-in policies
833 builtInPolicyDocuments().forEach { policyDoc in
834 model.register(policyDoc)
837 let knownMachines = containerMO.machines as? Set<MachineMO> ?? Set()
838 let allowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.allowed.rawValue }.compactMap { $0.machineID })
839 let disallowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.disallowed.rawValue }.compactMap { $0.machineID })
841 os_log("loadModel: allowedMachineIDs: %{public}@", log: tplogDebug, type: .default, allowedMachineIDs)
842 os_log("loadModel: disallowedMachineIDs: %{public}@", log: tplogDebug, type: .default, disallowedMachineIDs)
844 if allowedMachineIDs.isEmpty {
845 os_log("loadModel: no allowedMachineIDs?", log: tplogDebug, type: .default)
851 // Must be on containerMO's moc queue to call this
852 internal static func ensureEgoConsistency(from containerMO: ContainerMO, model: TPModel) {
853 guard let egoPeerID = containerMO.egoPeerID,
854 let egoStableData = containerMO.egoPeerStableInfo,
855 let egoStableSig = containerMO.egoPeerStableInfoSig
857 os_log("ensureEgoConsistency failed to find ego peer information", log: tplogDebug, type: .error)
861 guard let containerEgoStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
862 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from container", log: tplogDebug, type: .error)
866 guard let modelStableInfo = model.getStableInfoForPeer(withID: egoPeerID) else {
867 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from model", log: tplogDebug, type: .error)
871 if modelStableInfo.clock > containerEgoStableInfo.clock {
872 containerMO.egoPeerStableInfo = modelStableInfo.data
873 containerMO.egoPeerStableInfoSig = modelStableInfo.sig
878 static func dictionaryRepresentation(bottle: BottleMO) -> [String: Any] {
879 var dict: [String: String] = [:]
881 dict["bottleID"] = bottle.bottleID
882 dict["peerID"] = bottle.peerID
883 dict["signingSPKI"] = bottle.escrowedSigningSPKI?.base64EncodedString()
884 dict["signatureUsingPeerKey"] = bottle.signatureUsingPeerKey?.base64EncodedString()
885 dict["signatureUsingSPKI"] = bottle.signatureUsingEscrowKey?.base64EncodedString()
886 // ignore the bottle contents; they're mostly unreadable
891 static func peerdictionaryRepresentation(peer: TPPeer) -> [String: Any] {
892 var peerDict: [String: Any] = [
893 "permanentInfo": peer.permanentInfo.dictionaryRepresentation(),
894 "peerID": peer.peerID,
896 if let stableInfo = peer.stableInfo {
897 peerDict["stableInfo"] = stableInfo.dictionaryRepresentation()
899 if let dynamicInfo = peer.dynamicInfo {
900 peerDict["dynamicInfo"] = dynamicInfo.dictionaryRepresentation()
906 func onQueueDetermineLocalTrustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
907 let viablePeerCountsByModelID = self.model.viablePeerCountsByModelID()
908 let peerCountsByMachineID = self.model.peerCountsByMachineID()
910 if let egoPeerID = self.containerMO.egoPeerID {
911 var status = self.model.statusOfPeer(withID: egoPeerID)
912 var isExcluded: Bool = (status == .excluded)
914 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, loadError in
915 var returnError = loadError
917 guard returnError == nil else {
919 if let error = (loadError as NSError?) {
920 os_log("trust status: Unable to load ego keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
921 if error.code == errSecItemNotFound && error.domain == NSOSStatusErrorDomain {
922 os_log("trust status: Lost the ego key pair, returning 'excluded' in hopes of fixing up the identity", log: tplogDebug, type: .debug)
925 } else if error.code == errSecInteractionNotAllowed && error.domain == NSOSStatusErrorDomain {
926 // we have an item, but can't read it, suppressing error
932 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
934 viablePeerCountsByModelID: viablePeerCountsByModelID,
935 peerCountsByMachineID: peerCountsByMachineID,
936 isExcluded: isExcluded,
938 reply(egoStatus, returnError)
942 //ensure egoPeerKeys are populated
943 guard egoPeerKeys != nil else {
944 os_log("trust status: No error but Ego Peer Keys are nil", log: tplogDebug, type: .default)
945 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
947 viablePeerCountsByModelID: viablePeerCountsByModelID,
948 peerCountsByMachineID: peerCountsByMachineID,
952 reply(egoStatus, loadError)
956 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
958 viablePeerCountsByModelID: viablePeerCountsByModelID,
959 peerCountsByMachineID: peerCountsByMachineID,
960 isExcluded: isExcluded,
962 reply(egoStatus, nil)
967 // With no ego peer ID, either return 'excluded' if there are extant peers, or 'unknown' to signal no peers at all
968 if self.model.allPeerIDs().isEmpty {
969 os_log("No existing peers in account", log: tplogDebug, type: .debug)
970 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
972 viablePeerCountsByModelID: viablePeerCountsByModelID,
973 peerCountsByMachineID: peerCountsByMachineID,
976 reply(egoStatus, nil)
979 os_log("Existing peers in account, but we don't have a peer ID. We are excluded.", log: tplogDebug, type: .debug)
980 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
982 viablePeerCountsByModelID: viablePeerCountsByModelID,
983 peerCountsByMachineID: peerCountsByMachineID,
986 reply(egoStatus, nil)
992 func trustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
993 self.semaphore.wait()
994 let reply: (TrustedPeersHelperEgoPeerStatus, Error?) -> Void = {
995 // Suppress logging of successful replies here; it's not that useful
996 let logType: OSLogType = $1 == nil ? .debug : .info
997 os_log("trustStatus complete: %{public}@ %{public}@",
998 log: tplogTrace, type: logType, TPPeerStatusToString($0.egoStatus), traceError($1))
1000 self.semaphore.signal()
1003 self.moc.performAndWait {
1004 // Knowledge of your peer status only exists if you know about other peers. If you haven't fetched, fetch.
1005 if self.containerMO.changeToken == nil {
1006 self.fetchAndPersistChanges { fetchError in
1007 guard fetchError == nil else {
1008 if let error = fetchError {
1009 os_log("Unable to fetch changes, trust status is unknown: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1012 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
1014 viablePeerCountsByModelID: [:],
1015 peerCountsByMachineID: [:],
1018 reply(egoStatus, fetchError)
1022 self.moc.performAndWait {
1023 self.onQueueDetermineLocalTrustStatus(reply: reply)
1027 self.onQueueDetermineLocalTrustStatus(reply: reply)
1032 func fetchTrustState(reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) {
1033 let reply: (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void = {
1034 os_log("fetch trust state complete: %{public}@ %{public}@",
1035 log: tplogTrace, type: .info, String(reflecting: $0), traceError($2))
1039 self.moc.performAndWait {
1040 if let egoPeerID = self.containerMO.egoPeerID,
1041 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1042 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig {
1044 let keyFactory = TPECPublicKeyFactory()
1045 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1046 os_log("fetchTrustState failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
1047 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
1051 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(permanentInfo.signingPubKey.spki())
1052 os_log("fetchTrustState: ego peer is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
1054 let egoStableInfo = self.model.getStableInfoForPeer(withID: egoPeerID)
1056 let egoPeerStatus = TrustedPeersHelperPeerState(peerID: egoPeerID,
1057 isPreapproved: isPreapproved,
1058 status: self.model.statusOfPeer(withID: egoPeerID),
1059 memberChanges: false,
1060 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
1061 osVersion: egoStableInfo?.osVersion)
1063 var tphPeers: [TrustedPeersHelperPeer] = []
1065 if let egoPeer = self.model.peer(withID: egoPeerID) {
1066 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
1067 if let peer = self.model.peer(withID: trustedPeerID) {
1068 let peerViews = try? self.model.getViewsForPeer(peer.permanentInfo,
1069 stableInfo: peer.stableInfo)
1071 tphPeers.append(TrustedPeersHelperPeer(peerID: trustedPeerID,
1072 signingSPKI: peer.permanentInfo.signingPubKey.spki(),
1073 encryptionSPKI: peer.permanentInfo.encryptionPubKey.spki(),
1074 viewList: peerViews ?? Set()))
1076 os_log("No peer for trusted ID %{public}@", log: tplogDebug, type: .default, trustedPeerID)
1080 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
1083 os_log("Returning trust state: %{public}@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers)
1084 reply(egoPeerStatus, tphPeers, nil)
1086 // With no ego peer ID, there are no trusted peers
1087 os_log("No peer ID => no trusted peers", log: tplogDebug, type: .debug)
1088 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: false, unknownMachineIDs: false, osVersion: nil), [], nil)
1093 func dump(reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1094 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1095 os_log("dump complete: %{public}@",
1096 log: tplogTrace, type: .info, traceError($1))
1099 self.moc.performAndWait {
1100 var d: [AnyHashable: Any] = [:]
1102 if let egoPeerID = self.containerMO.egoPeerID {
1103 if let peer = self.model.peer(withID: egoPeerID) {
1104 d["self"] = Container.peerdictionaryRepresentation(peer: peer)
1106 d["self"] = ["peerID": egoPeerID]
1112 d["peers"] = self.model.allPeers().filter { $0.peerID != self.containerMO.egoPeerID }.map { peer in
1113 Container.peerdictionaryRepresentation(peer: peer)
1116 d["vouchers"] = self.model.allVouchers().map { $0.dictionaryRepresentation() }
1118 if let bottles = self.containerMO.bottles as? Set<BottleMO> {
1119 d["bottles"] = bottles.map { Container.dictionaryRepresentation(bottle: $0) }
1124 let midList = self.onqueueCurrentMIDList()
1125 d["machineIDsAllowed"] = midList.machineIDs(in: .allowed).sorted()
1126 d["machineIDsDisallowed"] = midList.machineIDs(in: .disallowed).sorted()
1127 d["modelRecoverySigningPublicKey"] = self.model.recoverySigningPublicKey()
1128 d["modelRecoveryEncryptionPublicKey"] = self.model.recoveryEncryptionPublicKey()
1134 func dumpEgoPeer(reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) {
1135 let reply: (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void = {
1136 os_log("dumpEgoPeer complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
1137 reply($0, $1, $2, $3, $4)
1139 self.moc.performAndWait {
1140 guard let egoPeerID = self.containerMO.egoPeerID else {
1141 reply(nil, nil, nil, nil, ContainerError.noPreparedIdentity)
1145 guard let peer = self.model.peer(withID: egoPeerID) else {
1146 reply(egoPeerID, nil, nil, nil, nil)
1150 reply(egoPeerID, peer.permanentInfo, peer.stableInfo, peer.dynamicInfo, nil)
1154 func validatePeers(request: ValidatePeersRequest, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1155 self.semaphore.wait()
1156 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1157 os_log("validatePeers complete %{public}@", log: tplogTrace, type: .info, traceError($1))
1158 self.semaphore.signal()
1162 self.cuttlefish.validatePeers(request) { response, error in
1163 os_log("ValidatePeers(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1164 guard let response = response, error == nil else {
1165 os_log("validatePeers failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1166 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
1170 var info: [AnyHashable: Any] = [:]
1171 info["health"] = response.validatorsHealth as AnyObject
1172 info["results"] = try? JSONSerialization.jsonObject(with: response.jsonUTF8Data())
1178 func reset(resetReason: CuttlefishResetReason, reply: @escaping (Error?) -> Void) {
1179 self.semaphore.wait()
1180 let reply: (Error?) -> Void = {
1181 os_log("reset complete %{public}@", log: tplogTrace, type: .info, traceError($0))
1182 self.semaphore.signal()
1186 self.moc.performAndWait {
1187 let resetReason = ResetReason.from(cuttlefishResetReason: resetReason)
1188 let request = ResetRequest.with {
1189 $0.resetReason = resetReason
1191 self.cuttlefish.reset(request) { response, error in
1192 os_log("Reset(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1193 guard let response = response, error == nil else {
1194 os_log("reset failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1195 reply(error ?? ContainerError.cloudkitResponseMissing)
1199 // Erase container's persisted state
1200 self.moc.performAndWait {
1201 self.moc.delete(self.containerMO)
1202 self.containerMO = ContainerMO(context: self.moc)
1203 self.containerMO.name = self.name.asSingleString()
1204 self.model = Container.loadModel(from: self.containerMO)
1206 try self.onQueuePersist(changes: response.changes)
1207 os_log("reset succeded", log: tplogDebug, type: .default)
1210 os_log("reset persist failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1218 func localReset(reply: @escaping (Error?) -> Void) {
1219 self.semaphore.wait()
1220 let reply: (Error?) -> Void = {
1221 os_log("localReset complete %{public}@", log: tplogTrace, type: .info, traceError($0))
1222 self.semaphore.signal()
1226 self.moc.performAndWait {
1228 // Erase container's persisted state
1229 self.moc.delete(self.containerMO)
1230 self.containerMO = ContainerMO(context: self.moc)
1231 self.containerMO.name = self.name.asSingleString()
1232 self.model = Container.loadModel(from: self.containerMO)
1242 func loadOrCreateKeyPair(privateKeyPersistentRef: Data?) throws -> _SFECKeyPair {
1243 if let privateKeyPersistentRef = privateKeyPersistentRef {
1244 return try TPHObjectiveC.fetchKeyPair(withPrivateKeyPersistentRef: privateKeyPersistentRef)
1246 let keySpecifier = _SFECKeySpecifier(curve: SFEllipticCurve.nistp384)
1247 guard let keyPair = _SFECKeyPair(randomKeyPairWith: keySpecifier) else {
1248 throw ContainerError.unableToCreateKeyPair
1254 // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion.versionNumber
1255 func prepare(epoch: UInt64,
1260 deviceName: String?,
1261 serialNumber: String,
1263 policyVersion: TPPolicyVersion?,
1264 policySecrets: [String: Data]?,
1265 signingPrivateKeyPersistentRef: Data?,
1266 encryptionPrivateKeyPersistentRef: Data?,
1267 reply: @escaping (String?, Data?, Data?, Data?, Data?, Set<String>?, TPPolicy?, Error?) -> Void) {
1268 self.semaphore.wait()
1269 let reply: (String?, Data?, Data?, Data?, Data?, Set<String>?, TPPolicy?, Error?) -> Void = {
1270 os_log("prepare complete peerID: %{public}@ %{public}@",
1271 log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($7))
1272 self.semaphore.signal()
1273 reply($0, $1, $2, $3, $4, $5, $6, $7)
1276 // Create a new peer identity with random keys, and store the keys in keychain
1277 let permanentInfo: TPPeerPermanentInfo
1278 let signingKeyPair: _SFECKeyPair
1279 let encryptionKeyPair: _SFECKeyPair
1281 signingKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: signingPrivateKeyPersistentRef)
1282 encryptionKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: encryptionPrivateKeyPersistentRef)
1284 // <rdar://problem/56270219> Octagon: use epoch transmitted across pairing channel
1285 permanentInfo = try TPPeerPermanentInfo(machineID: machineID,
1288 signing: signingKeyPair,
1289 encryptionKeyPair: encryptionKeyPair,
1290 peerIDHashAlgo: TPHashAlgo.SHA256)
1293 reply(nil, nil, nil, nil, nil, nil, nil, error)
1297 let peerID = permanentInfo.peerID
1299 let bottle: BottledPeer
1301 bottle = try BottledPeer(peerID: peerID,
1303 peerSigningKey: signingKeyPair,
1304 peerEncryptionKey: encryptionKeyPair,
1305 bottleSalt: bottleSalt)
1307 _ = try saveSecret(bottle.secret, label: peerID)
1309 os_log("bottle creation failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1310 reply(nil, nil, nil, nil, nil, nil, nil, error)
1314 saveEgoKeyPair(signingKeyPair, identifier: signingKeyIdentifier(peerID: peerID)) { success, error in
1315 guard success else {
1316 os_log("Unable to save signing key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1317 reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1320 saveEgoKeyPair(encryptionKeyPair, identifier: encryptionKeyIdentifier(peerID: peerID)) { success, error in
1321 guard success else {
1322 os_log("Unable to save encryption key: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1323 reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1327 let policyVersion = policyVersion ?? prevailingPolicyVersion
1328 self.fetchPolicyDocumentWithSemaphore(version: policyVersion) { policyDoc, policyFetchError in
1329 guard let policyDoc = policyDoc, policyFetchError == nil else {
1330 os_log("Unable to fetch policy: %{public}@", log: tplogDebug, type: .default, (policyFetchError as CVarArg?) ?? "error missing")
1331 reply(nil, nil, nil, nil, nil, nil, nil, error ?? ContainerError.unknownInternalError)
1335 // Save the prepared identity as containerMO.egoPeer* and its bottle
1336 self.moc.performAndWait {
1339 let stableInfo = TPPeerStableInfo(clock: 1,
1340 frozenPolicyVersion: frozenPolicyVersion,
1341 flexiblePolicyVersion: policyDoc.version,
1342 policySecrets: policySecrets,
1343 deviceName: deviceName,
1344 serialNumber: serialNumber,
1345 osVersion: osVersion,
1346 signing: signingKeyPair,
1347 recoverySigningPubKey: nil,
1348 recoveryEncryptionPubKey: nil,
1351 self.containerMO.egoPeerID = permanentInfo.peerID
1352 self.containerMO.egoPeerPermanentInfo = permanentInfo.data
1353 self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig
1354 self.containerMO.egoPeerStableInfo = stableInfo.data
1355 self.containerMO.egoPeerStableInfoSig = stableInfo.sig
1357 let bottleMO = BottleMO(context: self.moc)
1358 bottleMO.peerID = bottle.peerID
1359 bottleMO.bottleID = bottle.bottleID
1360 bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI
1361 bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
1362 bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey
1363 bottleMO.contents = bottle.contents
1365 self.containerMO.addToBottles(bottleMO)
1367 let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: permanentInfo, stableInfo: stableInfo)
1371 reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, syncingViews, policy, nil)
1373 os_log("Unable to save identity: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1374 reply(nil, nil, nil, nil, nil, nil, nil, error)
1381 func getEgoEpoch(reply: @escaping (UInt64, Error?) -> Void) {
1382 let reply: (UInt64, Error?) -> Void = {
1383 os_log("getEgoEpoch complete: %d %{public}@", log: tplogTrace, type: .info, $0, traceError($1))
1387 self.moc.performAndWait {
1388 guard let egoPeerID = self.containerMO.egoPeerID else {
1389 reply(0, ContainerError.noPreparedIdentity)
1392 guard let egoPeer = self.model.peer(withID: egoPeerID) else {
1393 reply(0, ContainerError.noPreparedIdentity)
1397 reply(egoPeer.permanentInfo.epoch, nil)
1400 func establish(ckksKeys: [CKKSKeychainBackedKeySet],
1401 tlkShares: [CKKSTLKShare],
1402 preapprovedKeys: [Data]?,
1403 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
1404 self.semaphore.wait()
1405 let reply: (String?, [CKRecord], Error?) -> Void = {
1406 os_log("establish complete peer: %{public}@ %{public}@",
1407 log: tplogTrace, type: .default, ($0 ?? "NULL") as CVarArg, traceError($2))
1408 self.semaphore.signal()
1412 self.moc.performAndWait {
1413 self.onqueueEstablish(ckksKeys: ckksKeys,
1414 tlkShares: tlkShares,
1415 preapprovedKeys: preapprovedKeys,
1416 reply: { peerID, ckrecords, _, _, error in
1417 reply(peerID, ckrecords, error)
1422 func onqueueTTRUntrusted() {
1423 let ttr = SecTapToRadar(tapToRadar: "Device not IDMS trusted",
1424 description: "Device not IDMS trusted",
1429 func fetchAfterEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1430 tlkShares: [CKKSTLKShare],
1431 reply: @escaping (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void) {
1432 self.moc.performAndWait {
1434 try self.deleteLocalCloudKitData()
1436 os_log("fetchAfterEstablish failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1437 reply(nil, [], nil, nil, error)
1440 self.onqueueFetchAndPersistChanges { error in
1441 guard error == nil else {
1442 os_log("fetchAfterEstablish failed to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1443 reply(nil, [], nil, nil, error)
1447 self.moc.performAndWait {
1448 guard let egoPeerID = self.containerMO.egoPeerID,
1449 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1450 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1451 let egoStableData = self.containerMO.egoPeerStableInfo,
1452 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1454 os_log("fetchAfterEstablish: failed to fetch egoPeerID", log: tplogDebug, type: .default)
1455 reply(nil, [], nil, nil, ContainerError.noPreparedIdentity)
1458 guard self.model.hasPeer(withID: egoPeerID) else {
1459 os_log("fetchAfterEstablish: did not find peer %{public}@ in model", log: tplogDebug, type: .default, egoPeerID)
1460 reply(nil, [], nil, nil, ContainerError.invalidPeerID)
1463 let keyFactory = TPECPublicKeyFactory()
1464 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1465 reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig)
1468 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1469 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1470 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
1473 self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares) { ckrecords, error in
1474 guard error == nil else {
1475 os_log("fetchAfterEstablish failed to update TLKs: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1476 reply(nil, [], nil, nil, error)
1481 let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo,
1482 stableInfo: selfStableInfo)
1483 os_log("fetchAfterEstablish succeeded", log: tplogDebug, type: .default)
1484 reply(egoPeerID, ckrecords ?? [], syncingViews, policy, nil)
1486 os_log("fetchAfterEstablish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1487 reply(nil, [], nil, nil, error)
1495 func onqueueEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1496 tlkShares: [CKKSTLKShare],
1497 preapprovedKeys: [Data]?,
1498 reply: @escaping (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void) {
1499 // Fetch ego peer identity from local storage.
1500 guard let egoPeerID = self.containerMO.egoPeerID,
1501 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1502 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1503 let egoStableData = self.containerMO.egoPeerStableInfo,
1504 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1506 reply(nil, [], nil, nil, ContainerError.noPreparedIdentity)
1510 let keyFactory = TPECPublicKeyFactory()
1511 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1512 reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig)
1515 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1516 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1517 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
1520 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
1521 os_log("establish: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
1522 self.onqueueTTRUntrusted()
1523 reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
1527 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
1528 guard let egoPeerKeys = egoPeerKeys else {
1529 os_log("Don't have my own peer keys; can't establish: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1530 reply(nil, [], nil, nil, error)
1533 self.moc.performAndWait {
1534 let viewKeys: [ViewKeys] = ckksKeys.map(ViewKeys.convert)
1535 let allTLKShares: [TLKShare]
1537 let octagonShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk }, asPeer: egoPeerKeys, toPeer: egoPeerKeys, epoch: Int(selfPermanentInfo.epoch))
1538 let sosShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
1540 allTLKShares = octagonShares + sosShares
1542 os_log("Unable to make TLKShares for self: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1543 reply(nil, [], nil, nil, error)
1547 let dynamicInfo: TPPeerDynamicInfo
1549 dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
1550 peerPermanentInfo: selfPermanentInfo,
1551 peerStableInfo: selfStableInfo,
1553 preapprovedKeys: preapprovedKeys,
1554 signing: egoPeerKeys.signingKey,
1555 currentMachineIDs: self.onqueueCurrentMIDList())
1557 os_log("dynamic info: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
1559 reply(nil, [], nil, nil, error)
1563 let peer = Peer.with {
1564 $0.peerID = egoPeerID
1565 $0.permanentInfoAndSig.peerPermanentInfo = egoPermData
1566 $0.permanentInfoAndSig.sig = egoPermSig
1567 $0.stableInfoAndSig.peerStableInfo = egoStableData
1568 $0.stableInfoAndSig.sig = egoStableSig
1569 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
1574 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
1576 reply(nil, [], nil, nil, error)
1579 os_log("Beginning establish for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
1580 os_log("Establish permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
1581 os_log("Establish permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
1582 os_log("Establish stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
1583 os_log("Establish stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
1584 os_log("Establish dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
1585 os_log("Establish dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
1587 os_log("Establish introducing %d key sets, %d tlk shares", log: tplogDebug, type: .default, viewKeys.count, allTLKShares.count)
1590 os_log("Establish bottle: %{public}@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString())
1591 os_log("Establish peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
1593 os_log("Establish unable to encode bottle/peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
1596 let request = EstablishRequest.with {
1599 $0.viewKeys = viewKeys
1600 $0.tlkShares = allTLKShares
1602 self.cuttlefish.establish(request) { response, error in
1603 os_log("Establish(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1604 os_log("Establish: viewKeys: %{public}@", String(describing: viewKeys))
1605 guard let response = response, error == nil else {
1607 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.establishFailed):
1608 os_log("establish returned failed, trying fetch", log: tplogDebug, type: .default)
1609 self.fetchAfterEstablish(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply)
1612 os_log("establish failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1613 reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing)
1619 os_log("Establish returned changes: %{public}@", log: tplogDebug, type: .default, try response.changes.jsonString())
1621 os_log("Establish returned changes, but they can't be serialized", log: tplogDebug, type: .default)
1624 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
1627 let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo,
1628 stableInfo: selfStableInfo)
1630 try self.persist(changes: response.changes)
1632 guard response.changes.more == false else {
1633 os_log("establish succeeded, but more changes need fetching...", log: tplogDebug, type: .default)
1635 self.fetchAndPersistChanges { fetchError in
1636 guard fetchError == nil else {
1637 // This is an odd error condition: we might be able to fetch again and be in a good state...
1638 os_log("fetch-after-establish failed: %{public}@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error")
1639 reply(nil, keyHierarchyRecords, nil, nil, fetchError)
1643 os_log("fetch-after-establish succeeded", log: tplogDebug, type: .default)
1644 reply(egoPeerID, keyHierarchyRecords, nil, nil, nil)
1649 os_log("establish succeeded", log: tplogDebug, type: .default)
1650 reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil)
1652 os_log("establish handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1653 reply(nil, keyHierarchyRecords, nil, nil, error)
1660 func setRecoveryKey(recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping (Error?) -> Void) {
1661 self.semaphore.wait()
1662 let reply: (Error?) -> Void = {
1663 os_log("setRecoveryKey complete: %{public}@", log: tplogTrace, type: .info, traceError($0))
1664 self.semaphore.signal()
1668 os_log("beginning a setRecoveryKey", log: tplogDebug, type: .default)
1670 self.moc.performAndWait {
1671 guard let egoPeerID = self.containerMO.egoPeerID else {
1672 os_log("no prepared identity, cannot set recovery key", log: tplogDebug, type: .default)
1673 reply(ContainerError.noPreparedIdentity)
1677 var recoveryKeys: RecoveryKey
1679 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
1681 os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1682 reply(ContainerError.failedToCreateRecoveryKey)
1686 let signingPublicKey: Data = recoveryKeys.peerKeys.signingVerificationKey.keyData
1687 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionVerificationKey.keyData
1689 os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString())
1690 os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString())
1692 guard let stableInfoData = self.containerMO.egoPeerStableInfo else {
1693 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1694 reply(ContainerError.nonMember)
1697 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1698 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1699 reply(ContainerError.nonMember)
1702 guard let permInfoData = self.containerMO.egoPeerPermanentInfo else {
1703 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1704 reply(ContainerError.nonMember)
1707 guard let permInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1708 os_log("permInfoSig does not exist", log: tplogDebug, type: .default)
1709 reply(ContainerError.nonMember)
1712 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
1713 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1714 reply(ContainerError.invalidStableInfoOrSig)
1717 let keyFactory = TPECPublicKeyFactory()
1718 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permInfoData, sig: permInfoSig, keyFactory: keyFactory) else {
1719 os_log("cannot create TPPeerPermanentInfo", log: tplogDebug, type: .default)
1720 reply(ContainerError.invalidStableInfoOrSig)
1724 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
1725 guard let signingKeyPair = signingKeyPair else {
1726 os_log("handle: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1730 self.moc.performAndWait {
1732 let tlkShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
1733 asPeer: recoveryKeys.peerKeys,
1734 toPeer: recoveryKeys.peerKeys,
1735 epoch: Int(permanentInfo.epoch))
1737 let policyVersion = stableInfo.bestPolicyVersion()
1738 let policyDoc = try self.getPolicyDoc(policyVersion.versionNumber)
1740 let updatedStableInfo = TPPeerStableInfo(clock: stableInfo.clock + 1,
1741 frozenPolicyVersion: frozenPolicyVersion,
1742 flexiblePolicyVersion: policyDoc.version,
1743 policySecrets: stableInfo.policySecrets,
1744 deviceName: stableInfo.deviceName,
1745 serialNumber: stableInfo.serialNumber,
1746 osVersion: stableInfo.osVersion,
1747 signing: signingKeyPair,
1748 recoverySigningPubKey: signingPublicKey,
1749 recoveryEncryptionPubKey: encryptionPublicKey,
1751 let signedStableInfo = SignedPeerStableInfo(updatedStableInfo)
1753 let request = SetRecoveryKeyRequest.with {
1754 $0.peerID = egoPeerID
1755 $0.recoverySigningPubKey = signingPublicKey
1756 $0.recoveryEncryptionPubKey = encryptionPublicKey
1757 $0.stableInfoAndSig = signedStableInfo
1758 $0.tlkShares = tlkShares
1759 $0.changeToken = self.containerMO.changeToken ?? ""
1762 self.cuttlefish.setRecoveryKey(request) { response, error in
1763 os_log("SetRecoveryKey(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1764 guard let response = response, error == nil else {
1765 os_log("setRecoveryKey failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1766 reply(error ?? ContainerError.cloudkitResponseMissing)
1770 self.moc.performAndWait {
1772 self.containerMO.egoPeerStableInfo = updatedStableInfo.data
1773 self.containerMO.egoPeerStableInfoSig = updatedStableInfo.sig
1774 try self.onQueuePersist(changes: response.changes)
1776 os_log("setRecoveryKey succeeded", log: tplogDebug, type: .default)
1779 os_log("setRecoveryKey handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
1792 func currentSetContainerBottleID(bottleMOs: Set<BottleMO>, bottleID: String) -> (Bool) {
1793 let bmos = bottleMOs.filter {
1794 $0.bottleID == bottleID
1796 return !bmos.isEmpty
1799 func onqueueFindBottle(bottleID: String, reply: @escaping (BottleMO?, Error?) -> Void) {
1802 var bottles: Set<BottleMO> = []
1803 var shouldPerformFetch = false
1805 if let containerBottles = self.containerMO.bottles as? Set<BottleMO> {
1806 if self.currentSetContainerBottleID(bottleMOs: containerBottles, bottleID: bottleID) == false {
1807 shouldPerformFetch = true
1809 bottles = containerBottles
1812 shouldPerformFetch = true
1815 if shouldPerformFetch == true {
1816 self.fetchViableBottlesWithSemaphore { _, _, error in
1817 guard error == nil else {
1818 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1823 guard let newBottles = self.containerMO.bottles as? Set<BottleMO> else {
1824 os_log("no bottles on container: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1825 reply(nil, ContainerError.noBottlesPresent)
1829 guard self.currentSetContainerBottleID(bottleMOs: newBottles, bottleID: bottleID) == true else {
1830 reply(nil, ContainerError.noBottlesForEscrowRecordID)
1834 os_log("onqueueFindBottle found bottle: %{public}@", log: tplogDebug, type: .default, newBottles)
1836 bottles = newBottles.filter {
1837 $0.bottleID == bottleID
1839 if bottles.count > 1 {
1840 reply(nil, ContainerError.tooManyBottlesForPeer)
1843 bmo = bottles.removeFirst()
1847 var filteredBottles = bottles.filter {
1848 $0.bottleID == bottleID
1850 if filteredBottles.count > 1 {
1851 reply(nil, ContainerError.tooManyBottlesForPeer)
1854 bmo = filteredBottles.removeFirst()
1859 func onqueueRecoverBottle(managedBottle: BottleMO, entropy: Data, bottleSalt: String) throws -> BottledPeer {
1860 guard let bottledContents = managedBottle.contents else {
1861 throw ContainerError.bottleDoesNotContainContents
1863 guard let signatureUsingEscrowKey = managedBottle.signatureUsingEscrowKey else {
1864 throw ContainerError.bottleDoesNotContainEscrowKeySignature
1867 guard let signatureUsingPeerKey = managedBottle.signatureUsingPeerKey else {
1868 throw ContainerError.bottleDoesNotContainerPeerKeySignature
1870 guard let sponsorPeerID = managedBottle.peerID else {
1871 throw ContainerError.bottleDoesNotContainPeerID
1874 //verify bottle signature using peer
1876 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1877 os_log("recover bottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1878 throw ContainerError.bottleCreatingPeerNotFound
1880 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1881 os_log("recover bottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1882 throw ContainerError.signatureVerificationFailed
1885 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1887 os_log("Verification of bottled signature failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1888 throw ContainerError.failedToCreateBottledPeer
1892 return try BottledPeer(contents: bottledContents,
1894 bottleSalt: bottleSalt,
1895 signatureUsingEscrow: signatureUsingEscrowKey,
1896 signatureUsingPeerKey: signatureUsingPeerKey)
1898 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1901 return try BottledPeer(contents: bottledContents,
1904 signatureUsingEscrow: signatureUsingEscrowKey,
1905 signatureUsingPeerKey: signatureUsingPeerKey)
1907 os_log("Creation of Bottled Peer failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1908 throw ContainerError.failedToCreateBottledPeer
1913 func vouchWithBottle(bottleID: String,
1916 tlkShares: [CKKSTLKShare],
1917 reply: @escaping (Data?, Data?, Int64, Int64, Error?) -> Void) {
1918 self.semaphore.wait()
1919 let reply: (Data?, Data?, Int64, Int64, Error?) -> Void = {
1920 os_log("vouchWithBottle complete: %{public}@",
1921 log: tplogTrace, type: .info, traceError($4))
1922 self.semaphore.signal()
1923 reply($0, $1, $2, $3, $4)
1926 self.fetchAndPersistChangesIfNeeded { error in
1927 guard error == nil else {
1928 os_log("vouchWithBottle unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1929 reply(nil, nil, 0, 0, error)
1933 self.onqueueFindBottle(bottleID: bottleID) { returnedBMO, error in
1934 self.moc.performAndWait {
1935 guard error == nil else {
1936 os_log("vouchWithBottle unable to find bottle for escrow record id: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1937 reply(nil, nil, 0, 0, error)
1941 guard let bmo: BottleMO = returnedBMO else {
1942 os_log("vouchWithBottle bottle is nil: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1943 reply(nil, nil, 0, 0, error)
1947 guard let bottledContents = bmo.contents else {
1948 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainContents)
1951 guard let signatureUsingEscrowKey = bmo.signatureUsingEscrowKey else {
1952 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainEscrowKeySignature)
1956 guard let signatureUsingPeerKey = bmo.signatureUsingPeerKey else {
1957 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainerPeerKeySignature)
1960 guard let sponsorPeerID = bmo.peerID else {
1961 reply(nil, nil, 0, 0, ContainerError.bottleDoesNotContainPeerID)
1965 //verify bottle signature using peer
1967 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1968 os_log("vouchWithBottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1969 reply(nil, nil, 0, 0, ContainerError.bottleCreatingPeerNotFound)
1972 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1973 os_log("vouchWithBottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1974 reply(nil, nil, 0, 0, ContainerError.signatureVerificationFailed)
1978 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1980 os_log("vouchWithBottle: Verification of bottled signature failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
1981 reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer)
1985 //create bottled peer
1986 let bottledPeer: BottledPeer
1988 bottledPeer = try BottledPeer(contents: bottledContents,
1990 bottleSalt: bottleSalt,
1991 signatureUsingEscrow: signatureUsingEscrowKey,
1992 signatureUsingPeerKey: signatureUsingPeerKey)
1994 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1997 bottledPeer = try BottledPeer(contents: bottledContents,
2000 signatureUsingEscrow: signatureUsingEscrowKey,
2001 signatureUsingPeerKey: signatureUsingPeerKey)
2004 os_log("Creation of Bottled Peer failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2005 reply(nil, nil, 0, 0, ContainerError.failedToCreateBottledPeer)
2010 os_log("Have a bottle for peer %{public}@", log: tplogDebug, type: .default, bottledPeer.peerID)
2012 // Extract any TLKs we have been given
2013 let (uniqueTLKsRecovered, totalSharesRecovered) = extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys, model: self.model)
2015 self.moc.performAndWait {
2016 // I must have an ego identity in order to vouch using bottle
2017 guard let egoPeerID = self.containerMO.egoPeerID else {
2018 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2019 reply(nil, nil, 0, 0, ContainerError.nonMember)
2022 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
2023 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
2024 reply(nil, nil, 0, 0, ContainerError.nonMember)
2027 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
2028 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
2029 reply(nil, nil, 0, 0, ContainerError.nonMember)
2032 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
2033 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
2034 reply(nil, nil, 0, 0, ContainerError.nonMember)
2037 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2038 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
2039 reply(nil, nil, 0, 0, ContainerError.nonMember)
2042 let keyFactory = TPECPublicKeyFactory()
2043 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2044 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2045 reply(nil, nil, 0, 0, ContainerError.invalidPermanentInfoOrSig)
2048 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2049 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2050 reply(nil, nil, 0, 0, ContainerError.invalidStableInfoOrSig)
2055 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2056 stableInfo: beneficiaryStableInfo,
2057 withSponsorID: sponsorPeerID,
2058 reason: TPVoucherReason.restore,
2059 signing: bottledPeer.peerKeys.signingKey)
2060 reply(voucher.data, voucher.sig, uniqueTLKsRecovered, totalSharesRecovered, nil)
2063 os_log("Error creating voucher with bottle: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2064 reply(nil, nil, 0, 0, error)
2073 func vouchWithRecoveryKey(recoveryKey: String,
2075 tlkShares: [CKKSTLKShare],
2076 reply: @escaping (Data?, Data?, Error?) -> Void) {
2077 self.semaphore.wait()
2078 let reply: (Data?, Data?, Error?) -> Void = {
2079 os_log("vouchWithRecoveryKey complete: %{public}@",
2080 log: tplogTrace, type: .info, traceError($2))
2081 self.semaphore.signal()
2085 self.moc.performAndWait {
2086 os_log("beginning a vouchWithRecoveryKey", log: tplogDebug, type: .default)
2088 // I must have an ego identity in order to vouch using bottle
2089 guard let egoPeerID = self.containerMO.egoPeerID else {
2090 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2091 reply(nil, nil, ContainerError.nonMember)
2094 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
2095 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
2096 reply(nil, nil, ContainerError.nonMember)
2099 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
2100 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
2101 reply(nil, nil, ContainerError.nonMember)
2104 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
2105 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
2106 reply(nil, nil, ContainerError.nonMember)
2109 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2110 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
2111 reply(nil, nil, ContainerError.nonMember)
2114 let keyFactory = TPECPublicKeyFactory()
2115 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2116 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2117 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2120 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2121 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2122 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2126 //create recovery key set
2127 var recoveryKeys: RecoveryKey
2129 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
2131 os_log("failed to create recovery keys: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2132 reply(nil, nil, ContainerError.failedToCreateRecoveryKey)
2136 extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys, model: self.model)
2138 let signingPublicKey: Data = recoveryKeys.peerKeys.signingKey.publicKey.keyData
2139 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionKey.publicKey.keyData
2141 os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .default, signingPublicKey.base64EncodedString())
2142 os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .default, encryptionPublicKey.base64EncodedString())
2144 guard self.model.isRecoveryKeyEnrolled() else {
2145 os_log("Recovery Key is not enrolled", log: tplogDebug, type: .default)
2146 reply(nil, nil, ContainerError.recoveryKeysNotEnrolled)
2150 //find matching peer containing recovery keys
2151 guard let sponsorPeerID = self.model.peerIDThatTrustsRecoveryKeys(TPRecoveryKeyPair(signingSPKI: signingPublicKey, encryptionSPKI: encryptionPublicKey)) else {
2152 os_log("Untrusted recovery key set", log: tplogDebug, type: .default)
2153 reply(nil, nil, ContainerError.untrustedRecoveryKeys)
2158 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2159 stableInfo: beneficiaryStableInfo,
2160 withSponsorID: sponsorPeerID,
2161 reason: TPVoucherReason.recoveryKey,
2162 signing: recoveryKeys.peerKeys.signingKey)
2163 reply(voucher.data, voucher.sig, nil)
2166 os_log("Error creating voucher using recovery key set: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2167 reply(nil, nil, error)
2173 func vouch(peerID: String,
2174 permanentInfo: Data,
2175 permanentInfoSig: Data,
2177 stableInfoSig: Data,
2178 ckksKeys: [CKKSKeychainBackedKeySet],
2179 reply: @escaping (Data?, Data?, Error?) -> Void) {
2180 self.semaphore.wait()
2181 let reply: (Data?, Data?, Error?) -> Void = {
2182 os_log("vouch complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2183 self.semaphore.signal()
2187 self.moc.performAndWait {
2188 // I must have an ego identity in order to vouch for someone else.
2189 guard let egoPeerID = self.containerMO.egoPeerID,
2190 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2191 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else {
2192 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2193 reply(nil, nil, ContainerError.nonMember)
2197 let keyFactory = TPECPublicKeyFactory()
2199 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2200 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2204 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: peerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2205 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2206 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2210 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2211 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2212 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2216 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2217 guard let egoPeerKeys = egoPeerKeys else {
2218 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")
2219 reply(nil, nil, error)
2223 self.fetchPolicyDocumentsWithSemaphore(versions: Set([beneficiaryStableInfo.bestPolicyVersion()])) { _, policyFetchError in
2224 guard policyFetchError == nil else {
2225 os_log("Unknown policy for beneficiary: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2226 reply(nil, nil, policyFetchError)
2230 self.moc.performAndWait {
2231 let voucher: TPVoucher
2233 voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2234 stableInfo: beneficiaryStableInfo,
2235 withSponsorID: egoPeerID,
2236 reason: TPVoucherReason.secureChannel,
2237 signing: egoPeerKeys.signingKey)
2239 os_log("Error creating voucher: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2240 reply(nil, nil, error)
2244 // And generate and upload any tlkShares
2245 let tlkShares: [TLKShare]
2247 // Note that this might not be the whole list, so filter some of them out
2248 let peerViews = try? self.model.getViewsForPeer(beneficiaryPermanentInfo,
2249 stableInfo: beneficiaryStableInfo)
2251 // Note: we only want to send up TLKs for uploaded ckks zones
2252 let ckksTLKs = ckksKeys
2253 .filter { !$0.newUpload }
2254 .filter { peerViews?.contains($0.tlk.zoneID.zoneName) ?? false }
2257 tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs,
2258 asPeer: egoPeerKeys,
2259 toPeer: beneficiaryPermanentInfo,
2260 epoch: Int(selfPermanentInfo.epoch))
2262 os_log("Unable to make TLKShares for beneficiary %{public}@(%{public}@): %{public}@", log: tplogDebug, type: .default, peerID, beneficiaryPermanentInfo, error as CVarArg)
2263 reply(nil, nil, error)
2267 guard !tlkShares.isEmpty else {
2268 os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default)
2269 reply(voucher.data, voucher.sig, nil)
2273 self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "",
2275 stableInfoAndSig: nil,
2276 dynamicInfoAndSig: nil,
2277 tlkShares: tlkShares,
2278 viewKeys: []) { response, error in
2279 guard let response = response, error == nil else {
2280 os_log("Unable to upload new tlkshares: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
2281 reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing)
2285 let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init)
2286 os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords)
2287 // We don't need to save these; CKKS will refetch them as needed
2289 reply(voucher.data, voucher.sig, nil)
2297 func departByDistrustingSelf(reply: @escaping (Error?) -> Void) {
2298 self.semaphore.wait()
2299 let reply: (Error?) -> Void = {
2300 os_log("departByDistrustingSelf complete: %{public}@", log: tplogTrace, type: .info, traceError($0))
2301 self.semaphore.signal()
2305 self.moc.performAndWait {
2306 guard let egoPeerID = self.containerMO.egoPeerID else {
2307 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2308 reply(ContainerError.noPreparedIdentity)
2312 self.onqueueDistrust(peerIDs: [egoPeerID], reply: reply)
2316 func distrust(peerIDs: Set<String>,
2317 reply: @escaping (Error?) -> Void) {
2318 self.semaphore.wait()
2319 let reply: (Error?) -> Void = {
2320 os_log("distrust complete: %{public}@", log: tplogTrace, type: .info, traceError($0))
2321 self.semaphore.signal()
2325 self.moc.performAndWait {
2326 guard let egoPeerID = self.containerMO.egoPeerID else {
2327 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2328 reply(ContainerError.noPreparedIdentity)
2332 guard !peerIDs.contains(egoPeerID) else {
2333 os_log("Self-distrust via peerID not allowed", log: tplogDebug, type: .default)
2334 reply(ContainerError.invalidPeerID)
2338 self.onqueueDistrust(peerIDs: peerIDs, reply: reply)
2342 func onqueueDistrust(peerIDs: Set<String>,
2343 reply: @escaping (Error?) -> Void) {
2345 guard let egoPeerID = self.containerMO.egoPeerID else {
2346 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2347 reply(ContainerError.noPreparedIdentity)
2351 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
2352 guard let signingKeyPair = signingKeyPair else {
2353 os_log("No longer have signing key pair; can't sign distrust: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2358 self.moc.performAndWait {
2359 let dynamicInfo: TPPeerDynamicInfo
2361 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
2363 removingPeerIDs: Array(peerIDs),
2364 preapprovedKeys: nil,
2365 signing: signingKeyPair,
2366 currentMachineIDs: self.onqueueCurrentMIDList())
2369 os_log("Error preparing dynamic info: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2374 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
2375 os_log("attempting distrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, peerIDs, dynamicInfo)
2377 let request = UpdateTrustRequest.with {
2378 $0.changeToken = self.containerMO.changeToken ?? ""
2379 $0.peerID = egoPeerID
2380 $0.dynamicInfoAndSig = signedDynamicInfo
2382 self.cuttlefish.updateTrust(request) { response, error in
2383 os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2384 guard let response = response, error == nil else {
2385 os_log("updateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2386 reply(error ?? ContainerError.cloudkitResponseMissing)
2391 try self.persist(changes: response.changes)
2392 os_log("distrust succeeded", log: tplogDebug, type: .default)
2395 os_log("distrust handling failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg))
2403 func fetchEscrowContents(reply: @escaping (Data?, String?, Data?, Error?) -> Void) {
2404 self.semaphore.wait()
2405 let reply: (Data?, String?, Data?, Error?) -> Void = {
2406 os_log("fetchEscrowContents complete: %{public}@", log: tplogTrace, type: .info, traceError($3))
2407 self.semaphore.signal()
2408 reply($0, $1, $2, $3)
2410 os_log("beginning a fetchEscrowContents", log: tplogDebug, type: .default)
2412 self.moc.performAndWait {
2413 guard let egoPeerID = self.containerMO.egoPeerID else {
2414 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2415 reply(nil, nil, nil, ContainerError.noPreparedIdentity)
2419 guard let bottles = self.containerMO.bottles as? Set<BottleMO> else {
2420 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2421 reply(nil, nil, nil, ContainerError.noBottleForPeer)
2425 var bmoSet = bottles.filter { $0.peerID == egoPeerID }
2426 let bmo = bmoSet.removeFirst()
2427 let bottleID = bmo.bottleID
2431 guard let loaded = try loadSecret(label: egoPeerID) else {
2432 os_log("fetchEscrowContents failed to load entropy", log: tplogDebug, type: .default)
2433 reply(nil, nil, nil, ContainerError.failedToFetchEscrowContents)
2438 os_log("fetchEscrowContents failed to load entropy: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2439 reply(nil, nil, nil, error)
2443 guard let signingPublicKey = bmo.escrowedSigningSPKI else {
2444 os_log("fetchEscrowContents no escrow signing spki", log: tplogDebug, type: .default)
2445 reply(nil, nil, nil, ContainerError.bottleDoesNotContainerEscrowKeySPKI)
2448 reply(entropy, bottleID, signingPublicKey, nil)
2452 func fetchViableBottles(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2453 self.semaphore.wait()
2454 let reply: ([String]?, [String]?, Error?) -> Void = {
2455 os_log("fetchViableBottles complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2456 self.semaphore.signal()
2460 self.fetchViableBottlesWithSemaphore(reply: reply)
2463 func onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: TPCachedViableBottles) -> Bool {
2464 guard let egoPeerID = self.containerMO.egoPeerID else {
2465 os_log("bottleForEgoPeer: No identity.", log: tplogDebug, type: .default)
2468 guard let bottles: Set<BottleMO> = self.containerMO.bottles as? Set<BottleMO> else {
2469 os_log("bottleForEgoPeer: No Bottles.", log: tplogDebug, type: .default)
2472 var matchesCached: Bool = false
2473 for bottle in bottles {
2474 guard let bottleID: String = bottle.bottleID else {
2477 if bottle.peerID == egoPeerID && (cachedBottles.viableBottles.contains(bottleID) || cachedBottles.partialBottles.contains(bottleID)) {
2478 matchesCached = true
2482 return matchesCached
2485 func fetchViableBottlesWithSemaphore(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2486 os_log("beginning a fetchViableBottles", log: tplogDebug, type: .default)
2488 let cachedBottles: TPCachedViableBottles = self.model.currentCachedViableBottlesSet()
2489 self.moc.performAndWait {
2490 if self.onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: cachedBottles)
2491 && (!cachedBottles.viableBottles.isEmpty || !cachedBottles.partialBottles.isEmpty) {
2492 os_log("returning from fetchViableBottles, using cached bottles", log: tplogDebug, type: .default)
2493 reply(cachedBottles.viableBottles, cachedBottles.partialBottles, nil)
2497 self.cuttlefish.fetchViableBottles { response, error in
2498 guard error == nil else {
2499 os_log("fetchViableBottles failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2500 reply(nil, nil, error)
2504 self.moc.performAndWait {
2506 guard let escrowPairs = response?.viableBottles else {
2507 os_log("fetchViableBottles returned no viable bottles", log: tplogDebug, type: .default)
2512 var partialPairs: [EscrowPair] = []
2513 if let partial = response?.partialBottles {
2514 partialPairs = partial
2516 os_log("fetchViableBottles returned no partially viable bottles, but that's ok", log: tplogDebug, type: .default)
2519 let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID }
2520 os_log("fetchViableBottles returned viable bottles: %{public}@", log: tplogDebug, type: .default, viableBottleIDs)
2522 let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID }
2523 os_log("fetchViableBottles returned partial bottles: %{public}@", log: tplogDebug, type: .default, partialBottleIDs)
2525 escrowPairs.forEach { pair in
2526 let bottle = pair.bottle
2528 // Save this bottle only if we don't already have it
2529 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2530 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2531 existing.peerID == bottle.peerID &&
2532 existing.bottleID == bottle.bottleID &&
2533 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2534 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2535 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2536 existing.contents == bottle.contents
2538 if !matchingBottles.isEmpty {
2539 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2544 let bmo = BottleMO(context: self.moc)
2545 bmo.peerID = bottle.peerID
2546 bmo.bottleID = bottle.bottleID
2547 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2548 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2549 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2550 bmo.contents = bottle.contents
2552 os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo)
2553 self.containerMO.addToBottles(bmo)
2556 partialPairs.forEach { pair in
2557 let bottle = pair.bottle
2559 // Save this bottle only if we don't already have it
2560 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2561 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2562 existing.peerID == bottle.peerID &&
2563 existing.bottleID == bottle.bottleID &&
2564 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2565 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2566 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2567 existing.contents == bottle.contents
2569 if !matchingBottles.isEmpty {
2570 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2575 let bmo = BottleMO(context: self.moc)
2576 bmo.peerID = bottle.peerID
2577 bmo.bottleID = bottle.bottleID
2578 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2579 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2580 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2581 bmo.contents = bottle.contents
2583 os_log("fetchViableBottles saving new bottle: %{public}@", log: tplogDebug, type: .default, bmo)
2584 self.containerMO.addToBottles(bmo)
2589 os_log("fetchViableBottles saved bottles", log: tplogDebug, type: .default)
2590 let cached = TPCachedViableBottles(viableBottles: viableBottleIDs, partialBottles: partialBottleIDs)
2591 self.model.setViableBottles(cached)
2592 reply(viableBottleIDs, partialBottleIDs, nil)
2594 os_log("fetchViableBottles unable to save bottles: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2595 reply(nil, nil, error)
2603 func fetchCurrentPolicy(reply: @escaping (Set<String>?, TPPolicy?, Error?) -> Void) {
2604 self.semaphore.wait()
2605 let reply: (Set<String>?, TPPolicy?, Error?) -> Void = {
2606 os_log("fetchCurrentPolicy complete: %{public}@", log: tplogTrace, type: .info, traceError($2))
2607 self.semaphore.signal()
2611 self.moc.performAndWait {
2612 guard let egoPeerID = self.containerMO.egoPeerID,
2613 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2614 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2615 let stableInfoData = self.containerMO.egoPeerStableInfo,
2616 let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2617 os_log("fetchCurrentPolicy failed to find ego peer information", log: tplogDebug, type: .error)
2618 reply(nil, nil, ContainerError.noPreparedIdentity)
2622 let keyFactory = TPECPublicKeyFactory()
2623 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2624 os_log("fetchCurrentPolicy failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
2625 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2628 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
2629 os_log("fetchCurrentPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
2630 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2635 let (views, policy) = try self.policyAndViewsFor(permanentInfo: permanentInfo, stableInfo: stableInfo)
2636 reply(views, policy, nil)
2639 os_log("TPPolicyDocument failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2640 reply(nil, nil, error)
2646 func policyAndViewsFor(permanentInfo: TPPeerPermanentInfo, stableInfo: TPPeerStableInfo) throws -> (Set<String>, TPPolicy) {
2647 let policyVersion = stableInfo.bestPolicyVersion()
2648 guard let policyDocument = self.model.policy(withVersion: policyVersion.versionNumber) else {
2649 os_log("current policy is missing?", log: tplogDebug, type: .default)
2650 throw ContainerError.unknownPolicyVersion(policyVersion.versionNumber)
2653 let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2654 let views = try policy.views(forModel: permanentInfo.modelID)
2656 return (views, policy)
2659 // All-or-nothing: return an error in case full list cannot be returned.
2660 // Completion handler data format: [version : [hash, data]]
2661 func fetchPolicyDocuments(versions: Set<TPPolicyVersion>,
2662 reply: @escaping ([TPPolicyVersion: Data]?, Error?) -> Void) {
2663 self.semaphore.wait()
2664 let reply: ([TPPolicyVersion: Data]?, Error?) -> Void = {
2665 os_log("fetchPolicyDocuments complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
2666 self.semaphore.signal()
2670 self.fetchPolicyDocumentsWithSemaphore(versions: versions) { policyDocuments, fetchError in
2671 reply(policyDocuments.flatMap { $0.mapValues { policyDoc in policyDoc.protobuf } }, fetchError)
2675 func fetchPolicyDocumentWithSemaphore(version: TPPolicyVersion,
2676 reply: @escaping (TPPolicyDocument?, Error?) -> Void) {
2677 self.fetchPolicyDocumentsWithSemaphore(versions: Set([version])) { versions, fetchError in
2678 guard fetchError == nil else {
2679 reply(nil, fetchError)
2683 guard let doc = versions?[version] else {
2684 os_log("fetchPolicyDocument: didn't return policy of version: %{public}@", log: tplogDebug, versions ?? "no versions")
2685 reply(nil, ContainerError.unknownPolicyVersion(version.versionNumber))
2693 func fetchPolicyDocumentsWithSemaphore(versions: Set<TPPolicyVersion>,
2694 reply: @escaping ([TPPolicyVersion: TPPolicyDocument]?, Error?) -> Void) {
2695 var remaining = versions
2696 var docs: [TPPolicyVersion: TPPolicyDocument] = [:]
2698 self.moc.performAndWait {
2699 for version in remaining {
2700 if let policydoc = try? self.getPolicyDoc(version.versionNumber), policydoc.version.policyHash == version.policyHash {
2701 docs[policydoc.version] = policydoc
2702 remaining.remove(version)
2707 guard !remaining.isEmpty else {
2712 let request = FetchPolicyDocumentsRequest.with {
2713 $0.keys = remaining.map { version in
2714 PolicyDocumentKey.with { $0.version = version.versionNumber; $0.hash = version.policyHash }}
2717 self.cuttlefish.fetchPolicyDocuments(request) { response, error in
2718 os_log("FetchPolicyDocuments(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2719 guard let response = response, error == nil else {
2720 os_log("FetchPolicyDocuments failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2721 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
2725 self.moc.performAndWait {
2726 for mapEntry in response.entries {
2727 // TODO: validate the policy's signature
2729 guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else {
2730 os_log("Can't make policy document with hash %{public}@ and data %{public}@",
2731 log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString())
2732 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2736 guard let expectedVersion = (remaining.filter { $0.versionNumber == doc.version.versionNumber }.first) else {
2737 os_log("Received a policy version we didn't request: %d", log: tplogDebug, type: .default, doc.version.versionNumber)
2738 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2742 guard expectedVersion.policyHash == doc.version.policyHash else {
2743 os_log("Requested hash %{public}@ does not match fetched hash %{public}@", log: tplogDebug, type: .default,
2744 expectedVersion.policyHash, doc.version.policyHash)
2745 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2749 remaining.remove(expectedVersion) // Server responses should be unique, let's enforce
2751 docs[doc.version] = doc
2752 self.model.register(doc)
2756 try self.moc.save() // if this fails callers might make bad data assumptions
2762 // Determine if there's anything left to fetch
2763 guard let unfetchedVersion = remaining.first else {
2764 // Nothing remaining? Success!
2769 reply(nil, ContainerError.unknownPolicyVersion(unfetchedVersion.versionNumber))
2774 // Must be on moc queue to call this.
2775 // Caller is responsible for saving the moc afterwards.
2777 private func registerPeerMO(permanentInfo: TPPeerPermanentInfo,
2778 stableInfo: TPPeerStableInfo? = nil,
2779 dynamicInfo: TPPeerDynamicInfo? = nil,
2780 vouchers: [TPVoucher]? = nil,
2781 isEgoPeer: Bool = false) throws -> PeerMO {
2782 let peerID = permanentInfo.peerID
2784 let peer = PeerMO(context: self.moc)
2785 peer.peerID = peerID
2786 peer.permanentInfo = permanentInfo.data
2787 peer.permanentInfoSig = permanentInfo.sig
2788 peer.stableInfo = stableInfo?.data
2789 peer.stableInfoSig = stableInfo?.sig
2790 peer.dynamicInfo = dynamicInfo?.data
2791 peer.dynamicInfoSig = dynamicInfo?.sig
2792 peer.isEgoPeer = isEgoPeer
2793 self.containerMO.addToPeers(peer)
2795 self.model.registerPeer(with: permanentInfo)
2796 if let stableInfo = stableInfo {
2797 try self.model.update(stableInfo, forPeerWithID: peerID)
2799 if let dynamicInfo = dynamicInfo {
2800 try self.model.update(dynamicInfo, forPeerWithID: peerID)
2802 vouchers?.forEach { voucher in
2803 self.model.register(voucher)
2804 let voucherMO = VoucherMO(context: self.moc)
2805 voucherMO.voucherInfo = voucher.data
2806 voucherMO.voucherInfoSig = voucher.sig
2807 peer.addToVouchers(voucherMO)
2812 /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */
2813 func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet],
2814 tlkShares: [CKKSTLKShare],
2815 egoPeerKeys: OctagonSelfPeerKeys,
2816 egoPeerDynamicInfo: TPPeerDynamicInfo,
2817 epoch: Int) throws -> ([ViewKeys], [TLKShare]) {
2818 let newCKKSKeys = ckksKeys.filter { $0.newUpload }
2819 let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert)
2821 let octagonSelfShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
2822 asPeer: egoPeerKeys,
2823 toPeer: egoPeerKeys,
2825 let extraShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
2827 var peerShares: [TLKShare] = []
2829 for keyset in newCKKSKeys {
2831 let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo,
2832 toAccessView: keyset.tlk.zoneID.zoneName)
2833 os_log("Planning to share %@ with peers %{public}@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess)
2835 let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) }
2836 let viewPeerShares = try peers.map { receivingPeer in
2837 TLKShare.convert(ckksTLKShare: try CKKSTLKShare(keyset.tlk,
2839 to: receivingPeer.permanentInfo,
2844 peerShares += viewPeerShares
2847 os_log("Unable to create TLKShares for keyset %@: %{public}@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg)
2851 return (newViewKeys, octagonSelfShares + peerShares + extraShares)
2854 func onqueuePreparePeerForJoining(egoPeerID: String,
2855 peerPermanentInfo: TPPeerPermanentInfo,
2856 stableInfo: TPPeerStableInfo,
2858 preapprovedKeys: [Data]?,
2859 vouchers: [SignedVoucher],
2860 egoPeerKeys: OctagonSelfPeerKeys) throws -> (Peer, TPPeerDynamicInfo) {
2861 let dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
2862 peerPermanentInfo: peerPermanentInfo,
2863 peerStableInfo: stableInfo,
2864 sponsorID: sponsorID,
2865 preapprovedKeys: preapprovedKeys,
2866 signing: egoPeerKeys.signingKey,
2867 currentMachineIDs: self.onqueueCurrentMIDList())
2869 let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: nil,
2870 egoPeerID: egoPeerID,
2871 existingStableInfo: stableInfo,
2872 dynamicInfo: dynamicInfo,
2873 signingKeyPair: egoPeerKeys.signingKey)
2875 let peer = Peer.with {
2876 $0.peerID = egoPeerID
2877 $0.permanentInfoAndSig = SignedPeerPermanentInfo(peerPermanentInfo)
2878 $0.stableInfoAndSig = SignedPeerStableInfo(newStableInfo ?? stableInfo)
2879 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
2880 $0.vouchers = vouchers
2883 return (peer, dynamicInfo)
2886 func join(voucherData: Data,
2888 ckksKeys: [CKKSKeychainBackedKeySet],
2889 tlkShares: [CKKSTLKShare],
2890 preapprovedKeys: [Data]?,
2891 reply: @escaping (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void) {
2892 self.semaphore.wait()
2893 let reply: (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void = {
2894 os_log("join complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
2895 self.semaphore.signal()
2896 reply($0, $1, $2, $3, $4)
2899 self.fetchAndPersistChanges { error in
2900 guard error == nil else {
2901 reply(nil, [], nil, nil, error)
2905 // To join, you must know all policies that exist
2906 let allPolicyVersions = self.model.allPolicyVersions()
2907 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
2908 if let error = policyFetchError {
2909 os_log("join: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2912 self.moc.performAndWait {
2913 guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else {
2914 reply(nil, [], nil, nil, ContainerError.invalidVoucherOrSig)
2917 guard let sponsor = self.model.peer(withID: voucher.sponsorID) else {
2918 reply(nil, [], nil, nil, ContainerError.sponsorNotRegistered(voucher.sponsorID))
2922 // Fetch ego peer identity from local storage.
2923 guard let egoPeerID = self.containerMO.egoPeerID,
2924 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2925 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2926 let egoStableData = self.containerMO.egoPeerStableInfo,
2927 let egoStableSig = self.containerMO.egoPeerStableInfoSig
2929 reply(nil, [], nil, nil, ContainerError.noPreparedIdentity)
2933 let keyFactory = TPECPublicKeyFactory()
2934 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2935 reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig)
2938 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
2939 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
2942 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
2943 os_log("join: self machineID %{public}@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
2944 self.onqueueTTRUntrusted()
2945 reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
2949 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2950 guard let egoPeerKeys = egoPeerKeys else {
2951 os_log("Don't have my own peer keys; can't join: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
2952 reply(nil, [], nil, nil, error)
2955 self.moc.performAndWait {
2957 let newDynamicInfo: TPPeerDynamicInfo
2959 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
2960 peerPermanentInfo: selfPermanentInfo,
2961 stableInfo: selfStableInfo,
2962 sponsorID: sponsor.peerID,
2963 preapprovedKeys: preapprovedKeys,
2964 vouchers: [SignedVoucher.with {
2965 $0.voucher = voucher.data
2966 $0.sig = voucher.sig
2968 egoPeerKeys: egoPeerKeys)
2970 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2971 reply(nil, [], nil, nil, error)
2975 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
2976 os_log("Unable to create new peer stable new for joining", log: tplogDebug, type: .default)
2977 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
2981 let allTLKShares: [TLKShare]
2982 let viewKeys: [ViewKeys]
2984 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
2985 tlkShares: tlkShares,
2986 egoPeerKeys: egoPeerKeys,
2987 egoPeerDynamicInfo: newDynamicInfo,
2988 epoch: Int(selfPermanentInfo.epoch))
2990 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
2991 reply(nil, [], nil, nil, error)
2996 try self.model.checkIntroduction(forCandidate: selfPermanentInfo,
2997 stableInfo: peer.stableInfoAndSig.toStableInfo(),
2998 withSponsorID: sponsor.peerID)
3000 os_log("Error checking introduction: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3001 reply(nil, [], nil, nil, error)
3007 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3009 reply(nil, [], nil, nil, error)
3013 os_log("Beginning join for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3014 os_log("Join permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3015 os_log("Join permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3016 os_log("Join stableInfo: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString())
3017 os_log("Join stableInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString())
3018 os_log("Join dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3019 os_log("Join dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3021 os_log("Join vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3022 os_log("Join voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3024 os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3027 os_log("Join peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3029 os_log("Join unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3032 let changeToken = self.containerMO.changeToken ?? ""
3033 let request = JoinWithVoucherRequest.with {
3034 $0.changeToken = changeToken
3037 $0.tlkShares = allTLKShares
3038 $0.viewKeys = viewKeys
3040 self.cuttlefish.joinWithVoucher(request) { response, error in
3041 os_log("JoinWithVoucher(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3042 guard let response = response, error == nil else {
3043 os_log("joinWithVoucher failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3044 reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing)
3048 self.moc.performAndWait {
3050 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3051 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3053 let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo,
3054 stableInfo: peerStableInfo)
3056 try self.onQueuePersist(changes: response.changes)
3057 os_log("JoinWithVoucher succeeded", log: tplogDebug)
3059 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3060 reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil)
3062 os_log("JoinWithVoucher failed: %{public}@", log: tplogDebug, String(describing: error))
3063 reply(nil, [], nil, nil, error)
3074 func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Bool, Error?) -> Void) {
3075 self.semaphore.wait()
3076 let reply: (Bool, Bool, Bool, Bool, Error?) -> Void = {
3077 os_log("health check complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
3078 self.semaphore.signal()
3079 reply($0, $1, $2, $3, $4)
3082 os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck)
3084 self.moc.performAndWait {
3085 guard let egoPeerID = self.containerMO.egoPeerID else {
3086 // No identity, nothing to do
3087 os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default)
3088 reply(false, false, false, false, ContainerError.noPreparedIdentity)
3091 let request = GetRepairActionRequest.with {
3092 $0.peerID = egoPeerID
3093 $0.requiresEscrowCheck = requiresEscrowCheck
3096 self.cuttlefish.getRepairAction(request) { response, error in
3097 guard error == nil else {
3098 reply(false, false, false, false, error)
3101 guard let action = response?.repairAction else {
3102 os_log("repair response is empty, returning false", log: tplogDebug, type: .default)
3103 reply(false, false, false, false, nil)
3106 var postRepairAccount: Bool = false
3107 var postRepairEscrow: Bool = false
3108 var resetOctagon: Bool = false
3109 var leaveTrust: Bool = false
3114 case .postRepairAccount:
3115 postRepairAccount = true
3116 case .postRepairEscrow:
3117 postRepairEscrow = true
3125 reply(postRepairAccount, postRepairEscrow, resetOctagon, leaveTrust, nil)
3130 func getSupportAppInfo(reply: @escaping (Data?, Error?) -> Void) {
3131 self.semaphore.wait()
3132 let reply: (Data?, Error?) -> Void = {
3133 os_log("getSupportAppInfo complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3134 self.semaphore.signal()
3138 self.cuttlefish.getSupportAppInfo { response, error in
3139 os_log("getSupportAppInfo(): %{public}@, error: %{public}@", log: tplogDebug,
3140 "(\(String(describing: response))", "\(String(describing: error))")
3141 guard let response = response, error == nil else {
3142 os_log("getSupportAppInfo failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3143 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3147 guard let data = try? response.serializedData() else {
3148 reply(nil, ContainerError.failedToSerializeData)
3157 func preflightPreapprovedJoin(reply: @escaping (Bool, Error?) -> Void) {
3158 self.semaphore.wait()
3159 let reply: (Bool, Error?) -> Void = {
3160 os_log("preflightPreapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3161 self.semaphore.signal()
3165 self.fetchAndPersistChanges { error in
3166 guard error == nil else {
3167 os_log("preflightPreapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3172 // We need to try to have all policy versions that our peers claim to behave
3173 let allPolicyVersions = self.model.allPolicyVersions()
3174 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3175 if let error = policyFetchError {
3176 os_log("preflightPreapprovedJoin: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3179 // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us?
3181 guard !self.model.allPeerIDs().isEmpty else {
3182 // If, after fetch and handle changes, there's no peers, then we can likely establish.
3187 guard let egoPeerID = self.containerMO.egoPeerID,
3188 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3189 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3191 os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3192 reply(false, ContainerError.noPreparedIdentity)
3196 let keyFactory = TPECPublicKeyFactory()
3197 guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3198 os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug)
3199 reply(false, ContainerError.invalidPermanentInfoOrSig)
3203 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else {
3204 os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3205 reply(false, ContainerError.noPeersPreapprovePreparedIdentity)
3214 func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet],
3215 tlkShares: [CKKSTLKShare],
3216 preapprovedKeys: [Data]?,
3217 reply: @escaping (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void) {
3218 self.semaphore.wait()
3219 let reply: (String?, [CKRecord], Set<String>?, TPPolicy?, Error?) -> Void = {
3220 os_log("preapprovedJoin complete: %{public}@", log: tplogTrace, type: .info, traceError($4))
3221 self.semaphore.signal()
3222 reply($0, $1, $2, $3, $4)
3225 self.fetchAndPersistChangesIfNeeded { error in
3226 guard error == nil else {
3227 os_log("preapprovedJoin unable to fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3228 reply(nil, [], nil, nil, error)
3231 self.moc.performAndWait {
3232 // If, after fetch and handle changes, there's no peers, then fire off an establish
3233 // Note that if the establish fails, retrying this call might work.
3234 // That's up to the caller.
3235 if self.model.allPeerIDs().isEmpty {
3236 os_log("preapprovedJoin but no existing peers, attempting establish", log: tplogDebug, type: .debug)
3237 self.onqueueEstablish(ckksKeys: ckksKeys,
3238 tlkShares: tlkShares,
3239 preapprovedKeys: preapprovedKeys,
3244 // Fetch ego peer identity from local storage.
3245 guard let egoPeerID = self.containerMO.egoPeerID,
3246 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3247 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3248 let egoStableData = self.containerMO.egoPeerStableInfo,
3249 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3251 os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3252 reply(nil, [], nil, nil, ContainerError.noPreparedIdentity)
3256 let keyFactory = TPECPublicKeyFactory()
3257 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3258 reply(nil, [], nil, nil, ContainerError.invalidPermanentInfoOrSig)
3261 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3262 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
3266 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3267 os_log("preapprovedJoin: self machineID %{public}@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3268 self.onqueueTTRUntrusted()
3269 reply(nil, [], nil, nil, ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3272 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3273 guard let egoPeerKeys = egoPeerKeys else {
3274 os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3275 reply(nil, [], nil, nil, error)
3279 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else {
3280 os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3281 reply(nil, [], nil, nil, ContainerError.noPeersPreapprovePreparedIdentity)
3285 self.moc.performAndWait {
3288 let newDynamicInfo: TPPeerDynamicInfo
3290 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3291 peerPermanentInfo: selfPermanentInfo,
3292 stableInfo: selfStableInfo,
3294 preapprovedKeys: preapprovedKeys,
3296 egoPeerKeys: egoPeerKeys)
3298 os_log("Unable to create peer for joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3299 reply(nil, [], nil, nil, error)
3303 guard let peerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3304 os_log("Unable to create new peer stable new for joining", log: tplogDebug, type: .default)
3305 reply(nil, [], nil, nil, ContainerError.invalidStableInfoOrSig)
3309 let allTLKShares: [TLKShare]
3310 let viewKeys: [ViewKeys]
3312 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3313 tlkShares: tlkShares,
3314 egoPeerKeys: egoPeerKeys,
3315 egoPeerDynamicInfo: newDynamicInfo,
3316 epoch: Int(selfPermanentInfo.epoch))
3318 os_log("Unable to process keys before joining: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3319 reply(nil, [], nil, nil, error)
3325 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3327 reply(nil, [], nil, nil, error)
3331 os_log("Beginning preapprovedJoin for peer %{public}@", log: tplogDebug, type: .default, egoPeerID)
3332 os_log("preapprovedJoin permanentInfo: %{public}@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3333 os_log("preapprovedJoin permanentInfoSig: %{public}@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3334 os_log("preapprovedJoin stableInfo: %{public}@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
3335 os_log("preapprovedJoin stableInfoSig: %{public}@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
3336 os_log("preapprovedJoin dynamicInfo: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3337 os_log("preapprovedJoin dynamicInfoSig: %{public}@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3339 os_log("preapprovedJoin vouchers: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3340 os_log("preapprovedJoin voucher signatures: %{public}@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3342 os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3345 os_log("preapprovedJoin peer: %{public}@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3347 os_log("preapprovedJoin unable to encode peer: %{public}@", log: tplogDebug, type: .debug, error as CVarArg)
3350 let changeToken = self.containerMO.changeToken ?? ""
3351 let request = JoinWithVoucherRequest.with {
3352 $0.changeToken = changeToken
3355 $0.tlkShares = allTLKShares
3356 $0.viewKeys = viewKeys
3358 self.cuttlefish.joinWithVoucher(request) { response, error in
3359 os_log("preapprovedJoin(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3360 guard let response = response, error == nil else {
3361 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3362 reply(nil, [], nil, nil, error ?? ContainerError.cloudkitResponseMissing)
3366 self.moc.performAndWait {
3368 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3369 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3371 let (syncingViews, policy) = try self.policyAndViewsFor(permanentInfo: selfPermanentInfo,
3372 stableInfo: peerStableInfo)
3374 try self.onQueuePersist(changes: response.changes)
3375 os_log("preapprovedJoin succeeded", log: tplogDebug)
3377 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3378 reply(egoPeerID, keyHierarchyRecords, syncingViews, policy, nil)
3380 os_log("preapprovedJoin failed: %{public}@", log: tplogDebug, String(describing: error))
3381 reply(nil, [], nil, nil, error)
3391 func update(deviceName: String?,
3392 serialNumber: String?,
3394 policyVersion: UInt64?,
3395 policySecrets: [String: Data]?,
3396 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3397 self.semaphore.wait()
3398 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3399 os_log("update complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3400 self.semaphore.signal()
3404 // Get (and save) the latest from cuttlefish
3405 let stableChanges = StableChanges(deviceName: deviceName,
3406 serialNumber: serialNumber,
3407 osVersion: osVersion,
3408 policyVersion: policyVersion,
3409 policySecrets: policySecrets)
3410 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply)
3413 func set(preapprovedKeys: [Data],
3414 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3415 self.semaphore.wait()
3416 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3417 os_log("setPreapprovedKeys complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3418 self.semaphore.signal()
3422 self.moc.performAndWait {
3423 os_log("setPreapprovedKeys: %@", log: tplogDebug, type: .default, preapprovedKeys)
3425 guard let egoPeerID = self.containerMO.egoPeerID else {
3426 // No identity, nothing to do
3427 os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default)
3428 reply(nil, ContainerError.noPreparedIdentity)
3431 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3432 guard let signingKeyPair = signingKeyPair else {
3433 os_log("setPreapprovedKeys: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3434 reply(nil, error ?? ContainerError.unableToCreateKeyPair)
3438 self.moc.performAndWait {
3439 let dynamicInfo: TPPeerDynamicInfo
3441 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3443 removingPeerIDs: nil,
3444 preapprovedKeys: preapprovedKeys,
3445 signing: signingKeyPair,
3446 currentMachineIDs: self.onqueueCurrentMIDList())
3448 os_log("setPreapprovedKeys: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3453 os_log("setPreapprovedKeys: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
3455 if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo {
3456 os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default)
3458 // Calling this will fill in the peer status
3459 self.updateTrustIfNeeded(reply: reply)
3463 os_log("setPreapprovedKeys: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3464 let request = UpdateTrustRequest.with {
3465 $0.changeToken = self.containerMO.changeToken ?? ""
3466 $0.peerID = egoPeerID
3467 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3470 self.perform(updateTrust: request) { state, error in
3471 guard error == nil else {
3472 os_log("setPreapprovedKeys: failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
3477 os_log("setPreapprovedKeys: updateTrust succeeded", log: tplogDebug, type: .default)
3485 func updateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3486 tlkShares: [CKKSTLKShare],
3487 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3488 self.semaphore.wait()
3489 let reply: ([CKRecord]?, Error?) -> Void = {
3490 os_log("updateTLKs complete: %{public}@", log: tplogTrace, type: .info, traceError($1))
3491 self.semaphore.signal()
3495 os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys)
3497 self.moc.performAndWait {
3498 self.onqueueUpdateTLKs(ckksKeys: ckksKeys, tlkShares: tlkShares, reply: reply)
3502 func onqueueUpdateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3503 tlkShares: [CKKSTLKShare],
3504 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3505 guard let egoPeerID = self.containerMO.egoPeerID,
3506 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3507 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3509 os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default)
3510 reply(nil, ContainerError.noPreparedIdentity)
3514 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else {
3515 os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys)
3516 reply(nil, ContainerError.invalidPermanentInfoOrSig)
3520 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3521 guard let egoPeerKeys = egoPeerKeys else {
3522 os_log("Don't have my own peer keys; can't upload new TLKs: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3526 self.moc.performAndWait {
3527 guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else {
3528 os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default)
3529 reply(nil, ContainerError.missingDynamicInfo)
3533 let allTLKShares: [TLKShare]
3534 let viewKeys: [ViewKeys]
3536 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3537 tlkShares: tlkShares,
3538 egoPeerKeys: egoPeerKeys,
3539 egoPeerDynamicInfo: egoPeerDynamicInfo,
3540 epoch: Int(selfPermanentInfo.epoch))
3542 os_log("Unable to process keys before uploading: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3547 let request = UpdateTrustRequest.with {
3548 $0.changeToken = self.containerMO.changeToken ?? ""
3549 $0.peerID = egoPeerID
3550 $0.tlkShares = allTLKShares
3551 $0.viewKeys = viewKeys
3554 self.cuttlefish.updateTrust(request) { response, error in
3555 os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3557 guard error == nil else {
3562 let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? []
3563 os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count)
3564 reply(keyHierarchyRecords, nil)
3570 func getState(reply: @escaping (ContainerState) -> Void) {
3571 self.semaphore.wait()
3572 let reply: (ContainerState) -> Void = {
3573 os_log("getState complete: %{public}@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
3574 self.semaphore.signal()
3578 self.moc.performAndWait {
3579 var state = ContainerState()
3580 state.egoPeerID = self.containerMO.egoPeerID
3582 if self.containerMO.bottles != nil {
3583 self.containerMO.bottles!.forEach { bottle in
3584 state.bottles.insert(bottle as! BottleMO)
3588 self.model.allPeers().forEach { peer in
3589 state.peers[peer.peerID] = peer
3591 state.vouchers = Array(self.model.allVouchers())
3596 // This will only fetch changes if no changes have ever been fetched before
3597 func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) {
3598 self.moc.performAndWait {
3599 if self.containerMO.changeToken == nil {
3600 self.onqueueFetchAndPersistChanges(reply: reply)
3607 private func fetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3608 self.moc.performAndWait {
3609 self.onqueueFetchAndPersistChanges(reply: reply)
3613 private func onqueueFetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3614 let request = FetchChangesRequest.with {
3615 $0.changeToken = self.containerMO.changeToken ?? ""
3617 os_log("Fetching with change token: %{public}@", log: tplogDebug, type: .default, !request.changeToken.isEmpty ? request.changeToken : "empty")
3619 self.cuttlefish.fetchChanges(request) { response, error in
3620 os_log("FetchChanges(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3621 guard let response = response, error == nil else {
3623 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired):
3624 os_log("change token is expired; resetting local CK storage", log: tplogDebug, type: .default)
3626 self.moc.performAndWait {
3628 try self.deleteLocalCloudKitData()
3630 os_log("Failed to reset local data: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3635 self.fetchAndPersistChanges(reply: reply)
3640 os_log("Fetch error is an unknown error: %{public}@", log: tplogDebug, type: .default, String(describing: error))
3643 os_log("Could not fetch changes: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3649 try self.persist(changes: response.changes)
3651 os_log("Could not persist changes: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3656 if response.changes.more {
3657 os_log("persist: More changes indicated. Fetching...", log: tplogDebug, type: .default)
3658 self.fetchAndPersistChanges(reply: reply)
3661 os_log("persist: no more changes!", log: tplogDebug, type: .default)
3668 // Check for delta update in trust lists, that should lead to update of TLKShares
3670 private func haveTrustMemberChanges(newDynamicInfo: TPPeerDynamicInfo, oldDynamicInfo: TPPeerDynamicInfo?) -> Bool {
3671 guard let oldDynamicInfo = oldDynamicInfo else {
3674 if newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs {
3677 if newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs {
3680 if newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals {
3686 // Fetch and persist changes from Cuttlefish. If this results
3687 // in us calculating a new dynamicInfo then give the new dynamicInfo
3688 // to Cuttlefish. If Cuttlefish returns more changes then persist
3689 // them locally, update dynamicInfo if needed, and keep doing that
3690 // until dynamicInfo is unchanged and there are no more changes to fetch.
3692 // Must be holding the semaphore to call this, and it remains
3693 // the caller's responsibility to release it after it completes
3694 // (i.e. after reply is invoked).
3695 internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil,
3696 peerChanges: Bool = false,
3697 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3698 self.fetchAndPersistChanges { error in
3699 if let error = error {
3700 os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3705 self.updateTrustIfNeeded(stableChanges: stableChanges, peerChanges: peerChanges, reply: reply)
3709 // If this results in us calculating a new dynamicInfo then,
3710 // upload the new dynamicInfo
3711 // to Cuttlefish. If Cuttlefish returns more changes then persist
3712 // them locally, update dynamicInfo if needed, and keep doing that
3713 // until dynamicInfo is unchanged and there are no more changes to fetch.
3715 // Must be holding the semaphore to call this, and it remains
3716 // the caller's responsibility to release it after it completes
3717 // (i.e. after reply is invoked).
3718 private func updateTrustIfNeeded(stableChanges: StableChanges? = nil,
3719 peerChanges: Bool = false,
3720 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3721 self.moc.performAndWait {
3722 guard let egoPeerID = self.containerMO.egoPeerID else {
3723 // No identity, nothing to do
3724 os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default)
3725 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil), nil)
3728 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3729 guard let signingKeyPair = signingKeyPair else {
3730 os_log("updateTrustIfNeeded: no signing key pair: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3731 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: peerChanges, unknownMachineIDs: false, osVersion: nil), error)
3734 guard let currentSelfInModel = self.model.peer(withID: egoPeerID) else {
3735 // Not in circle, nothing to do
3736 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(signingKeyPair.publicKey().spki())
3737 os_log("updateTrustIfNeeded: ego peer is not in model, is %{public}@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
3738 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3739 isPreapproved: isPreapproved,
3741 memberChanges: peerChanges,
3742 unknownMachineIDs: false,
3748 // We need to try to have all policy versions that our peers claim to behave
3749 let allPolicyVersions = self.model.allPolicyVersions()
3750 self.fetchPolicyDocumentsWithSemaphore(versions: allPolicyVersions) { _, policyFetchError in
3751 if let error = policyFetchError {
3752 os_log("updateTrustIfNeeded: error fetching all requested policies (continuing anyway): %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3755 self.moc.performAndWait {
3756 let dynamicInfo: TPPeerDynamicInfo
3757 var stableInfo: TPPeerStableInfo?
3760 // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair,
3761 // and then only load the key if it has changed and we need to sign a new one. This would also
3762 // help make our detection of change immune from non-canonical serialization of dynamicInfo.
3763 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3765 removingPeerIDs: nil,
3766 preapprovedKeys: nil,
3767 signing: signingKeyPair,
3768 currentMachineIDs: self.onqueueCurrentMIDList())
3770 stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges,
3771 egoPeerID: egoPeerID,
3772 existingStableInfo: currentSelfInModel.stableInfo,
3773 dynamicInfo: dynamicInfo,
3774 signingKeyPair: signingKeyPair)
3776 os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3777 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3778 isPreapproved: false,
3779 status: self.model.statusOfPeer(withID: egoPeerID),
3780 memberChanges: peerChanges,
3781 unknownMachineIDs: false,
3787 os_log("updateTrustIfNeeded: produced a stableInfo: %{public}@", log: tplogDebug, type: .default, String(describing: stableInfo))
3788 os_log("updateTrustIfNeeded: produced a dynamicInfo: %{public}@", log: tplogDebug, type: .default, dynamicInfo)
3790 let peer = self.model.peer(withID: egoPeerID)
3791 if (stableInfo == nil || stableInfo == peer?.stableInfo) &&
3792 dynamicInfo == peer?.dynamicInfo {
3793 os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default)
3794 // No change to the dynamicInfo: update the MID list now that we've reached a steady state
3796 self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo)
3799 os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3802 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3803 isPreapproved: false,
3804 status: self.model.statusOfPeer(withID: egoPeerID),
3805 memberChanges: peerChanges,
3806 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
3807 osVersion: peer?.stableInfo?.osVersion),
3811 // Check if we change that should trigger a notification that should trigger TLKShare updates
3812 let havePeerChanges = peerChanges || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo)
3814 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
3815 os_log("updateTrustIfNeeded: attempting updateTrust for %{public}@ with: %{public}@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3816 var request = UpdateTrustRequest.with {
3817 $0.changeToken = self.containerMO.changeToken ?? ""
3818 $0.peerID = egoPeerID
3819 $0.dynamicInfoAndSig = signedDynamicInfo
3821 if let stableInfo = stableInfo {
3822 request.stableInfoAndSig = SignedPeerStableInfo(stableInfo)
3825 self.perform(updateTrust: request, stableChanges: stableChanges, peerChanges: havePeerChanges, reply: reply)
3832 private func perform(updateTrust request: UpdateTrustRequest,
3833 stableChanges: StableChanges? = nil,
3834 peerChanges: Bool = false,
3835 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3837 self.cuttlefish.updateTrust(request) { response, error in
3838 os_log("UpdateTrust(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3839 guard let response = response, error == nil else {
3840 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3841 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3846 try self.persist(changes: response.changes)
3848 os_log("UpdateTrust failed: %{public}@", log: tplogDebug, String(describing: error))
3853 if response.changes.more {
3854 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges,
3855 peerChanges: peerChanges,
3858 self.updateTrustIfNeeded(stableChanges: stableChanges,
3859 peerChanges: peerChanges,
3865 private func persist(changes: Changes) throws {
3866 // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait.
3867 // So, do it ourself
3868 var outsideBlockError: Error?
3870 self.moc.performAndWait {
3871 os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default,
3872 changes.differences.count,
3874 os_log("persist: New change token: %{public}@", log: tplogDebug, type: .default, changes.changeToken)
3877 try self.onQueuePersist(changes: changes)
3879 outsideBlockError = error
3883 if let outsideBlockError = outsideBlockError {
3884 throw outsideBlockError
3888 // Must be on moc queue to call this.
3889 // Changes are registered in the model and stored in moc.
3890 private func onQueuePersist(changes: Changes) throws {
3891 self.containerMO.changeToken = changes.changeToken
3892 self.containerMO.moreChanges = changes.more
3894 if !changes.differences.isEmpty {
3895 self.model.clearViableBottles()
3898 try changes.differences.forEach { peerDifference in
3899 if let operation = peerDifference.operation {
3901 case .add(let peer):
3902 try self.addOrUpdate(peer: peer)
3904 case .update(let peer):
3905 try self.addOrUpdate(peer: peer)
3906 // Update containerMO ego data if it has changed.
3907 if peer.peerID == self.containerMO.egoPeerID {
3908 guard let stableInfoAndSig: TPPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3911 self.containerMO.egoPeerStableInfo = stableInfoAndSig.data
3912 self.containerMO.egoPeerStableInfoSig = stableInfoAndSig.sig
3915 case .remove(let peer):
3916 self.model.deletePeer(withID: peer.peerID)
3917 if let peerMO = try self.fetchPeerMO(peerID: peer.peerID) {
3918 self.moc.delete(peerMO)
3924 let signingKey = changes.recoverySigningPubKey
3925 let encryptionKey = changes.recoveryEncryptionPubKey
3927 if !signingKey.isEmpty && !encryptionKey.isEmpty {
3928 self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey)
3933 // Must be on moc queue to call this
3934 // Deletes all things that should come back from CloudKit. Keeps the egoPeer data, though.
3935 private func deleteLocalCloudKitData() throws {
3936 os_log("Deleting all CloudKit data", log: tplogDebug, type: .default)
3939 let peerRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
3940 peerRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3941 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: peerRequest))
3943 let bottleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Bottle")
3944 bottleRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3945 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: bottleRequest))
3947 self.containerMO.peers = nil
3948 self.containerMO.bottles = nil
3949 self.containerMO.changeToken = nil
3950 self.containerMO.moreChanges = false
3952 self.model = Container.loadModel(from: self.containerMO)
3955 os_log("Local delete failed: %{public}@", log: tplogDebug, type: .default, error as CVarArg)
3959 os_log("Saved model with %d peers", log: tplogDebug, type: .default, self.model.allPeerIDs().count)
3962 // Must be on moc queue to call this.
3963 private func addOrUpdate(signingKey: Data, encryptionKey: Data) {
3964 self.model.setRecoveryKeys(
3965 TPRecoveryKeyPair(signingSPKI: signingKey, encryptionSPKI: encryptionKey))
3967 self.containerMO.recoveryKeySigningSPKI = signingKey
3968 self.containerMO.recoveryKeyEncryptionSPKI = encryptionKey
3971 // Must be on moc queue to call this.
3972 private func addOrUpdate(peer: Peer) throws {
3973 if !self.model.hasPeer(withID: peer.peerID) {
3975 guard let permanentInfo = peer.permanentInfoAndSig.toPermanentInfo(peerID: peer.peerID) else {
3976 // Ignoring bad peer
3979 let stableInfo = peer.stableInfoAndSig.toStableInfo()
3980 let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo()
3981 let vouchers = peer.vouchers.compactMap {
3982 TPVoucher(infoWith: $0.voucher, sig: $0.sig)
3984 let isEgoPeer = peer.peerID == self.containerMO.egoPeerID
3985 try self.registerPeerMO(permanentInfo: permanentInfo,
3986 stableInfo: stableInfo,
3987 dynamicInfo: dynamicInfo,
3989 isEgoPeer: isEgoPeer)
3992 // The assertion here is that every peer registered in model is also present in containerMO
3993 let peerMO = try self.fetchPeerMO(peerID: peer.peerID)!
3995 if let stableInfo = peer.stableInfoAndSig.toStableInfo() {
3996 try self.model.update(stableInfo, forPeerWithID: peer.peerID)
3997 // Pull the stableInfo back out of the model, and persist that.
3998 // The model checks signatures and clocks to prevent replay attacks.
3999 let modelPeer = self.model.peer(withID: peer.peerID)
4000 peerMO.stableInfo = modelPeer?.stableInfo?.data
4001 peerMO.stableInfoSig = modelPeer?.stableInfo?.sig
4003 if let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo() {
4004 try self.model.update(dynamicInfo, forPeerWithID: peer.peerID)
4005 // Pull the dynamicInfo back out of the model, and persist that.
4006 // The model checks signatures and clocks to prevent replay attacks.
4007 let modelPeer = self.model.peer(withID: peer.peerID)
4008 peerMO.dynamicInfo = modelPeer?.dynamicInfo?.data
4009 peerMO.dynamicInfoSig = modelPeer?.dynamicInfo?.sig
4011 peer.vouchers.forEach {
4012 if let voucher = TPVoucher(infoWith: $0.voucher, sig: $0.sig) {
4013 self.model.register(voucher)
4014 let voucherMO = VoucherMO(context: self.moc)
4015 voucherMO.voucherInfo = voucher.data
4016 voucherMO.voucherInfoSig = voucher.sig
4017 peerMO.addToVouchers(voucherMO)
4023 // Must be on moc queue to call this.
4024 private func fetchPeerMO(peerID: String) throws -> PeerMO? {
4025 let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
4026 fetch.predicate = NSPredicate(format: "peerID == %@ && container == %@", peerID, self.containerMO)
4027 let peers = try self.moc.fetch(fetch)
4028 return peers.first as? PeerMO
4031 // Must be on moc queue to call this.
4032 private func getPolicyDoc(_ policyVersion: UInt64) throws -> TPPolicyDocument {
4033 guard let policyDoc = self.model.policy(withVersion: policyVersion) else {
4034 throw ContainerError.unknownPolicyVersion(policyVersion)
4036 assert(policyVersion == policyDoc.version.versionNumber)
4037 if policyVersion == prevailingPolicyVersion.versionNumber {
4038 assert(policyDoc.version.policyHash == prevailingPolicyVersion.policyHash)
4043 // Must be on moc queue to call this.
4044 private func createNewStableInfoIfNeeded(stableChanges: StableChanges?,
4046 existingStableInfo: TPPeerStableInfo?,
4047 dynamicInfo: TPPeerDynamicInfo,
4048 signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? {
4049 func noChange<T: Equatable>(_ change: T?, _ existing: T?) -> Bool {
4050 return (nil == change) || change == existing
4053 let policyOfPeers = try? self.model.policy(forPeerIDs: dynamicInfo.includedPeerIDs,
4054 candidatePeerID: egoPeerID,
4055 candidateStableInfo: existingStableInfo)
4057 // Pick the best version of:
4058 // 1. The policy version asked for by the client
4059 // 2. The max of our existing policyVersion, the highest policy used by our trusted peers, and the compile-time prevailing policy version
4060 let optimalPolicyVersionNumber = stableChanges?.policyVersion ??
4061 max(existingStableInfo?.bestPolicyVersion().versionNumber ?? prevailingPolicyVersion.versionNumber,
4062 policyOfPeers?.version.versionNumber ?? prevailingPolicyVersion.versionNumber,
4063 prevailingPolicyVersion.versionNumber)
4065 // Determine which recovery key we'd like to be using, given our current idea of who to trust
4066 let optimalRecoveryKey = self.model.bestRecoveryKey(for: existingStableInfo, dynamicInfo: dynamicInfo)
4068 if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) &&
4069 noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) &&
4070 noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) &&
4071 noChange(optimalPolicyVersionNumber, existingStableInfo?.bestPolicyVersion().versionNumber) &&
4072 noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) &&
4073 noChange(optimalRecoveryKey?.signingSPKI, existingStableInfo?.recoverySigningPublicKey) &&
4074 noChange(optimalRecoveryKey?.encryptionSPKI, existingStableInfo?.recoveryEncryptionPublicKey) {
4078 let optimalPolicyVersion = try self.getPolicyDoc(optimalPolicyVersionNumber).version
4080 return try self.model.createStableInfo(withFrozenPolicyVersion: frozenPolicyVersion,
4081 flexiblePolicyVersion: optimalPolicyVersion,
4082 policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets,
4083 deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "",
4084 serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "",
4085 osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "",
4086 signing: signingKeyPair,
4087 recoverySigningPubKey: optimalRecoveryKey?.signingSPKI,
4088 recoveryEncryptionPubKey: optimalRecoveryKey?.encryptionSPKI)
4091 private func assembleBottle(egoPeerID: String) throws -> Bottle {
4092 let bottleMoSet = containerMO.bottles as? Set<BottleMO>
4094 var bottleMOs = bottleMoSet?.filter {
4095 $0.peerID == egoPeerID
4098 if let count = bottleMOs?.count {
4100 throw ContainerError.tooManyBottlesForPeer
4101 } else if count == 0 {
4102 throw ContainerError.noBottleForPeer
4105 throw ContainerError.failedToAssembleBottle
4108 let BM: BottleMO? = (bottleMOs?.removeFirst())
4110 let bottle = try Bottle.with {
4111 $0.peerID = egoPeerID
4113 if let bottledPeerData = BM?.contents {
4114 $0.contents = bottledPeerData
4116 throw ContainerError.failedToAssembleBottle
4119 if let bottledPeerEscrowSigningSPKI = BM?.escrowedSigningSPKI {
4120 $0.escrowedSigningSpki = bottledPeerEscrowSigningSPKI
4122 throw ContainerError.failedToAssembleBottle
4125 if let bottledPeerSignatureUsingEscrowKey = BM?.signatureUsingEscrowKey {
4126 $0.signatureUsingEscrowKey = bottledPeerSignatureUsingEscrowKey
4128 throw ContainerError.failedToAssembleBottle
4131 if let bottledPeerSignatureUsingPeerKey = BM?.signatureUsingPeerKey {
4132 $0.signatureUsingPeerKey = bottledPeerSignatureUsingPeerKey
4134 throw ContainerError.failedToAssembleBottle
4137 if let bID = BM?.bottleID {
4140 throw ContainerError.failedToAssembleBottle
4147 func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) {
4148 self.semaphore.wait()
4149 let reply: (Error?) -> Void = {
4150 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4151 self.semaphore.signal()
4155 var updatedRequest = request
4157 if let egoPeerID = self.containerMO.egoPeerID {
4158 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, _ in
4159 updatedRequest.octagonEgoIdentity = (egoPeerKeys != nil)
4163 self.moc.performAndWait {
4164 self.cuttlefish.reportHealth(updatedRequest) { response, error in
4165 os_log("reportHealth(%{public}@): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
4166 guard error == nil else {
4175 func pushHealthInquiry(reply: @escaping (Error?) -> Void) {
4176 self.semaphore.wait()
4177 let reply: (Error?) -> Void = {
4178 os_log("reportHealth complete %{public}@", log: tplogTrace, type: .info, traceError($0))
4179 self.semaphore.signal()
4183 self.moc.performAndWait {
4184 self.cuttlefish.pushHealthInquiry(PushHealthInquiryRequest()) { response, error in
4185 os_log("pushHealthInquiry(): %{public}@, error: %{public}@", log: tplogDebug, "\(String(describing: response))", "\(String(describing: error))")
4186 guard error == nil else {