2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
29 import SecurityFoundation
31 let tplogDebug = OSLog(subsystem: "com.apple.security.trustedpeers", category: "debug")
32 let tplogTrace = OSLog(subsystem: "com.apple.security.trustedpeers", category: "trace")
34 let egoIdentitiesAccessGroup = "com.apple.security.egoIdentities"
36 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
103 extension ContainerError: LocalizedError {
104 public var errorDescription: String? {
106 case .unableToCreateKeyPair:
107 return "unable to create key pair"
108 case .noPreparedIdentity:
109 return "no prepared identity"
110 case .failedToStoreIdentity:
111 return "failed to stored identity"
112 case .needsAuthentication:
113 return "needs authentication"
114 case .missingStableInfo:
115 return "missing stable info"
116 case .missingDynamicInfo:
117 return "missing dynamic info"
120 case .invalidPermanentInfoOrSig:
121 return "invalid permanent info or signature"
122 case .invalidStableInfoOrSig:
123 return "invalid stable info or signature"
124 case .invalidVoucherOrSig:
125 return "invalid voucher or signature"
126 case .sponsorNotRegistered(let s):
127 return "sponsor not registered: \(s)"
128 case .unknownPolicyVersion(let v):
129 return "unknown policy version: \(v)"
130 case .preparedIdentityNotOnAllowedList(let id):
131 return "prepared identity (\(id)) not on allowed machineID list"
132 case .couldNotLoadAllowedList:
133 return "could not load allowed machineID list"
134 case .noPeersPreapprovePreparedIdentity:
135 return "no peers preapprove prepared identity"
136 case .policyDocumentDoesNotValidate:
137 return "policy document from server doesn't validate"
138 case .tooManyBottlesForPeer:
139 return "too many bottles exist for peer"
140 case .noBottleForPeer:
141 return "no bottle exists for peer"
142 case .restoreBottleFailed:
143 return "failed to restore bottle"
144 case .noBottlesForEscrowRecordID:
145 return "0 bottles exist for escrow record id"
146 case .bottleDoesNotContainContents:
147 return "bottle does not contain encrypted contents"
148 case .bottleDoesNotContainEscrowKeySignature:
149 return "bottle does not contain escrow signature"
150 case .bottleDoesNotContainerPeerKeySignature:
151 return "bottle does not contain peer signature"
152 case .bottleDoesNotContainPeerID:
153 return "bottle does not contain peer id"
154 case .failedToCreateBottledPeer:
155 return "failed to create a bottled peer"
156 case .signatureVerificationFailed:
157 return "failed to verify signature"
158 case .bottleDoesNotContainerEscrowKeySPKI:
159 return "bottle does not contain escrowed key spki"
160 case .failedToFetchEscrowContents:
161 return "failed to fetch escrow contents"
162 case .failedToCreateRecoveryKey:
163 return "failed to create recovery keys"
164 case .untrustedRecoveryKeys:
165 return "untrusted recovery keys"
166 case .noBottlesPresent:
167 return "no bottle present"
168 case .recoveryKeysNotEnrolled:
169 return "recovery key is not enrolled with octagon"
170 case .bottleCreatingPeerNotFound:
171 return "The peer that created the bottle was not found"
172 case .unknownCloudKitError:
173 return "unknown error from cloudkit"
174 case .cloudkitResponseMissing:
175 return "Response missing from CloudKit"
176 case .failedToLoadSecret(errorCode: let errorCode):
177 return "failed to load secret: \(errorCode)"
178 case .failedToLoadSecretDueToType:
179 return "Failed to load secret due to type mismatch (value was not dictionary)"
180 case .failedToAssembleBottle:
181 return "failed to assemble bottle for peer"
183 return "peerID is invalid"
184 case .failedToStoreSecret(errorCode: let errorCode):
185 return "failed to store the secret in the keychain \(errorCode)"
186 case .unknownSecurityFoundationError:
187 return "SecurityFoundation returned an unknown type"
192 extension ContainerError: CustomNSError {
194 public static var errorDomain: String {
195 return "com.apple.security.trustedpeers.container"
198 public var errorCode: Int {
200 case .unableToCreateKeyPair:
202 case .noPreparedIdentity:
204 case .failedToStoreIdentity:
206 case .needsAuthentication:
208 case .missingStableInfo:
210 case .missingDynamicInfo:
214 case .invalidPermanentInfoOrSig:
216 case .invalidVoucherOrSig:
218 case .invalidStableInfoOrSig:
220 case .sponsorNotRegistered:
222 case .unknownPolicyVersion:
224 case .preparedIdentityNotOnAllowedList:
226 case .noPeersPreapprovePreparedIdentity:
228 case .policyDocumentDoesNotValidate:
230 // 16 was invalidPeerID and failedToAssembleBottle
231 case .tooManyBottlesForPeer:
233 case .noBottleForPeer:
235 case .restoreBottleFailed:
237 case .noBottlesForEscrowRecordID:
239 case .bottleDoesNotContainContents:
241 case .bottleDoesNotContainEscrowKeySignature:
243 case .bottleDoesNotContainerPeerKeySignature:
245 case .bottleDoesNotContainPeerID:
247 case .failedToCreateBottledPeer:
249 case .signatureVerificationFailed:
251 // 27 was failedToLoadSecret*
252 case .bottleDoesNotContainerEscrowKeySPKI:
254 case .failedToFetchEscrowContents:
256 case .couldNotLoadAllowedList:
258 case .failedToCreateRecoveryKey:
260 case .untrustedRecoveryKeys:
262 case .noBottlesPresent:
264 case .recoveryKeysNotEnrolled:
266 case .bottleCreatingPeerNotFound:
268 case .unknownCloudKitError:
270 case .cloudkitResponseMissing:
272 case .failedToLoadSecret:
274 case .failedToLoadSecretDueToType:
276 case .failedToStoreSecret:
278 case .failedToAssembleBottle:
282 case .unknownSecurityFoundationError:
287 public var underlyingError: NSError? {
289 case .failedToLoadSecret(errorCode: let errorCode):
290 return NSError(domain: "securityd", code: errorCode)
291 case .failedToStoreSecret(errorCode: let errorCode):
292 return NSError(domain: "securityd", code: errorCode)
298 public var errorUserInfo: [String: Any] {
299 var ret = [String: Any]()
300 if let desc = self.errorDescription {
301 ret[NSLocalizedDescriptionKey] = desc
303 if let underlyingError = self.underlyingError {
304 ret[NSUnderlyingErrorKey] = underlyingError
310 internal func traceError(_ error: Error?) -> String {
311 if let error = error {
312 return "error: \(String(describing: error))"
318 func saveSecret(_ secret: Data, label: String) throws {
320 let query: [CFString: Any] = [
321 kSecClass: kSecClassInternetPassword,
322 kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
323 kSecUseDataProtectionKeychain: true,
324 kSecAttrAccessGroup: "com.apple.security.octagon",
325 kSecAttrSynchronizable: false,
326 kSecAttrDescription: label,
328 kSecValueData: secret,
331 var results: CFTypeRef?
332 var status = SecItemAdd(query as CFDictionary, &results)
334 if status == errSecSuccess {
338 if status == errSecDuplicateItem {
339 // Add every primary key attribute to this find dictionary
340 var findQuery: [CFString: Any] = [:]
341 findQuery[kSecClass] = query[kSecClass]
342 findQuery[kSecAttrSynchronizable] = query[kSecAttrSynchronizable]
343 findQuery[kSecAttrAccessGroup] = query[kSecAttrAccessGroup]
344 findQuery[kSecAttrServer] = query[kSecAttrDescription]
345 findQuery[kSecAttrPath] = query[kSecAttrPath]
346 findQuery[kSecUseDataProtectionKeychain] = query[kSecUseDataProtectionKeychain]
348 var updateQuery: [CFString: Any] = query
349 updateQuery[kSecClass] = nil
351 status = SecItemUpdate(findQuery as CFDictionary, updateQuery as CFDictionary)
353 if status != errSecSuccess {
354 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
357 throw ContainerError.failedToStoreSecret(errorCode: Int(status))
361 func loadSecret(label: String) throws -> (Data?) {
364 let query: [CFString: Any] = [
365 kSecClass: kSecClassInternetPassword,
366 kSecAttrAccessGroup: "com.apple.security.octagon",
367 kSecAttrDescription: label,
368 kSecReturnAttributes: true,
369 kSecReturnData: true,
370 kSecAttrSynchronizable: false,
371 kSecMatchLimit: kSecMatchLimitOne,
374 var result: CFTypeRef?
375 let status = SecItemCopyMatching(query as CFDictionary, &result)
377 if status != errSecSuccess || result == nil {
378 throw ContainerError.failedToLoadSecret(errorCode: Int(status))
382 if let dictionary = result as? [CFString: Any] {
383 secret = dictionary[kSecValueData] as? Data
385 throw ContainerError.failedToLoadSecretDueToType
391 func saveEgoKeyPair(_ keyPair: _SFECKeyPair, identifier: String, resultHandler: @escaping (Bool, Error?) -> Void) {
392 let keychainManager = _SFKeychainManager.default()
393 let signingIdentity = _SFIdentity(keyPair: keyPair)
394 let accessibility = SFAccessibilityMakeWithMode(SFAccessibilityMode.accessibleWhenUnlocked) // class A
395 let accessPolicy = _SFAccessPolicy(accessibility: accessibility,
396 sharingPolicy: SFSharingPolicy.thisDeviceOnly)
397 accessPolicy.accessGroup = egoIdentitiesAccessGroup
398 keychainManager.setIdentity(signingIdentity,
399 forIdentifier: identifier,
400 accessPolicy: accessPolicy,
401 resultHandler: resultHandler)
404 func loadEgoKeyPair(identifier: String, resultHandler: @escaping (_SFECKeyPair?, Error?) -> Void) {
405 let keychainManager = _SFKeychainManager.default()
407 // FIXME constrain to egoIdentitiesAccessGroup, <rdar://problem/39597940>
408 keychainManager.identity(forIdentifier: identifier) { result in
409 switch result.resultType {
410 case .valueAvailable:
411 resultHandler(result.value?.keyPair as? _SFECKeyPair, nil)
412 case .needsAuthentication:
413 resultHandler(nil, ContainerError.needsAuthentication)
415 resultHandler(nil, result.error)
417 resultHandler(nil, ContainerError.unknownSecurityFoundationError)
422 func loadEgoKeys(peerID: String, resultHandler: @escaping (OctagonSelfPeerKeys?, Error?) -> Void) {
423 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: peerID)) { signingKey, error in
424 guard let signingKey = signingKey else {
425 os_log("Unable to load signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
426 resultHandler(nil, error)
430 loadEgoKeyPair(identifier: encryptionKeyIdentifier(peerID: peerID)) { encryptionKey, error in
431 guard let encryptionKey = encryptionKey else {
432 os_log("Unable to load encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
433 resultHandler(nil, error)
438 resultHandler(try OctagonSelfPeerKeys(peerID: peerID, signingKey: signingKey, encryptionKey: encryptionKey), nil)
440 resultHandler(nil, error)
446 func removeEgoKeysSync(peerID: String) throws -> Bool {
447 var resultSema = DispatchSemaphore(value: 0)
449 let keychainManager = _SFKeychainManager.default()
451 var retresultForSigningDeletion: Bool = false
452 var reterrorForSigningDeletion: Error?
454 //remove signing keys
455 keychainManager.removeItem(withIdentifier: signingKeyIdentifier(peerID: peerID)) { result, error in
456 retresultForSigningDeletion = result
457 reterrorForSigningDeletion = error
463 if let error = reterrorForSigningDeletion {
467 if retresultForSigningDeletion == false {
468 return retresultForSigningDeletion
471 // now let's do the same thing with the encryption keys
472 resultSema = DispatchSemaphore(value: 0)
473 var retresultForEncryptionDeletion: Bool = false
474 var reterrorForEncryptionDeletion: Error?
476 keychainManager.removeItem(withIdentifier: encryptionKeyIdentifier(peerID: peerID)) { result, error in
477 retresultForEncryptionDeletion = result
478 reterrorForEncryptionDeletion = error
483 if let error = reterrorForEncryptionDeletion {
487 return retresultForEncryptionDeletion && retresultForSigningDeletion
490 func loadEgoKeysSync(peerID: String) throws -> OctagonSelfPeerKeys {
491 // Gotta promote to synchronous; 'antipattern' ahoy
492 let resultSema = DispatchSemaphore(value: 0)
494 var result: OctagonSelfPeerKeys?
497 loadEgoKeys(peerID: peerID) { keys, error in
504 if let error = reserror {
508 if let result = result {
515 func signingKeyIdentifier(peerID: String) -> String {
516 return "signing-key " + peerID
519 func encryptionKeyIdentifier(peerID: String) -> String {
520 return "encryption-key " + peerID
523 func makeTLKShares(ckksTLKs: [CKKSKeychainBackedKey]?, asPeer: CKKSSelfPeer, toPeer: CKKSPeer, epoch: Int) throws -> [TLKShare] {
524 return try (ckksTLKs ?? []).map { tlk in
525 os_log("Making TLKShare for %@ for key %@", log: tplogDebug, type: .default, toPeer.description, tlk)
526 // Not being able to convert a TLK to a TLKShare is a failure, but not having a TLK is only half bad
528 return TLKShare.convert(ckksTLKShare: try CKKSTLKShare(tlk, as: asPeer, to: toPeer, epoch: epoch, poisoned: 0))
530 let nserror = error as NSError
531 if nserror.domain == "securityd" && nserror.code == errSecItemNotFound {
532 os_log("No TLK contents for %@, no TLK share possible", log: tplogDebug, type: .default, tlk)
541 func extract(tlkShares: [CKKSTLKShare], peer: CKKSSelfPeer) {
542 os_log("Attempting to recover %d TLK shares for peer %@", log: tplogDebug, type: .default, tlkShares.count, peer.peerID)
543 for share in tlkShares {
544 guard share.receiverPeerID == peer.peerID else {
545 os_log("Skipping %@ (wrong peerID)", log: tplogDebug, type: .default, share)
550 // TODO: how should we handle peer sets here?
551 let key = try share.recoverTLK(peer,
552 trustedPeers: [peer as! AnyHashable],
555 try key.saveMaterialToKeychain()
556 os_log("Recovered %@ (from %@)", log: tplogDebug, type: .default, key, share)
558 os_log("Failed to recover share %@: %@", log: tplogDebug, type: .default, share, error as CVarArg)
564 struct ContainerState {
565 var egoPeerID: String?
566 var peers: [String: TPPeer] = [:]
567 var vouchers: [TPVoucher] = []
568 var bottles = Set<BottleMO>()
569 var recoverySigningKey: Data?
570 var recoveryEncryptionKey: Data?
573 internal struct StableChanges {
574 let deviceName: String?
575 let serialNumber: String?
576 let osVersion: String?
577 let policyVersion: UInt64?
578 let policySecrets: [String: Data]?
579 let recoverySigningPubKey: Data?
580 var recoveryEncryptionPubKey: Data?
583 // CoreData doesn't handle creating an identical model from an identical URL. Help it out.
584 private var nsObjectModels: [URL: NSManagedObjectModel] = [:]
585 private let nsObjectModelsQueue = DispatchQueue(label: "com.apple.security.TrustedPeersHelper.nsObjectModels")
586 func getOrMakeModel(url: URL) -> NSManagedObjectModel {
587 return nsObjectModelsQueue.sync {
588 if let model = nsObjectModels[url] {
591 let newModel = NSManagedObjectModel(contentsOf: url)!
592 nsObjectModels[url] = newModel
597 struct ContainerName: Hashable, CustomStringConvertible {
598 let container: String
601 func asSingleString() -> String {
602 // Just to be nice, hide the fact that multiple contexts exist on most machines
603 if self.context == OTDefaultContext {
604 return self.container
606 return self.container + "-" + self.context
610 var description: String {
611 return "Container(\(self.container),\(self.context))"
615 /// This maps to a Cuttlefish service backed by a CloudKit container,
616 /// and a corresponding local Core Data persistent container.
618 /// Methods may be invoked concurrently.
619 class Container: NSObject {
620 let name: ContainerName
622 private let cuttlefish: CuttlefishAPIAsync
624 // Only one request (from Client) is permitted to be in progress at a time.
625 // That includes while waiting for network, i.e. one request must complete
626 // before the next can begin. Otherwise two requests could in parallel be
627 // fetching updates from Cuttlefish, with ensuing changeToken overwrites etc.
628 // This applies for mutating requests -- requests that can only read the current
629 // state (on the moc queue) do not need to.
630 internal let semaphore = DispatchSemaphore(value: 1)
632 // All Core Data access happens through moc: NSManagedObjectContext. The
633 // moc insists on having its own queue, and all operations must happen on
635 internal let moc: NSManagedObjectContext
637 // Rather than Container having its own dispatch queue, we use moc's queue
638 // to synchronise access to our own state as well. So the following instance
639 // variables must only be accessed within blocks executed by calling
640 // moc.perform() or moc.performAndWait().
641 internal var containerMO: ContainerMO
642 internal var model: TPModel
645 Construct a Container.
647 - Parameter name: The name the CloudKit container to which requests will be routed.
649 The "real" container that drives CKKS etc should be named `"com.apple.security.keychain"`.
650 Use other names for test containers such as for rawfish (rawhide-style testing for cuttlefish)
652 - Parameter persistentStoreURL: The location the local Core Data database for this container will be stored.
654 - Parameter cuttlefish: Interface to cuttlefish.
656 init(name: ContainerName, persistentStoreDescription: NSPersistentStoreDescription, cuttlefish: CuttlefishAPIAsync) throws {
657 var initError: Error?
658 var containerMO: ContainerMO?
661 // Set up Core Data stack
662 let url = Bundle(for: type(of: self)).url(forResource: "TrustedPeersHelper", withExtension: "momd")!
663 let mom = getOrMakeModel(url: url)
664 let persistentContainer = NSPersistentContainer(name: "TrustedPeersHelper", managedObjectModel: mom)
665 persistentContainer.persistentStoreDescriptions = [persistentStoreDescription]
667 persistentContainer.loadPersistentStores { _, error in
670 if let initError = initError {
674 let moc = persistentContainer.newBackgroundContext()
675 moc.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
678 // Fetch an existing ContainerMO record if it exists, or create and save one
680 let containerFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Container")
681 containerFetch.predicate = NSPredicate(format: "name == %@", name.asSingleString())
682 let fetchedContainers = try moc.fetch(containerFetch)
683 if let container = fetchedContainers.first as? ContainerMO {
684 containerMO = container
686 containerMO = ContainerMO(context: moc)
687 containerMO!.name = name.asSingleString()
690 // Perform upgrades as needed
691 Container.onqueueUpgradeMachineIDSetToModel(container: containerMO!, moc: moc)
692 Container.onqueueUpgradeMachineIDSetToUseStatus(container: containerMO!, moc: moc)
694 model = Container.loadModel(from: containerMO!)
695 Container.ensureEgoConsistency(from: containerMO!, model: model!)
702 if let initError = initError {
708 self.containerMO = containerMO!
709 self.cuttlefish = cuttlefish
715 // Must be on containerMO's moc queue to call this
716 internal static func loadModel(from containerMO: ContainerMO) -> TPModel {
717 // Populate model from persistent store
718 let model = TPModel(decrypter: Decrypter())
719 let keyFactory = TPECPublicKeyFactory()
720 let peers = containerMO.peers as? Set<PeerMO>
721 peers?.forEach { peer in
722 guard let permanentInfo = TPPeerPermanentInfo(peerID: peer.peerID!,
723 data: peer.permanentInfo! as Data,
724 sig: peer.permanentInfoSig! as Data,
725 keyFactory: keyFactory) else {
728 model.registerPeer(with: permanentInfo)
729 if let data = peer.stableInfo, let sig = peer.stableInfoSig {
730 if let stableInfo = TPPeerStableInfo(data: data as Data, sig: sig as Data) {
732 try model.update(stableInfo, forPeerWithID: permanentInfo.peerID)
734 os_log("loadModel unable to update stable info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg)
737 os_log("loadModel: peer %@ has unparseable stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
740 os_log("loadModel: peer %@ has no stable info", log: tplogDebug, type: .default, permanentInfo.peerID)
742 if let data = peer.dynamicInfo, let sig = peer.dynamicInfoSig {
743 if let dynamicInfo = TPPeerDynamicInfo(data: data as Data, sig: sig as Data) {
745 try model.update(dynamicInfo, forPeerWithID: permanentInfo.peerID)
747 os_log("loadModel unable to update dynamic info for peer(%@): %@", log: tplogDebug, type: .default, peer, error as CVarArg)
750 os_log("loadModel: peer %@ has unparseable dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
753 os_log("loadModel: peer %@ has no dynamic info", log: tplogDebug, type: .default, permanentInfo.peerID)
755 peer.vouchers?.forEach {
756 let v = $0 as! VoucherMO
757 if let data = v.voucherInfo, let sig = v.voucherInfoSig {
758 if let voucher = TPVoucher(infoWith: data, sig: sig) {
759 model.register(voucher)
765 // Register persisted policies (cached from cuttlefish)
766 let policies = containerMO.policies as? Set<PolicyMO>
767 policies?.forEach { policyMO in
768 if let policyHash = policyMO.policyHash,
769 let policyData = policyMO.policyData {
770 if let policyDoc = TPPolicyDocument.policyDoc(withHash: policyHash, data: policyData) {
771 model.register(policyDoc)
776 // Register built-in policies
777 builtInPolicyDocuments().forEach { policyDoc in
778 model.register(policyDoc)
781 let knownMachines = containerMO.machines as? Set<MachineMO> ?? Set()
782 let allowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.allowed.rawValue }.compactMap { $0.machineID })
783 let disallowedMachineIDs = Set(knownMachines.filter { $0.status == TPMachineIDStatus.disallowed.rawValue }.compactMap { $0.machineID })
785 os_log("loadModel: allowedMachineIDs: %@", log: tplogDebug, type: .default, allowedMachineIDs)
786 os_log("loadModel: disallowedMachineIDs: %@", log: tplogDebug, type: .default, disallowedMachineIDs)
788 if allowedMachineIDs.count == 0 {
789 os_log("loadModel: no allowedMachineIDs?", log: tplogDebug, type: .default)
795 // Must be on containerMO's moc queue to call this
796 internal static func ensureEgoConsistency(from containerMO: ContainerMO, model: TPModel) {
797 guard let egoPeerID = containerMO.egoPeerID,
798 let egoStableData = containerMO.egoPeerStableInfo,
799 let egoStableSig = containerMO.egoPeerStableInfoSig
801 os_log("ensureEgoConsistency failed to find ego peer information", log: tplogDebug, type: .error)
805 guard let containerEgoStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
806 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from container", log: tplogDebug, type: .error)
810 guard let modelStableInfo = model.getStableInfoForPeer(withID: egoPeerID) else {
811 os_log("ensureEgoConsistency failed to create TPPeerStableInfo from model", log: tplogDebug, type: .error)
815 if modelStableInfo.clock > containerEgoStableInfo.clock {
816 containerMO.egoPeerStableInfo = modelStableInfo.data
817 containerMO.egoPeerStableInfoSig = modelStableInfo.sig
822 static func dictionaryRepresentation(bottle: BottleMO) -> [String: Any] {
823 var dict: [String: String] = [:]
825 dict["bottleID"] = bottle.bottleID
826 dict["peerID"] = bottle.peerID
827 dict["signingSPKI"] = bottle.escrowedSigningSPKI?.base64EncodedString()
828 dict["signatureUsingPeerKey"] = bottle.signatureUsingPeerKey?.base64EncodedString()
829 dict["signatureUsingSPKI"] = bottle.signatureUsingEscrowKey?.base64EncodedString()
830 // ignore the bottle contents; they're mostly unreadable
835 static func peerdictionaryRepresentation(peer: TPPeer) -> [String: Any] {
836 var peerDict: [String: Any] = [
837 "permanentInfo": peer.permanentInfo.dictionaryRepresentation(),
838 "peerID": peer.peerID,
840 if let stableInfo = peer.stableInfo {
841 peerDict["stableInfo"] = stableInfo.dictionaryRepresentation()
843 if let dynamicInfo = peer.dynamicInfo {
844 peerDict["dynamicInfo"] = dynamicInfo.dictionaryRepresentation()
850 func onQueueDetermineLocalTrustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
851 let viablePeerCountsByModelID = self.model.viablePeerCountsByModelID()
853 if let egoPeerID = self.containerMO.egoPeerID {
854 var status = self.model.statusOfPeer(withID: egoPeerID)
855 var isExcluded: Bool = (status == .excluded)
857 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, loadError in
858 var returnError = loadError
860 guard returnError == nil else {
862 if let error = (loadError as NSError?) {
863 os_log("trust status: Unable to load ego keys: %@", log: tplogDebug, type: .default, error as CVarArg)
864 if error.code == errSecItemNotFound && error.domain == NSOSStatusErrorDomain {
865 os_log("trust status: Lost the ego key pair, returning 'excluded' in hopes of fixing up the identity", log: tplogDebug, type: .debug)
868 } else if error.code == errSecInteractionNotAllowed && error.domain == NSOSStatusErrorDomain {
869 // we have an item, but can't read it, suppressing error
875 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
877 viablePeerCountsByModelID: viablePeerCountsByModelID,
878 isExcluded: isExcluded,
880 reply(egoStatus, returnError)
884 //ensure egoPeerKeys are populated
885 guard egoPeerKeys != nil else {
886 os_log("trust status: No error but Ego Peer Keys are nil", log: tplogDebug, type: .default)
887 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
889 viablePeerCountsByModelID: viablePeerCountsByModelID,
893 reply(egoStatus, loadError)
897 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: egoPeerID,
899 viablePeerCountsByModelID: viablePeerCountsByModelID,
900 isExcluded: isExcluded,
902 reply(egoStatus, nil)
907 // With no ego peer ID, either return 'excluded' if there are extant peers, or 'unknown' to signal no peers at all
908 if self.model.allPeerIDs().isEmpty {
909 os_log("No existing peers in account", log: tplogDebug, type: .debug)
910 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
912 viablePeerCountsByModelID: viablePeerCountsByModelID,
915 reply(egoStatus, nil)
918 os_log("Existing peers in account, but we don't have a peer ID. We are excluded.", log: tplogDebug, type: .debug)
919 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
921 viablePeerCountsByModelID: viablePeerCountsByModelID,
924 reply(egoStatus, nil)
930 func trustStatus(reply: @escaping (TrustedPeersHelperEgoPeerStatus, Error?) -> Void) {
931 self.semaphore.wait()
932 let reply: (TrustedPeersHelperEgoPeerStatus, Error?) -> Void = {
933 // Suppress logging of successful replies here; it's not that useful
934 let logType: OSLogType = $1 == nil ? .debug : .info
935 os_log("trustStatus complete: %@ %@",
936 log: tplogTrace, type: logType, TPPeerStatusToString($0.egoStatus), traceError($1))
938 self.semaphore.signal()
941 self.moc.performAndWait {
942 // Knowledge of your peer status only exists if you know about other peers. If you haven't fetched, fetch.
943 if self.containerMO.changeToken == nil {
944 self.fetchAndPersistChanges { fetchError in
945 guard fetchError == nil else {
946 if let error = fetchError {
947 os_log("Unable to fetch changes, trust status is unknown: %@", log: tplogDebug, type: .default, error as CVarArg)
950 let egoStatus = TrustedPeersHelperEgoPeerStatus(egoPeerID: nil,
952 viablePeerCountsByModelID: [:],
955 reply(egoStatus, fetchError)
959 self.moc.performAndWait {
960 self.onQueueDetermineLocalTrustStatus(reply: reply)
964 self.onQueueDetermineLocalTrustStatus(reply: reply)
969 func fetchTrustState(reply: @escaping (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void) {
970 let reply: (TrustedPeersHelperPeerState?, [TrustedPeersHelperPeer]?, Error?) -> Void = {
971 os_log("fetch trust state complete: %@ %@",
972 log: tplogTrace, type: .info, String(reflecting: $0), traceError($2))
976 self.moc.performAndWait {
977 if let egoPeerID = self.containerMO.egoPeerID,
978 let egoPermData = self.containerMO.egoPeerPermanentInfo,
979 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig {
981 let keyFactory = TPECPublicKeyFactory()
982 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
983 os_log("fetchTrustState failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
984 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
988 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(permanentInfo.signingPubKey.spki())
989 os_log("fetchTrustState: ego peer is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
991 let egoStableInfo = self.model.getStableInfoForPeer(withID: egoPeerID)
993 let egoPeerStatus = TrustedPeersHelperPeerState(peerID: egoPeerID,
994 isPreapproved: isPreapproved,
995 status: self.model.statusOfPeer(withID: egoPeerID),
996 memberChanges: false,
997 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
998 osVersion: egoStableInfo?.osVersion)
1000 var tphPeers: [TrustedPeersHelperPeer] = []
1002 if let egoPeer = self.model.peer(withID: egoPeerID) {
1003 egoPeer.trustedPeerIDs.forEach { trustedPeerID in
1004 if let peer = self.model.peer(withID: trustedPeerID) {
1005 let peerViews = try? self.model.getViewsForPeer(peer.permanentInfo,
1006 stableInfo: peer.stableInfo,
1009 tphPeers.append(TrustedPeersHelperPeer(peerID: trustedPeerID,
1010 signingSPKI: peer.permanentInfo.signingPubKey.spki(),
1011 encryptionSPKI: peer.permanentInfo.encryptionPubKey.spki(),
1012 viewList: peerViews ?? Set()))
1014 os_log("No peer for trusted ID %@", log: tplogDebug, type: .default, trustedPeerID)
1018 os_log("No ego peer in model; no trusted peers", log: tplogDebug, type: .default)
1021 os_log("Returning trust state: %@ %@", log: tplogDebug, type: .default, egoPeerStatus, tphPeers)
1022 reply(egoPeerStatus, tphPeers, nil)
1024 // With no ego peer ID, there are no trusted peers
1025 os_log("No peer ID => no trusted peers", log: tplogDebug, type: .debug)
1026 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: false, unknownMachineIDs: false, osVersion: nil), [], nil)
1031 func dump(reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1032 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1033 os_log("dump complete: %@",
1034 log: tplogTrace, type: .info, traceError($1))
1037 self.moc.performAndWait {
1038 var d: [AnyHashable: Any] = [:]
1040 if let egoPeerID = self.containerMO.egoPeerID {
1041 if let peer = self.model.peer(withID: egoPeerID) {
1042 d["self"] = Container.peerdictionaryRepresentation(peer: peer)
1044 d["self"] = ["peerID": egoPeerID]
1050 d["peers"] = self.model.allPeers().filter { $0.peerID != self.containerMO.egoPeerID }.map { peer in
1051 Container.peerdictionaryRepresentation(peer: peer)
1054 d["vouchers"] = self.model.allVouchers().map { $0.dictionaryRepresentation() }
1056 if let bottles = self.containerMO.bottles as? Set<BottleMO> {
1057 d["bottles"] = bottles.map { Container.dictionaryRepresentation(bottle: $0) }
1062 let midList = self.onqueueCurrentMIDList()
1063 d["machineIDsAllowed"] = midList.machineIDs(in: .allowed).sorted()
1064 d["machineIDsDisallowed"] = midList.machineIDs(in: .disallowed).sorted()
1065 d["modelRecoverySigningPublicKey"] = self.model.recoverySigningPublicKey()
1066 d["modelRecoveryEncryptionPublicKey"] = self.model.recoveryEncryptionPublicKey()
1072 func dumpEgoPeer(reply: @escaping (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void) {
1073 let reply: (String?, TPPeerPermanentInfo?, TPPeerStableInfo?, TPPeerDynamicInfo?, Error?) -> Void = {
1074 os_log("dumpEgoPeer complete: %@", log: tplogTrace, type: .info, traceError($4))
1075 reply($0, $1, $2, $3, $4)
1077 self.moc.performAndWait {
1078 guard let egoPeerID = self.containerMO.egoPeerID else {
1079 reply(nil, nil, nil, nil, ContainerError.noPreparedIdentity)
1083 guard let peer = self.model.peer(withID: egoPeerID) else {
1084 reply(egoPeerID, nil, nil, nil, nil)
1088 reply(egoPeerID, peer.permanentInfo, peer.stableInfo, peer.dynamicInfo, nil)
1092 func validatePeers(request: ValidatePeersRequest, reply: @escaping ([AnyHashable: Any]?, Error?) -> Void) {
1093 self.semaphore.wait()
1094 let reply: ([AnyHashable: Any]?, Error?) -> Void = {
1095 os_log("validatePeers complete %@", log: tplogTrace, type: .info, traceError($1))
1096 self.semaphore.signal()
1100 self.cuttlefish.validatePeers(request) { response, error in
1101 os_log("ValidatePeers(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1102 guard let response = response, error == nil else {
1103 os_log("validatePeers failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1104 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
1108 var info: [AnyHashable: Any] = [:]
1109 info["health"] = response.validatorsHealth as AnyObject
1110 info["results"] = try? JSONSerialization.jsonObject(with: response.jsonUTF8Data())
1116 func getViews(inViews: [String], reply: @escaping ([String]?, Error?) -> Void) {
1117 let reply: ([String]?, Error?) -> Void = {
1118 os_log("getViews complete %@", log: tplogTrace, type: .info, traceError($1))
1121 self.moc.performAndWait {
1122 guard let egoPeerID = self.containerMO.egoPeerID,
1123 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1124 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1125 let egoStableData = self.containerMO.egoPeerStableInfo,
1126 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1128 os_log("getViews failed to find ego peer information", log: tplogDebug, type: .error)
1129 reply(nil, ContainerError.noPreparedIdentity)
1132 guard let stableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1133 os_log("getViews failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
1134 reply(nil, ContainerError.invalidStableInfoOrSig)
1138 let keyFactory = TPECPublicKeyFactory()
1139 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1140 os_log("getViews failed to create TPPeerPermanentInfo", log: tplogDebug, type: .error)
1141 reply(nil, ContainerError.invalidPermanentInfoOrSig)
1146 let views = try self.model.getViewsForPeer(permanentInfo, stableInfo: stableInfo, inViews: Set(inViews))
1147 reply(Array(views), nil)
1155 func reset(resetReason: CuttlefishResetReason, reply: @escaping (Error?) -> Void) {
1156 self.semaphore.wait()
1157 let reply: (Error?) -> Void = {
1158 os_log("reset complete %@", log: tplogTrace, type: .info, traceError($0))
1159 self.semaphore.signal()
1163 self.moc.performAndWait {
1164 let resetReason = ResetReason.from(cuttlefishResetReason: resetReason)
1165 let request = ResetRequest.with {
1166 $0.resetReason = resetReason
1168 self.cuttlefish.reset(request) { response, error in
1169 os_log("Reset(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1170 guard let response = response, error == nil else {
1171 os_log("reset failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1172 reply(error ?? ContainerError.cloudkitResponseMissing)
1176 // Erase container's persisted state
1177 self.moc.performAndWait {
1178 self.moc.delete(self.containerMO)
1179 self.containerMO = ContainerMO(context: self.moc)
1180 self.containerMO.name = self.name.asSingleString()
1181 self.model = Container.loadModel(from: self.containerMO)
1183 try self.onQueuePersist(changes: response.changes)
1184 os_log("reset succeded", log: tplogDebug, type: .default)
1187 os_log("reset persist failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1195 func localReset(reply: @escaping (Error?) -> Void) {
1196 self.semaphore.wait()
1197 let reply: (Error?) -> Void = {
1198 os_log("localReset complete %@", log: tplogTrace, type: .info, traceError($0))
1199 self.semaphore.signal()
1203 self.moc.performAndWait {
1205 // Erase container's persisted state
1206 self.moc.delete(self.containerMO)
1207 self.containerMO = ContainerMO(context: self.moc)
1208 self.containerMO.name = self.name.asSingleString()
1209 self.model = Container.loadModel(from: self.containerMO)
1219 func loadOrCreateKeyPair(privateKeyPersistentRef: Data?) throws -> _SFECKeyPair {
1220 if let privateKeyPersistentRef = privateKeyPersistentRef {
1221 return try TPHObjectiveC.fetchKeyPair(withPrivateKeyPersistentRef: privateKeyPersistentRef)
1223 let keySpecifier = _SFECKeySpecifier(curve: SFEllipticCurve.nistp384)
1224 guard let keyPair = _SFECKeyPair(randomKeyPairWith: keySpecifier) else {
1225 throw ContainerError.unableToCreateKeyPair
1231 // policyVersion should only be non-nil for testing, to override prevailingPolicyVersion
1232 func prepare(epoch: UInt64,
1237 deviceName: String?,
1238 serialNumber: String,
1240 policyVersion: UInt64?,
1241 policySecrets: [String: Data]?,
1242 signingPrivateKeyPersistentRef: Data?,
1243 encryptionPrivateKeyPersistentRef: Data?,
1244 reply: @escaping (String?, Data?, Data?, Data?, Data?, Error?) -> Void) {
1245 self.semaphore.wait()
1246 let reply: (String?, Data?, Data?, Data?, Data?, Error?) -> Void = {
1247 os_log("prepare complete peerID: %@ %@",
1248 log: tplogTrace, type: .info, ($0 ?? "NULL") as CVarArg, traceError($5))
1249 self.semaphore.signal()
1250 reply($0, $1, $2, $3, $4, $5)
1253 // Create a new peer identity with random keys, and store the keys in keychain
1254 let permanentInfo: TPPeerPermanentInfo
1255 let signingKeyPair: _SFECKeyPair
1256 let encryptionKeyPair: _SFECKeyPair
1258 signingKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: signingPrivateKeyPersistentRef)
1259 encryptionKeyPair = try self.loadOrCreateKeyPair(privateKeyPersistentRef: encryptionPrivateKeyPersistentRef)
1261 permanentInfo = try TPPeerPermanentInfo(machineID: machineID,
1264 signing: signingKeyPair,
1265 encryptionKeyPair: encryptionKeyPair,
1266 peerIDHashAlgo: TPHashAlgo.SHA256)
1269 reply(nil, nil, nil, nil, nil, error)
1273 let peerID = permanentInfo.peerID
1275 let bottle: BottledPeer
1277 bottle = try BottledPeer(peerID: peerID,
1279 peerSigningKey: signingKeyPair,
1280 peerEncryptionKey: encryptionKeyPair,
1281 bottleSalt: bottleSalt)
1283 _ = try saveSecret(bottle.secret, label: peerID)
1285 os_log("bottle creation failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1286 reply(nil, nil, nil, nil, nil, error)
1290 saveEgoKeyPair(signingKeyPair, identifier: signingKeyIdentifier(peerID: peerID)) { success, error in
1291 guard success else {
1292 os_log("Unable to save signing key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1293 reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1296 saveEgoKeyPair(encryptionKeyPair, identifier: encryptionKeyIdentifier(peerID: peerID)) { success, error in
1297 guard success else {
1298 os_log("Unable to save encryption key: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1299 reply(nil, nil, nil, nil, nil, error ?? ContainerError.failedToStoreIdentity)
1303 // Save the prepared identity as containerMO.egoPeer* and its bottle
1304 self.moc.performAndWait {
1306 let policyVersion = policyVersion ?? prevailingPolicyVersion
1307 let policyDoc = try self.getPolicyDoc(policyVersion)
1309 let stableInfo = TPPeerStableInfo(clock: 1,
1310 policyVersion: policyDoc.policyVersion,
1311 policyHash: policyDoc.policyHash,
1312 policySecrets: policySecrets,
1313 deviceName: deviceName,
1314 serialNumber: serialNumber,
1315 osVersion: osVersion,
1316 signing: signingKeyPair,
1317 recoverySigningPubKey: nil,
1318 recoveryEncryptionPubKey: nil,
1321 self.containerMO.egoPeerID = permanentInfo.peerID
1322 self.containerMO.egoPeerPermanentInfo = permanentInfo.data
1323 self.containerMO.egoPeerPermanentInfoSig = permanentInfo.sig
1324 self.containerMO.egoPeerStableInfo = stableInfo.data
1325 self.containerMO.egoPeerStableInfoSig = stableInfo.sig
1327 let bottleMO = BottleMO(context: self.moc)
1328 bottleMO.peerID = bottle.peerID
1329 bottleMO.bottleID = bottle.bottleID
1330 bottleMO.escrowedSigningSPKI = bottle.escrowSigningSPKI
1331 bottleMO.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
1332 bottleMO.signatureUsingPeerKey = bottle.signatureUsingPeerKey
1333 bottleMO.contents = bottle.contents
1335 self.containerMO.addToBottles(bottleMO)
1339 reply(permanentInfo.peerID, permanentInfo.data, permanentInfo.sig, stableInfo.data, stableInfo.sig, nil)
1341 reply(nil, nil, nil, nil, nil, error)
1347 func getEgoEpoch(reply: @escaping (UInt64, Error?) -> Void) {
1348 let reply: (UInt64, Error?) -> Void = {
1349 os_log("getEgoEpoch complete: %d %@", log: tplogTrace, type: .info, $0, traceError($1))
1353 self.moc.performAndWait {
1354 guard let egoPeerID = self.containerMO.egoPeerID else {
1355 reply(0, ContainerError.noPreparedIdentity)
1358 guard let egoPeer = self.model.peer(withID: egoPeerID) else {
1359 reply(0, ContainerError.noPreparedIdentity)
1363 reply(egoPeer.permanentInfo.epoch, nil)
1366 func establish(ckksKeys: [CKKSKeychainBackedKeySet],
1367 tlkShares: [CKKSTLKShare],
1368 preapprovedKeys: [Data]?,
1369 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
1370 self.semaphore.wait()
1371 let reply: (String?, [CKRecord], Error?) -> Void = {
1372 os_log("establish complete peer: %@ %@",
1373 log: tplogTrace, type: .default, ($0 ?? "NULL") as CVarArg, traceError($2))
1374 self.semaphore.signal()
1378 self.moc.performAndWait {
1379 self.onqueueEstablish(ckksKeys: ckksKeys,
1380 tlkShares: tlkShares,
1381 preapprovedKeys: preapprovedKeys,
1386 func onqueueTTRUntrusted() {
1387 let ttr = SecTapToRadar(tapToRadar: "Device not IDMS trusted",
1388 description: "Device not IDMS trusted",
1393 func onqueueEstablish(ckksKeys: [CKKSKeychainBackedKeySet],
1394 tlkShares: [CKKSTLKShare],
1395 preapprovedKeys: [Data]?,
1396 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
1397 // Fetch ego peer identity from local storage.
1398 guard let egoPeerID = self.containerMO.egoPeerID,
1399 let egoPermData = self.containerMO.egoPeerPermanentInfo,
1400 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
1401 let egoStableData = self.containerMO.egoPeerStableInfo,
1402 let egoStableSig = self.containerMO.egoPeerStableInfoSig
1404 reply(nil, [], ContainerError.noPreparedIdentity)
1408 let keyFactory = TPECPublicKeyFactory()
1409 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
1410 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
1413 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
1414 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1415 reply(nil, [], ContainerError.invalidStableInfoOrSig)
1418 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
1419 os_log("establish: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
1420 self.onqueueTTRUntrusted()
1421 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
1425 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
1426 guard let egoPeerKeys = egoPeerKeys else {
1427 os_log("Don't have my own peer keys; can't establish: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
1428 reply(nil, [], error)
1431 self.moc.performAndWait {
1432 let viewKeys: [ViewKeys] = ckksKeys.map(ViewKeys.convert)
1433 let allTLKShares: [TLKShare]
1435 let octagonShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk }, asPeer: egoPeerKeys, toPeer: egoPeerKeys, epoch: Int(selfPermanentInfo.epoch))
1436 let sosShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
1438 allTLKShares = octagonShares + sosShares
1440 os_log("Unable to make TLKShares for self: %@", log: tplogDebug, type: .default, error as CVarArg)
1441 reply(nil, [], error)
1445 let dynamicInfo: TPPeerDynamicInfo
1447 dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
1448 peerPermanentInfo: selfPermanentInfo,
1449 peerStableInfo: selfStableInfo,
1451 preapprovedKeys: preapprovedKeys,
1452 signing: egoPeerKeys.signingKey,
1453 currentMachineIDs: self.onqueueCurrentMIDList())
1455 os_log("dynamic info: %@", log: tplogDebug, type: .default, dynamicInfo)
1457 reply(nil, [], error)
1461 let peer = Peer.with {
1462 $0.peerID = egoPeerID
1463 $0.permanentInfoAndSig.peerPermanentInfo = egoPermData
1464 $0.permanentInfoAndSig.sig = egoPermSig
1465 $0.stableInfoAndSig.peerStableInfo = egoStableData
1466 $0.stableInfoAndSig.sig = egoStableSig
1467 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
1472 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
1474 reply(nil, [], error)
1477 os_log("Beginning establish for peer %@", log: tplogDebug, type: .default, egoPeerID)
1478 os_log("Establish permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
1479 os_log("Establish permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
1480 os_log("Establish stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
1481 os_log("Establish stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
1482 os_log("Establish dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
1483 os_log("Establish dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
1485 os_log("Establish introducing %d key sets, %d tlk shares", log: tplogDebug, type: .default, viewKeys.count, allTLKShares.count)
1488 os_log("Establish bottle: %@", log: tplogDebug, type: .debug, try bottle.serializedData().base64EncodedString())
1489 os_log("Establish peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
1491 os_log("Establish unable to encode bottle/peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
1494 let request = EstablishRequest.with {
1497 $0.viewKeys = viewKeys
1498 $0.tlkShares = allTLKShares
1500 self.cuttlefish.establish(request) { response, error in
1501 os_log("Establish(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1502 os_log("Establish: viewKeys: %@", String(describing: viewKeys))
1503 guard let response = response, error == nil else {
1504 os_log("establish failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1505 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
1510 os_log("Establish returned changes: %@", log: tplogDebug, type: .default, try response.changes.jsonString())
1512 os_log("Establish returned changes, but they can't be serialized", log: tplogDebug, type: .default)
1515 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
1518 try self.persist(changes: response.changes)
1520 guard response.changes.more == false else {
1521 os_log("establish succeeded, but more changes need fetching...", log: tplogDebug, type: .default)
1523 self.fetchAndPersistChanges { fetchError in
1524 guard fetchError == nil else {
1525 // This is an odd error condition: we might be able to fetch again and be in a good state...
1526 os_log("fetch-after-establish failed: %@", log: tplogDebug, type: .default, (fetchError as CVarArg?) ?? "no error")
1527 reply(nil, keyHierarchyRecords, fetchError)
1531 os_log("fetch-after-establish succeeded", log: tplogDebug, type: .default)
1532 reply(egoPeerID, keyHierarchyRecords, nil)
1537 os_log("establish succeeded", log: tplogDebug, type: .default)
1538 reply(egoPeerID, keyHierarchyRecords, nil)
1540 os_log("establish handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1541 reply(nil, keyHierarchyRecords, error)
1548 func setRecoveryKey(recoveryKey: String, salt: String, ckksKeys: [CKKSKeychainBackedKeySet], reply: @escaping (Error?) -> Void) {
1549 self.semaphore.wait()
1550 let reply: (Error?) -> Void = {
1551 os_log("setRecoveryKey complete: %@", log: tplogTrace, type: .info, traceError($0))
1552 self.semaphore.signal()
1556 os_log("beginning a setRecoveryKey", log: tplogDebug, type: .default)
1558 self.moc.performAndWait {
1559 guard let egoPeerID = self.containerMO.egoPeerID else {
1560 os_log("no prepared identity, cannot set recovery key", log: tplogDebug, type: .default)
1561 reply(ContainerError.noPreparedIdentity)
1565 var recoveryKeys: RecoveryKey
1567 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
1569 os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg)
1570 reply(ContainerError.failedToCreateRecoveryKey)
1574 let signingPublicKey: Data = recoveryKeys.peerKeys.signingVerificationKey.keyData
1575 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionVerificationKey.keyData
1577 os_log("setRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString())
1578 os_log("setRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString())
1580 guard let stableInfoData = self.containerMO.egoPeerStableInfo else {
1581 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1582 reply(ContainerError.nonMember)
1585 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1586 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1587 reply(ContainerError.nonMember)
1590 guard let permInfoData = self.containerMO.egoPeerPermanentInfo else {
1591 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1592 reply(ContainerError.nonMember)
1595 guard let permInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1596 os_log("permInfoSig does not exist", log: tplogDebug, type: .default)
1597 reply(ContainerError.nonMember)
1600 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
1601 os_log("cannot create TPPeerStableInfo", log: tplogDebug, type: .default)
1602 reply(ContainerError.invalidStableInfoOrSig)
1605 let keyFactory = TPECPublicKeyFactory()
1606 guard let permanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permInfoData, sig: permInfoSig, keyFactory: keyFactory) else {
1607 os_log("cannot create TPPeerPermanentInfo", log: tplogDebug, type: .default)
1608 reply(ContainerError.invalidStableInfoOrSig)
1612 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
1613 guard let signingKeyPair = signingKeyPair else {
1614 os_log("handle: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1618 self.moc.performAndWait {
1620 let tlkShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
1621 asPeer: recoveryKeys.peerKeys,
1622 toPeer: recoveryKeys.peerKeys,
1623 epoch: Int(permanentInfo.epoch))
1625 let policyVersion = stableInfo.policyVersion
1626 let policyDoc = try self.getPolicyDoc(policyVersion)
1628 let updatedStableInfo = TPPeerStableInfo(clock: stableInfo.clock + 1,
1629 policyVersion: policyDoc.policyVersion,
1630 policyHash: policyDoc.policyHash,
1631 policySecrets: stableInfo.policySecrets,
1632 deviceName: stableInfo.deviceName,
1633 serialNumber: stableInfo.serialNumber,
1634 osVersion: stableInfo.osVersion,
1635 signing: signingKeyPair,
1636 recoverySigningPubKey: signingPublicKey,
1637 recoveryEncryptionPubKey: encryptionPublicKey,
1639 let signedStableInfo = SignedPeerStableInfo(updatedStableInfo)
1641 let request = SetRecoveryKeyRequest.with {
1642 $0.peerID = egoPeerID
1643 $0.recoverySigningPubKey = signingPublicKey
1644 $0.recoveryEncryptionPubKey = encryptionPublicKey
1645 $0.stableInfoAndSig = signedStableInfo
1646 $0.tlkShares = tlkShares
1647 $0.changeToken = self.containerMO.changeToken ?? ""
1650 self.cuttlefish.setRecoveryKey(request) { response, error in
1651 os_log("SetRecoveryKey(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
1652 guard let response = response, error == nil else {
1653 os_log("setRecoveryKey failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1654 reply(error ?? ContainerError.cloudkitResponseMissing)
1658 self.moc.performAndWait {
1660 self.containerMO.egoPeerStableInfo = updatedStableInfo.data
1661 self.containerMO.egoPeerStableInfoSig = updatedStableInfo.sig
1662 try self.onQueuePersist(changes: response.changes)
1664 os_log("setRecoveryKey succeeded", log: tplogDebug, type: .default)
1667 os_log("setRecoveryKey handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
1680 func currentSetContainerBottleID(bottleMOs: Set<BottleMO>, bottleID: String) -> (Bool) {
1681 let bmos = bottleMOs.filter {
1682 $0.bottleID == bottleID
1684 return !bmos.isEmpty
1687 func onqueueFindBottle(bottleID: String, reply: @escaping (BottleMO?, Error?) -> Void) {
1690 var bottles: Set<BottleMO> = []
1691 var shouldPerformFetch = false
1693 if let containerBottles = self.containerMO.bottles as? Set<BottleMO> {
1694 if self.currentSetContainerBottleID(bottleMOs: containerBottles, bottleID: bottleID) == false {
1695 shouldPerformFetch = true
1697 bottles = containerBottles
1700 shouldPerformFetch = true
1703 if shouldPerformFetch == true {
1704 self.fetchViableBottlesWithSemaphore { _, _, error in
1705 guard error == nil else {
1706 os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1711 guard let newBottles = self.containerMO.bottles as? Set<BottleMO> else {
1712 os_log("no bottles on container: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
1713 reply(nil, ContainerError.noBottlesPresent)
1717 guard self.currentSetContainerBottleID(bottleMOs: newBottles, bottleID: bottleID) == true else {
1718 reply(nil, ContainerError.noBottlesForEscrowRecordID)
1722 os_log("onqueueFindBottle found bottle: %@", log: tplogDebug, type: .default, newBottles)
1724 bottles = newBottles.filter {
1725 $0.bottleID == bottleID
1727 if bottles.count > 1 {
1728 reply(nil, ContainerError.tooManyBottlesForPeer)
1731 bmo = bottles.removeFirst()
1735 var filteredBottles = bottles.filter {
1736 $0.bottleID == bottleID
1738 if filteredBottles.count > 1 {
1739 reply(nil, ContainerError.tooManyBottlesForPeer)
1742 bmo = filteredBottles.removeFirst()
1747 func onqueueRecoverBottle(managedBottle: BottleMO, entropy: Data, bottleSalt: String) throws -> BottledPeer {
1748 guard let bottledContents = managedBottle.contents else {
1749 throw ContainerError.bottleDoesNotContainContents
1751 guard let signatureUsingEscrowKey = managedBottle.signatureUsingEscrowKey else {
1752 throw ContainerError.bottleDoesNotContainEscrowKeySignature
1755 guard let signatureUsingPeerKey = managedBottle.signatureUsingPeerKey else {
1756 throw ContainerError.bottleDoesNotContainerPeerKeySignature
1758 guard let sponsorPeerID = managedBottle.peerID else {
1759 throw ContainerError.bottleDoesNotContainPeerID
1762 //verify bottle signature using peer
1764 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1765 os_log("recover bottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1766 throw ContainerError.bottleCreatingPeerNotFound
1768 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1769 os_log("recover bottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1770 throw ContainerError.signatureVerificationFailed
1773 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1775 os_log("Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1776 throw ContainerError.failedToCreateBottledPeer
1780 return try BottledPeer(contents: bottledContents,
1782 bottleSalt: bottleSalt,
1783 signatureUsingEscrow: signatureUsingEscrowKey,
1784 signatureUsingPeerKey: signatureUsingPeerKey)
1786 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1789 return try BottledPeer(contents: bottledContents,
1792 signatureUsingEscrow: signatureUsingEscrowKey,
1793 signatureUsingPeerKey: signatureUsingPeerKey)
1795 os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1796 throw ContainerError.failedToCreateBottledPeer
1801 func preflightVouchWithBottle(bottleID: String,
1802 reply: @escaping (String?, Error?) -> Void) {
1803 self.semaphore.wait()
1804 let reply: (String?, Error?) -> Void = {
1805 os_log("preflightVouchWithBottle complete: %@",
1806 log: tplogTrace, type: .info, traceError($1))
1807 self.semaphore.signal()
1811 self.moc.performAndWait {
1812 self.onqueueFindBottle(bottleID: bottleID) { bottleMO, error in
1813 guard let bottleMO = bottleMO else {
1814 os_log("preflightVouchWithBottle found no bottle: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1819 reply(bottleMO.peerID, nil)
1824 func vouchWithBottle(bottleID: String,
1827 tlkShares: [CKKSTLKShare],
1828 reply: @escaping (Data?, Data?, Error?) -> Void) {
1829 self.semaphore.wait()
1830 let reply: (Data?, Data?, Error?) -> Void = {
1831 os_log("vouchWithBottle complete: %@",
1832 log: tplogTrace, type: .info, traceError($2))
1833 self.semaphore.signal()
1837 self.fetchAndPersistChanges { error in
1838 guard error == nil else {
1839 os_log("vouchWithBottle unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1840 reply(nil, nil, error)
1844 self.onqueueFindBottle(bottleID: bottleID) { returnedBMO, error in
1845 self.moc.performAndWait {
1846 guard error == nil else {
1847 os_log("vouchWithBottle unable to find bottle for escrow record id: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1848 reply(nil, nil, error)
1852 guard let bmo: BottleMO = returnedBMO else {
1853 os_log("vouchWithBottle bottle is nil: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
1854 reply(nil, nil, error)
1858 guard let bottledContents = bmo.contents else {
1859 reply(nil, nil, ContainerError.bottleDoesNotContainContents)
1862 guard let signatureUsingEscrowKey = bmo.signatureUsingEscrowKey else {
1863 reply(nil, nil, ContainerError.bottleDoesNotContainEscrowKeySignature)
1867 guard let signatureUsingPeerKey = bmo.signatureUsingPeerKey else {
1868 reply(nil, nil, ContainerError.bottleDoesNotContainerPeerKeySignature)
1871 guard let sponsorPeerID = bmo.peerID else {
1872 reply(nil, nil, ContainerError.bottleDoesNotContainPeerID)
1876 //verify bottle signature using peer
1878 guard let sponsorPeer = self.model.peer(withID: sponsorPeerID) else {
1879 os_log("vouchWithBottle: Unable to find peer that created the bottle", log: tplogDebug, type: .default)
1880 reply(nil, nil, ContainerError.bottleCreatingPeerNotFound)
1883 guard let signingKey: _SFECPublicKey = sponsorPeer.permanentInfo.signingPubKey as? _SFECPublicKey else {
1884 os_log("vouchWithBottle: Unable to create a sponsor public key", log: tplogDebug, type: .default)
1885 reply(nil, nil, ContainerError.signatureVerificationFailed)
1889 _ = try BottledPeer.verifyBottleSignature(data: bottledContents, signature: signatureUsingPeerKey, pubKey: signingKey)
1891 os_log("vouchWithBottle: Verification of bottled signature failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1892 reply(nil, nil, ContainerError.failedToCreateBottledPeer)
1896 //create bottled peer
1897 let bottledPeer: BottledPeer
1899 bottledPeer = try BottledPeer(contents: bottledContents,
1901 bottleSalt: bottleSalt,
1902 signatureUsingEscrow: signatureUsingEscrowKey,
1903 signatureUsingPeerKey: signatureUsingPeerKey)
1905 os_log("Creation of Bottled Peer failed with bottle salt: %@,\nAttempting with empty bottle salt", bottleSalt)
1908 bottledPeer = try BottledPeer(contents: bottledContents,
1911 signatureUsingEscrow: signatureUsingEscrowKey,
1912 signatureUsingPeerKey: signatureUsingPeerKey)
1915 os_log("Creation of Bottled Peer failed: %@", log: tplogDebug, type: .default, error as CVarArg)
1916 reply(nil, nil, ContainerError.failedToCreateBottledPeer)
1921 os_log("Have a bottle for peer %@", log: tplogDebug, type: .default, bottledPeer.peerID)
1923 // Extract any TLKs we have been given
1924 extract(tlkShares: tlkShares, peer: bottledPeer.peerKeys)
1926 self.moc.performAndWait {
1927 // I must have an ego identity in order to vouch using bottle
1928 guard let egoPeerID = self.containerMO.egoPeerID else {
1929 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
1930 reply(nil, nil, ContainerError.nonMember)
1933 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
1934 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
1935 reply(nil, nil, ContainerError.nonMember)
1938 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
1939 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
1940 reply(nil, nil, ContainerError.nonMember)
1943 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
1944 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
1945 reply(nil, nil, ContainerError.nonMember)
1948 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
1949 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
1950 reply(nil, nil, ContainerError.nonMember)
1953 let keyFactory = TPECPublicKeyFactory()
1954 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
1955 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
1956 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
1959 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
1960 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
1961 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
1966 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
1967 stableInfo: beneficiaryStableInfo,
1968 withSponsorID: sponsorPeerID,
1969 reason: TPVoucherReason.restore,
1970 signing: bottledPeer.peerKeys.signingKey)
1971 reply(voucher.data, voucher.sig, nil)
1974 os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg)
1975 reply(nil, nil, error)
1984 func vouchWithRecoveryKey(recoveryKey: String,
1986 tlkShares: [CKKSTLKShare],
1987 reply: @escaping (Data?, Data?, Error?) -> Void) {
1988 self.semaphore.wait()
1989 let reply: (Data?, Data?, Error?) -> Void = {
1990 os_log("vouchWithRecoveryKey complete: %@",
1991 log: tplogTrace, type: .info, traceError($2))
1992 self.semaphore.signal()
1996 self.moc.performAndWait {
1997 os_log("beginning a vouchWithRecoveryKey", log: tplogDebug, type: .default)
1999 // I must have an ego identity in order to vouch using bottle
2000 guard let egoPeerID = self.containerMO.egoPeerID else {
2001 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2002 reply(nil, nil, ContainerError.nonMember)
2005 guard let permanentInfo = self.containerMO.egoPeerPermanentInfo else {
2006 os_log("permanentInfo does not exist", log: tplogDebug, type: .default)
2007 reply(nil, nil, ContainerError.nonMember)
2010 guard let permanentInfoSig = self.containerMO.egoPeerPermanentInfoSig else {
2011 os_log("permanentInfoSig does not exist", log: tplogDebug, type: .default)
2012 reply(nil, nil, ContainerError.nonMember)
2015 guard let stableInfo = self.containerMO.egoPeerStableInfo else {
2016 os_log("stableInfo does not exist", log: tplogDebug, type: .default)
2017 reply(nil, nil, ContainerError.nonMember)
2020 guard let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2021 os_log("stableInfoSig does not exist", log: tplogDebug, type: .default)
2022 reply(nil, nil, ContainerError.nonMember)
2025 let keyFactory = TPECPublicKeyFactory()
2026 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: permanentInfo, sig: permanentInfoSig, keyFactory: keyFactory) else {
2027 os_log("Invalid permenent info or signature; can't vouch for them", log: tplogDebug, type: .default)
2028 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2031 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2032 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2033 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2037 //create recovery key set
2038 var recoveryKeys: RecoveryKey
2040 recoveryKeys = try RecoveryKey(recoveryKeyString: recoveryKey, recoverySalt: salt)
2042 os_log("failed to create recovery keys: %@", log: tplogDebug, type: .default, error as CVarArg)
2043 reply(nil, nil, ContainerError.failedToCreateRecoveryKey)
2047 extract(tlkShares: tlkShares, peer: recoveryKeys.peerKeys)
2049 let signingPublicKey: Data = recoveryKeys.peerKeys.signingKey.publicKey.keyData
2050 let encryptionPublicKey: Data = recoveryKeys.peerKeys.encryptionKey.publicKey.keyData
2052 os_log("vouchWithRecoveryKey signingPubKey: %@", log: tplogDebug, type: .debug, signingPublicKey.base64EncodedString())
2053 os_log("vouchWithRecoveryKey encryptionPubKey: %@", log: tplogDebug, type: .debug, encryptionPublicKey.base64EncodedString())
2055 guard self.model.isRecoveryKeyEnrolled() else {
2056 os_log("Recovery Key is not enrolled", log: tplogDebug, type: .default)
2057 reply(nil, nil, ContainerError.recoveryKeysNotEnrolled)
2061 //find matching peer containing recovery keys
2062 guard let sponsorPeerID = self.model.peerIDThatTrustsRecoveryKeys(TPRecoveryKeyPair(signingSPKI: signingPublicKey, encryptionSPKI: encryptionPublicKey)) else {
2063 os_log("Untrusted recovery key set", log: tplogDebug, type: .default)
2064 reply(nil, nil, ContainerError.untrustedRecoveryKeys)
2069 let voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2070 stableInfo: beneficiaryStableInfo,
2071 withSponsorID: sponsorPeerID,
2072 reason: TPVoucherReason.recoveryKey,
2073 signing: recoveryKeys.peerKeys.signingKey)
2074 reply(voucher.data, voucher.sig, nil)
2077 os_log("Error creating voucher using recovery key set: %@", log: tplogDebug, type: .default, error as CVarArg)
2078 reply(nil, nil, error)
2084 func vouch(peerID: String,
2085 permanentInfo: Data,
2086 permanentInfoSig: Data,
2088 stableInfoSig: Data,
2089 ckksKeys: [CKKSKeychainBackedKeySet],
2090 reply: @escaping (Data?, Data?, Error?) -> Void) {
2091 self.semaphore.wait()
2092 let reply: (Data?, Data?, Error?) -> Void = {
2093 os_log("vouch complete: %@", log: tplogTrace, type: .info, traceError($2))
2094 self.semaphore.signal()
2098 self.moc.performAndWait {
2099 // I must have an ego identity in order to vouch for someone else.
2100 guard let egoPeerID = self.containerMO.egoPeerID,
2101 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2102 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig else {
2103 os_log("As a nonmember, can't vouch for someone else", log: tplogDebug, type: .default)
2104 reply(nil, nil, ContainerError.nonMember)
2108 let keyFactory = TPECPublicKeyFactory()
2110 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2111 reply(nil, nil, ContainerError.invalidPermanentInfoOrSig)
2115 guard let beneficiaryPermanentInfo = TPPeerPermanentInfo(peerID: peerID, 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)
2121 guard let beneficiaryStableInfo = TPPeerStableInfo(data: stableInfo, sig: stableInfoSig) else {
2122 os_log("Invalid stableinfo or signature; van't vouch for them", log: tplogDebug, type: .default)
2123 reply(nil, nil, ContainerError.invalidStableInfoOrSig)
2127 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2128 guard let egoPeerKeys = egoPeerKeys else {
2129 os_log("Don't have my own keys: can't vouch for %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, (error as CVarArg?) ?? "no error")
2130 reply(nil, nil, error)
2133 self.moc.performAndWait {
2134 let voucher: TPVoucher
2136 voucher = try self.model.createVoucher(forCandidate: beneficiaryPermanentInfo,
2137 stableInfo: beneficiaryStableInfo,
2138 withSponsorID: egoPeerID,
2139 reason: TPVoucherReason.secureChannel,
2140 signing: egoPeerKeys.signingKey)
2143 os_log("Error creating voucher: %@", log: tplogDebug, type: .default, error as CVarArg)
2144 reply(nil, nil, error)
2148 // And generate and upload any tlkShares
2149 // Note that this might not be the whole list: <rdar://problem/47899980> Octagon: Limited Peers
2150 let tlkShares: [TLKShare]
2152 // Note: we only want to send up TLKs for uploaded ckks zones
2153 let ckksTLKs = ckksKeys.filter { !$0.newUpload }.map { $0.tlk }
2155 tlkShares = try makeTLKShares(ckksTLKs: ckksTLKs,
2156 asPeer: egoPeerKeys,
2157 toPeer: beneficiaryPermanentInfo,
2158 epoch: Int(selfPermanentInfo.epoch))
2160 os_log("Unable to make TLKShares for beneficiary %@: %@", log: tplogDebug, type: .default, beneficiaryPermanentInfo, error as CVarArg)
2161 reply(nil, nil, error)
2165 guard !tlkShares.isEmpty else {
2166 os_log("No TLKShares to upload for new peer, returning voucher", log: tplogDebug, type: .default)
2167 reply(voucher.data, voucher.sig, nil)
2171 self.cuttlefish.updateTrust(changeToken: self.containerMO.changeToken ?? "",
2173 stableInfoAndSig: nil,
2174 dynamicInfoAndSig: nil,
2175 tlkShares: tlkShares,
2176 viewKeys: []) { response, error in
2177 guard let response = response, error == nil else {
2178 os_log("Unable to upload new tlkshares: %@", log: tplogDebug, type: .default, error as CVarArg? ?? "no error")
2179 reply(voucher.data, voucher.sig, error ?? ContainerError.cloudkitResponseMissing)
2183 let newKeyRecords = response.zoneKeyHierarchyRecords.map(CKRecord.init)
2184 os_log("Uploaded new tlkshares: %@", log: tplogDebug, type: .default, newKeyRecords)
2185 // We don't need to save these; CKKS will refetch them as needed
2187 reply(voucher.data, voucher.sig, nil)
2194 func departByDistrustingSelf(reply: @escaping (Error?) -> Void) {
2195 self.semaphore.wait()
2196 let reply: (Error?) -> Void = {
2197 os_log("departByDistrustingSelf complete: %@", log: tplogTrace, type: .info, traceError($0))
2198 self.semaphore.signal()
2202 self.moc.performAndWait {
2203 guard let egoPeerID = self.containerMO.egoPeerID else {
2204 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2205 reply(ContainerError.noPreparedIdentity)
2209 self.onqueueDistrust(peerIDs: [egoPeerID], reply: reply)
2213 func distrust(peerIDs: Set<String>,
2214 reply: @escaping (Error?) -> Void) {
2215 self.semaphore.wait()
2216 let reply: (Error?) -> Void = {
2217 os_log("distrust complete: %@", log: tplogTrace, type: .info, traceError($0))
2218 self.semaphore.signal()
2222 self.moc.performAndWait {
2223 guard let egoPeerID = self.containerMO.egoPeerID else {
2224 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2225 reply(ContainerError.noPreparedIdentity)
2229 guard !peerIDs.contains(egoPeerID) else {
2230 os_log("Self-distrust via peerID not allowed", log: tplogDebug, type: .default)
2231 reply(ContainerError.invalidPeerID)
2235 self.onqueueDistrust(peerIDs: peerIDs, reply: reply)
2239 func onqueueDistrust(peerIDs: Set<String>,
2240 reply: @escaping (Error?) -> Void) {
2242 guard let egoPeerID = self.containerMO.egoPeerID else {
2243 os_log("No dynamic info for self?", log: tplogDebug, type: .default)
2244 reply(ContainerError.noPreparedIdentity)
2248 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
2249 guard let signingKeyPair = signingKeyPair else {
2250 os_log("No longer have signing key pair; can't sign distrust: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2255 self.moc.performAndWait {
2256 let dynamicInfo: TPPeerDynamicInfo
2258 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
2260 removingPeerIDs: Array(peerIDs),
2261 preapprovedKeys: nil,
2262 signing: signingKeyPair,
2263 currentMachineIDs: self.onqueueCurrentMIDList())
2266 os_log("Error preparing dynamic info: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "nil")
2271 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
2272 os_log("attempting distrust for %@ with: %@", log: tplogDebug, type: .default, peerIDs, dynamicInfo)
2274 let request = UpdateTrustRequest.with {
2275 $0.changeToken = self.containerMO.changeToken ?? ""
2276 $0.peerID = egoPeerID
2277 $0.dynamicInfoAndSig = signedDynamicInfo
2279 self.cuttlefish.updateTrust(request) { response, error in
2280 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2281 guard let response = response, error == nil else {
2282 os_log("updateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2283 reply(error ?? ContainerError.cloudkitResponseMissing)
2288 try self.persist(changes: response.changes)
2289 os_log("distrust succeeded", log: tplogDebug, type: .default)
2292 os_log("distrust handling failed: %@", log: tplogDebug, type: .default, (error as CVarArg))
2300 func fetchEscrowContents(reply: @escaping (Data?, String?, Data?, Error?) -> Void) {
2301 self.semaphore.wait()
2302 let reply: (Data?, String?, Data?, Error?) -> Void = {
2303 os_log("fetchEscrowContents complete: %@", log: tplogTrace, type: .info, traceError($3))
2304 self.semaphore.signal()
2305 reply($0, $1, $2, $3)
2307 os_log("beginning a fetchEscrowContents", log: tplogDebug, type: .default)
2309 self.moc.performAndWait {
2310 guard let egoPeerID = self.containerMO.egoPeerID else {
2311 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2312 reply(nil, nil, nil, ContainerError.noPreparedIdentity)
2316 guard let bottles = self.containerMO.bottles as? Set<BottleMO> else {
2317 os_log("fetchEscrowContents failed", log: tplogDebug, type: .default)
2318 reply(nil, nil, nil, ContainerError.noBottleForPeer)
2322 var bmoSet = bottles.filter { $0.peerID == egoPeerID }
2323 let bmo = bmoSet.removeFirst()
2324 let bottleID = bmo.bottleID
2328 guard let loaded = try loadSecret(label: egoPeerID) else {
2329 os_log("fetchEscrowContents failed to load entropy", log: tplogDebug, type: .default)
2330 reply(nil, nil, nil, ContainerError.failedToFetchEscrowContents)
2335 os_log("fetchEscrowContents failed to load entropy: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2336 reply(nil, nil, nil, error)
2340 guard let signingPublicKey = bmo.escrowedSigningSPKI else {
2341 os_log("fetchEscrowContents no escrow signing spki", log: tplogDebug, type: .default)
2342 reply(nil, nil, nil, ContainerError.bottleDoesNotContainerEscrowKeySPKI)
2345 reply(entropy, bottleID, signingPublicKey, nil)
2349 func fetchViableBottles(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2350 self.semaphore.wait()
2351 let reply: ([String]?, [String]?, Error?) -> Void = {
2352 os_log("fetchViableBottles complete: %@", log: tplogTrace, type: .info, traceError($2))
2353 self.semaphore.signal()
2357 self.fetchViableBottlesWithSemaphore(reply: reply)
2360 func onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: TPCachedViableBottles) -> Bool {
2361 guard let egoPeerID = self.containerMO.egoPeerID else {
2362 os_log("bottleForEgoPeer: No identity.", log: tplogDebug, type: .default)
2365 guard let bottles: Set<BottleMO> = self.containerMO.bottles as? Set<BottleMO> else {
2366 os_log("bottleForEgoPeer: No Bottles.", log: tplogDebug, type: .default)
2369 var matchesCached: Bool = false
2370 for bottle in bottles {
2371 guard let bottleID: String = bottle.bottleID else {
2374 if bottle.peerID == egoPeerID && (cachedBottles.viableBottles.contains(bottleID) || cachedBottles.partialBottles.contains(bottleID)) {
2375 matchesCached = true
2379 return matchesCached
2382 func fetchViableBottlesWithSemaphore(reply: @escaping ([String]?, [String]?, Error?) -> Void) {
2383 os_log("beginning a fetchViableBottles", log: tplogDebug, type: .default)
2385 let cachedBottles: TPCachedViableBottles = self.model.currentCachedViableBottlesSet()
2386 self.moc.performAndWait {
2387 if self.onqueueCachedBottlesContainEgoPeerBottle(cachedBottles: cachedBottles)
2388 && (cachedBottles.viableBottles.count > 0 || cachedBottles.partialBottles.count > 0) {
2389 os_log("returning from fetchViableBottles, using cached bottles", log: tplogDebug, type: .default)
2390 reply(cachedBottles.viableBottles, cachedBottles.partialBottles, nil)
2394 self.cuttlefish.fetchViableBottles { response, error in
2395 guard error == nil else {
2396 os_log("fetchViableBottles failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2397 reply(nil, nil, error)
2401 self.moc.performAndWait {
2403 guard let escrowPairs = response?.viableBottles else {
2404 os_log("fetchViableBottles returned no viable bottles: %@", log: tplogDebug, type: .default)
2409 var partialPairs: [EscrowPair] = []
2410 if let partial = response?.partialBottles {
2411 partialPairs = partial
2413 os_log("fetchViableBottles returned no partially viable bottles, but that's ok: %@", log: tplogDebug, type: .default)
2416 let viableBottleIDs = escrowPairs.compactMap { $0.bottle.bottleID }
2417 os_log("fetchViableBottles returned viable bottles: %@", log: tplogDebug, type: .default, viableBottleIDs)
2419 let partialBottleIDs = partialPairs.compactMap { $0.bottle.bottleID }
2420 os_log("fetchViableBottles returned partial bottles: %@", log: tplogDebug, type: .default, partialBottleIDs)
2422 escrowPairs.forEach { pair in
2423 let bottle = pair.bottle
2425 // Save this bottle only if we don't already have it
2426 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2427 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2428 existing.peerID == bottle.peerID &&
2429 existing.bottleID == bottle.bottleID &&
2430 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2431 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2432 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2433 existing.contents == bottle.contents
2435 if !matchingBottles.isEmpty {
2436 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2441 let bmo = BottleMO(context: self.moc)
2442 bmo.peerID = bottle.peerID
2443 bmo.bottleID = bottle.bottleID
2444 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2445 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2446 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2447 bmo.contents = bottle.contents
2449 os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo)
2450 self.containerMO.addToBottles(bmo)
2453 partialPairs.forEach { pair in
2454 let bottle = pair.bottle
2456 // Save this bottle only if we don't already have it
2457 if let existingBottles = self.containerMO.bottles as? Set<BottleMO> {
2458 let matchingBottles: Set<BottleMO> = existingBottles.filter { existing in
2459 existing.peerID == bottle.peerID &&
2460 existing.bottleID == bottle.bottleID &&
2461 existing.escrowedSigningSPKI == bottle.escrowedSigningSpki &&
2462 existing.signatureUsingEscrowKey == bottle.signatureUsingEscrowKey &&
2463 existing.signatureUsingPeerKey == bottle.signatureUsingPeerKey &&
2464 existing.contents == bottle.contents
2466 if !matchingBottles.isEmpty {
2467 os_log("fetchViableBottles already knows about bottle", log: tplogDebug, type: .default, bottle.bottleID)
2472 let bmo = BottleMO(context: self.moc)
2473 bmo.peerID = bottle.peerID
2474 bmo.bottleID = bottle.bottleID
2475 bmo.escrowedSigningSPKI = bottle.escrowedSigningSpki
2476 bmo.signatureUsingEscrowKey = bottle.signatureUsingEscrowKey
2477 bmo.signatureUsingPeerKey = bottle.signatureUsingPeerKey
2478 bmo.contents = bottle.contents
2480 os_log("fetchViableBottles saving new bottle: %@", log: tplogDebug, type: .default, bmo)
2481 self.containerMO.addToBottles(bmo)
2486 os_log("fetchViableBottles saved bottles", log: tplogDebug, type: .default)
2487 let cached = TPCachedViableBottles(viableBottles: viableBottleIDs, partialBottles: partialBottleIDs)
2488 self.model.setViableBottles(cached)
2489 reply(viableBottleIDs, partialBottleIDs, nil)
2491 os_log("fetchViableBottles unable to save bottles: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2492 reply(nil, nil, error)
2500 func fetchPolicy(reply: @escaping (TPPolicy?, Error?) -> Void) {
2501 self.semaphore.wait()
2502 let reply: (TPPolicy?, Error?) -> Void = {
2503 os_log("fetchPolicy complete: %@", log: tplogTrace, type: .info, traceError($1))
2504 self.semaphore.signal()
2508 self.moc.performAndWait {
2509 var keys: [NSNumber: String] = [:]
2511 guard let stableInfoData = self.containerMO.egoPeerStableInfo,
2512 let stableInfoSig = self.containerMO.egoPeerStableInfoSig else {
2513 os_log("fetchPolicy failed to find ego peer stableinfodata/sig", log: tplogDebug, type: .error)
2514 reply(nil, ContainerError.noPreparedIdentity)
2517 guard let stableInfo = TPPeerStableInfo(data: stableInfoData, sig: stableInfoSig) else {
2518 os_log("fetchPolicy failed to create TPPeerStableInfo", log: tplogDebug, type: .error)
2519 reply(nil, ContainerError.invalidStableInfoOrSig)
2523 let policyVersionCounter = stableInfo.policyVersion
2524 let policyVersion = NSNumber(value: policyVersionCounter)
2525 keys[policyVersion] = stableInfo.policyHash
2527 if let policyDocument = self.model.policy(withVersion: policyVersionCounter) {
2528 os_log("fetchPolicy: have a local version of policy %@: %@", log: tplogDebug, type: .default, policyVersion, policyDocument)
2530 let policy = try policyDocument.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2534 os_log("TPPolicyDocument failed: %@", log: tplogDebug, type: .default, error as CVarArg)
2540 self.fetchPolicyDocuments(keys: keys) { result, error in
2541 guard error == nil else {
2545 guard let result = result else {
2546 os_log("fetchPolicy: nil policies returned")
2547 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2550 guard result.count == 1 else {
2551 os_log("fetchPolicy: wrong length returned")
2552 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2555 guard let r = result[policyVersion] else {
2556 os_log("fetchPolicy: version not found")
2557 reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value))
2560 guard let data = r[1].data(using: .utf8) else {
2561 os_log("fetchPolicy: failed to convert data")
2562 reply(nil, ContainerError.unknownPolicyVersion(policyVersion.uint64Value))
2565 guard let pd = TPPolicyDocument.policyDoc(withHash: r[0], data: data) else {
2566 os_log("fetchPolicy: pd is nil")
2567 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2571 let policy = try pd.policy(withSecrets: stableInfo.policySecrets, decrypter: Decrypter())
2574 os_log("TPPolicyDocument: %@", log: tplogDebug, type: .default, error as CVarArg)
2581 // All-or-nothing: return an error in case full list cannot be returned.
2582 // Completion handler data format: [version : [hash, data]]
2583 func fetchPolicyDocuments(keys: [NSNumber: String],
2584 reply: @escaping ([NSNumber: [String]]?, Error?) -> Void) {
2585 self.semaphore.wait()
2586 let reply: ([NSNumber: [String]]?, Error?) -> Void = {
2587 os_log("fetchPolicyDocuments complete: %@", log: tplogTrace, type: .info, traceError($1))
2588 self.semaphore.signal()
2593 var docs: [NSNumber: [String]] = [:]
2595 self.moc.performAndWait {
2596 for (version, hash) in keys {
2597 if let policydoc = try? self.getPolicyDoc(version.uint64Value), policydoc.policyHash == hash {
2598 docs[version] = [policydoc.policyHash, policydoc.protobuf.base64EncodedString()]
2609 let request = FetchPolicyDocumentsRequest.with {
2610 $0.keys = keys.map { key, value in
2611 PolicyDocumentKey.with { $0.version = key.uint64Value; $0.hash = value }}
2614 self.cuttlefish.fetchPolicyDocuments(request) { response, error in
2615 os_log("FetchPolicyDocuments(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2616 guard let response = response, error == nil else {
2617 os_log("FetchPolicyDocuments failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2618 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
2622 self.moc.performAndWait {
2623 for mapEntry in response.entries {
2624 // TODO: validate the policy's signature
2626 guard let doc = TPPolicyDocument.policyDoc(withHash: mapEntry.key.hash, data: mapEntry.value) else {
2627 os_log("Can't make policy document with hash %@ and data %@",
2628 log: tplogDebug, type: .default, mapEntry.key.hash, mapEntry.value.base64EncodedString())
2629 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2633 guard let hash = keys[NSNumber(value: doc.policyVersion)], hash == doc.policyHash else {
2634 os_log("Requested hash %@ does not match fetched hash %@", log: tplogDebug, type: .default,
2635 keys[NSNumber(value: doc.policyVersion)] ?? "<nil>", doc.policyHash)
2636 reply(nil, ContainerError.policyDocumentDoesNotValidate)
2639 keys[NSNumber(value: doc.policyVersion)] = nil // Server responses should be unique, let's enforce
2640 docs[NSNumber(value: doc.policyVersion)] = [doc.policyHash, doc.protobuf.base64EncodedString()]
2641 self.model.register(doc)
2645 try self.moc.save() // if this fails callers might make bad data assumptions
2652 let (unknownVersion, _) = keys.first!
2653 reply(nil, ContainerError.unknownPolicyVersion(unknownVersion.uint64Value))
2662 // Must be on moc queue to call this.
2663 // Caller is responsible for saving the moc afterwards.
2665 private func registerPeerMO(permanentInfo: TPPeerPermanentInfo,
2666 stableInfo: TPPeerStableInfo? = nil,
2667 dynamicInfo: TPPeerDynamicInfo? = nil,
2668 vouchers: [TPVoucher]? = nil,
2669 isEgoPeer: Bool = false) throws -> PeerMO {
2670 let peerID = permanentInfo.peerID
2672 let peer = PeerMO(context: self.moc)
2673 peer.peerID = peerID
2674 peer.permanentInfo = permanentInfo.data
2675 peer.permanentInfoSig = permanentInfo.sig
2676 peer.stableInfo = stableInfo?.data
2677 peer.stableInfoSig = stableInfo?.sig
2678 peer.dynamicInfo = dynamicInfo?.data
2679 peer.dynamicInfoSig = dynamicInfo?.sig
2680 peer.isEgoPeer = isEgoPeer
2681 self.containerMO.addToPeers(peer)
2683 self.model.registerPeer(with: permanentInfo)
2684 if let stableInfo = stableInfo {
2685 try self.model.update(stableInfo, forPeerWithID: peerID)
2687 if let dynamicInfo = dynamicInfo {
2688 try self.model.update(dynamicInfo, forPeerWithID: peerID)
2690 vouchers?.forEach { voucher in
2691 self.model.register(voucher)
2692 let voucherMO = VoucherMO(context: self.moc)
2693 voucherMO.voucherInfo = voucher.data
2694 voucherMO.voucherInfoSig = voucher.sig
2695 peer.addToVouchers(voucherMO)
2700 /* Returns any new CKKS keys that need uploading, as well as any TLKShares necessary for those keys */
2701 func makeSharesForNewKeySets(ckksKeys: [CKKSKeychainBackedKeySet],
2702 tlkShares: [CKKSTLKShare],
2703 egoPeerKeys: OctagonSelfPeerKeys,
2704 egoPeerDynamicInfo: TPPeerDynamicInfo,
2705 epoch: Int) throws -> ([ViewKeys], [TLKShare]) {
2706 let newCKKSKeys = ckksKeys.filter { $0.newUpload }
2707 let newViewKeys: [ViewKeys] = newCKKSKeys.map(ViewKeys.convert)
2709 let octagonSelfShares = try makeTLKShares(ckksTLKs: ckksKeys.map { $0.tlk },
2710 asPeer: egoPeerKeys,
2711 toPeer: egoPeerKeys,
2713 let extraShares = tlkShares.map { TLKShare.convert(ckksTLKShare: $0) }
2715 var peerShares: [TLKShare] = []
2717 for keyset in newCKKSKeys {
2719 let peerIDsWithAccess = try self.model.getPeerIDsTrustedByPeer(with: egoPeerDynamicInfo,
2720 toAccessView: keyset.tlk.zoneID.zoneName)
2721 os_log("Planning to share %@ with peers %@", log: tplogDebug, type: .default, String(describing: keyset.tlk), peerIDsWithAccess)
2723 let peers = peerIDsWithAccess.compactMap { self.model.peer(withID: $0) }
2724 let viewPeerShares = try peers.map { receivingPeer in
2725 TLKShare.convert(ckksTLKShare: try CKKSTLKShare(keyset.tlk,
2727 to: receivingPeer.permanentInfo,
2732 peerShares = peerShares + viewPeerShares
2735 os_log("Unable to create TLKShares for keyset %@: %@", log: tplogDebug, type: .default, String(describing: keyset), error as CVarArg)
2739 return (newViewKeys, octagonSelfShares + peerShares + extraShares)
2742 func onqueuePreparePeerForJoining(egoPeerID: String,
2743 peerPermanentInfo: TPPeerPermanentInfo,
2744 stableInfo: TPPeerStableInfo,
2746 preapprovedKeys: [Data]?,
2747 vouchers: [SignedVoucher],
2748 egoPeerKeys: OctagonSelfPeerKeys) throws -> (Peer, TPPeerDynamicInfo) {
2749 let dynamicInfo = try self.model.dynamicInfo(forJoiningPeerID: egoPeerID,
2750 peerPermanentInfo: peerPermanentInfo,
2751 peerStableInfo: stableInfo,
2752 sponsorID: sponsorID,
2753 preapprovedKeys: preapprovedKeys,
2754 signing: egoPeerKeys.signingKey,
2755 currentMachineIDs: self.onqueueCurrentMIDList())
2757 let newStableInfo = try self.createNewStableInfoIfNeeded(stableChanges: nil,
2758 egoPeerID: egoPeerID,
2759 dynamicInfo: dynamicInfo,
2760 signingKeyPair: egoPeerKeys.signingKey)
2762 let peer = Peer.with {
2763 $0.peerID = egoPeerID
2764 $0.permanentInfoAndSig = SignedPeerPermanentInfo(peerPermanentInfo)
2765 $0.stableInfoAndSig = SignedPeerStableInfo(newStableInfo ?? stableInfo)
2766 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
2767 $0.vouchers = vouchers
2770 return (peer, dynamicInfo)
2773 func join(voucherData: Data,
2775 ckksKeys: [CKKSKeychainBackedKeySet],
2776 tlkShares: [CKKSTLKShare],
2777 preapprovedKeys: [Data]?,
2778 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
2779 self.semaphore.wait()
2780 let reply: (String?, [CKRecord], Error?) -> Void = {
2781 os_log("join complete: %@", log: tplogTrace, type: .info, traceError($2))
2782 self.semaphore.signal()
2786 self.fetchAndPersistChanges { error in
2787 guard error == nil else {
2788 reply(nil, [], error)
2791 self.moc.performAndWait {
2792 guard let voucher = TPVoucher(infoWith: voucherData, sig: voucherSig) else {
2793 reply(nil, [], ContainerError.invalidVoucherOrSig)
2796 guard let sponsor = self.model.peer(withID: voucher.sponsorID) else {
2797 reply(nil, [], ContainerError.sponsorNotRegistered(voucher.sponsorID))
2801 // Fetch ego peer identity from local storage.
2802 guard let egoPeerID = self.containerMO.egoPeerID,
2803 let egoPermData = self.containerMO.egoPeerPermanentInfo,
2804 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
2805 let egoStableData = self.containerMO.egoPeerStableInfo,
2806 let egoStableSig = self.containerMO.egoPeerStableInfoSig
2808 reply(nil, [], ContainerError.noPreparedIdentity)
2812 let keyFactory = TPECPublicKeyFactory()
2813 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
2814 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
2817 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
2818 reply(nil, [], ContainerError.invalidStableInfoOrSig)
2821 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
2822 os_log("join: self machineID %@ not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
2823 self.onqueueTTRUntrusted()
2824 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
2828 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
2829 guard let egoPeerKeys = egoPeerKeys else {
2830 os_log("Don't have my own peer keys; can't join: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
2831 reply(nil, [], error)
2834 self.moc.performAndWait {
2836 let newDynamicInfo: TPPeerDynamicInfo
2838 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
2839 peerPermanentInfo: selfPermanentInfo,
2840 stableInfo: selfStableInfo,
2841 sponsorID: sponsor.peerID,
2842 preapprovedKeys: preapprovedKeys,
2843 vouchers: [SignedVoucher.with {
2844 $0.voucher = voucher.data
2845 $0.sig = voucher.sig
2847 egoPeerKeys: egoPeerKeys)
2849 os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg)
2850 reply(nil, [], error)
2854 let allTLKShares: [TLKShare]
2855 let viewKeys: [ViewKeys]
2857 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
2858 tlkShares: tlkShares,
2859 egoPeerKeys: egoPeerKeys,
2860 egoPeerDynamicInfo: newDynamicInfo,
2861 epoch: Int(selfPermanentInfo.epoch))
2863 os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg)
2864 reply(nil, [], error)
2869 try self.model.checkIntroduction(forCandidate: selfPermanentInfo,
2870 stableInfo: peer.stableInfoAndSig.toStableInfo(),
2871 withSponsorID: sponsor.peerID)
2873 os_log("Error checking introduction: %@", log: tplogDebug, type: .default, error as CVarArg)
2874 reply(nil, [], error)
2880 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
2882 reply(nil, [], error)
2886 os_log("Beginning join for peer %@", log: tplogDebug, type: .default, egoPeerID)
2887 os_log("Join permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
2888 os_log("Join permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
2889 os_log("Join stableInfo: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.peerStableInfo.base64EncodedString())
2890 os_log("Join stableInfoSig: %@", log: tplogDebug, type: .debug, peer.stableInfoAndSig.sig.base64EncodedString())
2891 os_log("Join dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
2892 os_log("Join dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
2894 os_log("Join vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
2895 os_log("Join voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
2897 os_log("Uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
2900 os_log("Join peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
2902 os_log("Join unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
2905 let changeToken = self.containerMO.changeToken ?? ""
2906 let request = JoinWithVoucherRequest.with {
2907 $0.changeToken = changeToken
2910 $0.tlkShares = allTLKShares
2911 $0.viewKeys = viewKeys
2913 self.cuttlefish.joinWithVoucher(request) { response, error in
2914 os_log("JoinWithVoucher(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
2915 guard let response = response, error == nil else {
2916 os_log("joinWithVoucher failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
2917 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
2921 self.moc.performAndWait {
2923 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
2924 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
2925 try self.onQueuePersist(changes: response.changes)
2926 os_log("JoinWithVoucher succeeded", log: tplogDebug)
2928 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
2929 reply(egoPeerID, keyHierarchyRecords, nil)
2931 os_log("JoinWithVoucher failed: %@", log: tplogDebug, String(describing: error))
2932 reply(nil, [], error)
2942 func requestHealthCheck(requiresEscrowCheck: Bool, reply: @escaping (Bool, Bool, Bool, Error?) -> Void) {
2943 self.semaphore.wait()
2944 let reply: (Bool, Bool, Bool, Error?) -> Void = {
2945 os_log("health check complete: %@", log: tplogTrace, type: .info, traceError($3))
2946 self.semaphore.signal()
2947 reply($0, $1, $2, $3)
2950 os_log("requestHealthCheck requiring escrow check: %d", log: tplogDebug, type: .default, requiresEscrowCheck)
2952 self.moc.performAndWait {
2953 guard let egoPeerID = self.containerMO.egoPeerID else {
2954 // No identity, nothing to do
2955 os_log("requestHealthCheck: No identity.", log: tplogDebug, type: .default)
2956 reply(false, false, false, ContainerError.noPreparedIdentity)
2959 let request = GetRepairActionRequest.with {
2960 $0.peerID = egoPeerID
2961 $0.requiresEscrowCheck = requiresEscrowCheck
2964 self.cuttlefish.getRepairAction(request) { response, error in
2965 guard error == nil else {
2966 reply(false, false, false, error)
2969 guard let action = response?.repairAction else {
2970 os_log("repair response is empty, returning false: %@", log: tplogDebug, type: .default)
2971 reply(false, false, false, nil)
2974 var postRepairAccount: Bool = false
2975 var postRepairEscrow: Bool = false
2976 var resetOctagon: Bool = false
2981 case .postRepairAccount:
2982 postRepairAccount = true
2984 case .postRepairEscrow:
2985 postRepairEscrow = true
2993 reply(postRepairAccount, postRepairEscrow, resetOctagon, nil)
2998 func preflightPreapprovedJoin(reply: @escaping (Bool, Error?) -> Void) {
2999 self.semaphore.wait()
3000 let reply: (Bool, Error?) -> Void = {
3001 os_log("preflightPreapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($1))
3002 self.semaphore.signal()
3006 self.fetchAndPersistChanges { error in
3007 guard error == nil else {
3008 os_log("preflightPreapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3013 // We explicitly ignore the machine ID list here; we're only interested in the peer states: do they preapprove us?
3015 guard !self.model.allPeerIDs().isEmpty else {
3016 // If, after fetch and handle changes, there's no peers, then we can likely establish.
3021 guard let egoPeerID = self.containerMO.egoPeerID,
3022 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3023 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3025 os_log("preflightPreapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3026 reply(false, ContainerError.noPreparedIdentity)
3030 let keyFactory = TPECPublicKeyFactory()
3031 guard let egoPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3032 os_log("preflightPreapprovedJoin: invalid permanent info", log: tplogDebug, type: .debug)
3033 reply(false, ContainerError.invalidPermanentInfoOrSig)
3037 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPermanentInfo.signingPubKey.spki()) else {
3038 os_log("preflightPreapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3039 reply(false, ContainerError.noPeersPreapprovePreparedIdentity)
3047 func preapprovedJoin(ckksKeys: [CKKSKeychainBackedKeySet],
3048 tlkShares: [CKKSTLKShare],
3049 preapprovedKeys: [Data]?,
3050 reply: @escaping (String?, [CKRecord], Error?) -> Void) {
3051 self.semaphore.wait()
3052 let reply: (String?, [CKRecord], Error?) -> Void = {
3053 os_log("preapprovedJoin complete: %@", log: tplogTrace, type: .info, traceError($2))
3054 self.semaphore.signal()
3058 self.fetchAndPersistChangesIfNeeded { error in
3059 guard error == nil else {
3060 os_log("preapprovedJoin unable to fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "")
3061 reply(nil, [], error)
3064 self.moc.performAndWait {
3065 // If, after fetch and handle changes, there's no peers, then fire off an establish
3066 // Note that if the establish fails, retrying this call might work.
3067 // That's up to the caller.
3068 if self.model.allPeerIDs().isEmpty {
3069 os_log("preapprovedJoin but no existing peers, attempting establish", log: tplogDebug, type: .debug)
3070 self.onqueueEstablish(ckksKeys: ckksKeys,
3071 tlkShares: tlkShares,
3072 preapprovedKeys: preapprovedKeys,
3077 // Fetch ego peer identity from local storage.
3078 guard let egoPeerID = self.containerMO.egoPeerID,
3079 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3080 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig,
3081 let egoStableData = self.containerMO.egoPeerStableInfo,
3082 let egoStableSig = self.containerMO.egoPeerStableInfoSig
3084 os_log("preapprovedJoin: no prepared identity", log: tplogDebug, type: .debug)
3085 reply(nil, [], ContainerError.noPreparedIdentity)
3089 let keyFactory = TPECPublicKeyFactory()
3090 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: keyFactory) else {
3091 reply(nil, [], ContainerError.invalidPermanentInfoOrSig)
3094 guard let selfStableInfo = TPPeerStableInfo(data: egoStableData, sig: egoStableSig) else {
3095 reply(nil, [], ContainerError.invalidStableInfoOrSig)
3099 guard self.onqueueMachineIDAllowedByIDMS(machineID: selfPermanentInfo.machineID) else {
3100 os_log("preapprovedJoin: self machineID %@ (me) not on list", log: tplogDebug, type: .debug, selfPermanentInfo.machineID)
3101 self.onqueueTTRUntrusted()
3102 reply(nil, [], ContainerError.preparedIdentityNotOnAllowedList(selfPermanentInfo.machineID))
3105 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3106 guard let egoPeerKeys = egoPeerKeys else {
3107 os_log("preapprovedJoin: Don't have my own keys: can't join", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3108 reply(nil, [], error)
3112 guard self.model.hasPotentiallyTrustedPeerPreapprovingKey(egoPeerKeys.signingKey.publicKey().spki()) else {
3113 os_log("preapprovedJoin: no peers preapprove our key", log: tplogDebug, type: .debug)
3114 reply(nil, [], ContainerError.noPeersPreapprovePreparedIdentity)
3118 self.moc.performAndWait {
3121 let newDynamicInfo: TPPeerDynamicInfo
3123 (peer, newDynamicInfo) = try self.onqueuePreparePeerForJoining(egoPeerID: egoPeerID,
3124 peerPermanentInfo: selfPermanentInfo,
3125 stableInfo: selfStableInfo,
3127 preapprovedKeys: preapprovedKeys,
3129 egoPeerKeys: egoPeerKeys)
3131 os_log("Unable to create peer for joining: %@", log: tplogDebug, type: .default, error as CVarArg)
3132 reply(nil, [], error)
3136 let allTLKShares: [TLKShare]
3137 let viewKeys: [ViewKeys]
3139 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3140 tlkShares: tlkShares,
3141 egoPeerKeys: egoPeerKeys,
3142 egoPeerDynamicInfo: newDynamicInfo,
3143 epoch: Int(selfPermanentInfo.epoch))
3145 os_log("Unable to process keys before joining: %@", log: tplogDebug, type: .default, error as CVarArg)
3146 reply(nil, [], error)
3152 bottle = try self.assembleBottle(egoPeerID: egoPeerID)
3154 reply(nil, [], error)
3158 os_log("Beginning preapprovedJoin for peer %@", log: tplogDebug, type: .default, egoPeerID)
3159 os_log("preapprovedJoin permanentInfo: %@", log: tplogDebug, type: .debug, egoPermData.base64EncodedString())
3160 os_log("preapprovedJoin permanentInfoSig: %@", log: tplogDebug, type: .debug, egoPermSig.base64EncodedString())
3161 os_log("preapprovedJoin stableInfo: %@", log: tplogDebug, type: .debug, egoStableData.base64EncodedString())
3162 os_log("preapprovedJoin stableInfoSig: %@", log: tplogDebug, type: .debug, egoStableSig.base64EncodedString())
3163 os_log("preapprovedJoin dynamicInfo: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.peerDynamicInfo.base64EncodedString())
3164 os_log("preapprovedJoin dynamicInfoSig: %@", log: tplogDebug, type: .debug, peer.dynamicInfoAndSig.sig.base64EncodedString())
3166 os_log("preapprovedJoin vouchers: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.voucher.base64EncodedString() })
3167 os_log("preapprovedJoin voucher signatures: %@", log: tplogDebug, type: .debug, peer.vouchers.map { $0.sig.base64EncodedString() })
3169 os_log("preapprovedJoin: uploading %d tlk shares", log: tplogDebug, type: .default, allTLKShares.count)
3172 os_log("preapprovedJoin peer: %@", log: tplogDebug, type: .debug, try peer.serializedData().base64EncodedString())
3174 os_log("preapprovedJoin unable to encode peer: %@", log: tplogDebug, type: .debug, error as CVarArg)
3177 let changeToken = self.containerMO.changeToken ?? ""
3178 let request = JoinWithVoucherRequest.with {
3179 $0.changeToken = changeToken
3182 $0.tlkShares = allTLKShares
3183 $0.viewKeys = viewKeys
3185 self.cuttlefish.joinWithVoucher(request) { response, error in
3186 os_log("preapprovedJoin(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3187 guard let response = response, error == nil else {
3188 os_log("preapprovedJoin failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3189 reply(nil, [], error ?? ContainerError.cloudkitResponseMissing)
3193 self.moc.performAndWait {
3195 self.containerMO.egoPeerStableInfo = peer.stableInfoAndSig.peerStableInfo
3196 self.containerMO.egoPeerStableInfoSig = peer.stableInfoAndSig.sig
3197 try self.onQueuePersist(changes: response.changes)
3198 os_log("preapprovedJoin succeeded", log: tplogDebug)
3200 let keyHierarchyRecords = response.zoneKeyHierarchyRecords.compactMap { CKRecord($0) }
3201 reply(egoPeerID, keyHierarchyRecords, nil)
3203 os_log("preapprovedJoin failed: %@", log: tplogDebug, String(describing: error))
3204 reply(nil, [], error)
3214 func update(deviceName: String?,
3215 serialNumber: String?,
3217 policyVersion: UInt64?,
3218 policySecrets: [String: Data]?,
3219 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3220 self.semaphore.wait()
3221 let reply: (TrustedPeersHelperPeerState?, Error?) -> Void = {
3222 os_log("update complete: %@", log: tplogTrace, type: .info, traceError($1))
3223 self.semaphore.signal()
3227 // Get (and save) the latest from cuttlefish
3228 let stableChanges = StableChanges(deviceName: deviceName,
3229 serialNumber: serialNumber,
3230 osVersion: osVersion,
3231 policyVersion: policyVersion,
3232 policySecrets: policySecrets,
3233 recoverySigningPubKey: nil,
3234 recoveryEncryptionPubKey: nil)
3235 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges, reply: reply)
3238 func set(preapprovedKeys: [Data],
3239 reply: @escaping (Error?) -> Void) {
3240 self.semaphore.wait()
3241 let reply: (Error?) -> Void = {
3242 os_log("setPreapprovedKeys complete: %@", log: tplogTrace, type: .info, traceError($0))
3243 self.semaphore.signal()
3247 self.moc.performAndWait {
3248 os_log("setPreapprovedKeys: %@", log: tplogDebug, type: .default, preapprovedKeys)
3250 guard let egoPeerID = self.containerMO.egoPeerID else {
3251 // No identity, nothing to do
3252 os_log("setPreapprovedKeys: No identity.", log: tplogDebug, type: .default)
3253 reply(ContainerError.noPreparedIdentity)
3256 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3257 guard let signingKeyPair = signingKeyPair else {
3258 os_log("setPreapprovedKeys: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3259 reply(error ?? ContainerError.unableToCreateKeyPair)
3263 self.moc.performAndWait {
3264 let dynamicInfo: TPPeerDynamicInfo
3266 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3268 removingPeerIDs: nil,
3269 preapprovedKeys: preapprovedKeys,
3270 signing: signingKeyPair,
3271 currentMachineIDs: self.onqueueCurrentMIDList())
3273 os_log("setPreapprovedKeys: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg)
3278 os_log("setPreapprovedKeys: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo)
3280 if dynamicInfo == self.model.peer(withID: egoPeerID)?.dynamicInfo {
3281 os_log("setPreapprovedKeys: no change; nothing to do.", log: tplogDebug, type: .default)
3286 os_log("setPreapprovedKeys: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3287 let request = UpdateTrustRequest.with {
3288 $0.changeToken = self.containerMO.changeToken ?? ""
3289 $0.peerID = egoPeerID
3290 $0.dynamicInfoAndSig = SignedPeerDynamicInfo(dynamicInfo)
3293 self.cuttlefish.updateTrust(request) { response, error in
3294 os_log("setPreapprovedKeys(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3295 guard let response = response, error == nil else {
3296 os_log("setPreapprovedKeys failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3297 reply(error ?? ContainerError.cloudkitResponseMissing)
3300 os_log("setPreapprovedKeys: updateTrust suceeded", log: tplogDebug, type: .default)
3303 try self.persist(changes: response.changes)
3305 os_log("setPreapprovedKeys: could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg)
3317 func updateTLKs(ckksKeys: [CKKSKeychainBackedKeySet],
3318 tlkShares: [CKKSTLKShare],
3319 reply: @escaping ([CKRecord]?, Error?) -> Void) {
3320 self.semaphore.wait()
3321 let reply: ([CKRecord]?, Error?) -> Void = {
3322 os_log("updateTLKs complete: %@", log: tplogTrace, type: .info, traceError($1))
3323 self.semaphore.signal()
3327 os_log("Uploading some new TLKs: %@", log: tplogDebug, type: .default, ckksKeys)
3329 self.moc.performAndWait {
3330 guard let egoPeerID = self.containerMO.egoPeerID,
3331 let egoPermData = self.containerMO.egoPeerPermanentInfo,
3332 let egoPermSig = self.containerMO.egoPeerPermanentInfoSig
3334 os_log("Have no self identity, can't make tlk shares", log: tplogDebug, type: .default)
3335 reply(nil, ContainerError.noPreparedIdentity)
3339 guard let selfPermanentInfo = TPPeerPermanentInfo(peerID: egoPeerID, data: egoPermData, sig: egoPermSig, keyFactory: TPECPublicKeyFactory()) else {
3340 os_log("Couldn't parse self identity", log: tplogDebug, type: .default, ckksKeys)
3341 reply(nil, ContainerError.invalidPermanentInfoOrSig)
3345 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, error in
3346 guard let egoPeerKeys = egoPeerKeys else {
3347 os_log("Don't have my own peer keys; can't upload new TLKs: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "error missing")
3351 self.moc.performAndWait {
3352 guard let egoPeerDynamicInfo = self.model.getDynamicInfoForPeer(withID: egoPeerID) else {
3353 os_log("Unable to fetch dynamic info for self", log: tplogDebug, type: .default)
3354 reply(nil, ContainerError.missingDynamicInfo)
3358 let allTLKShares: [TLKShare]
3359 let viewKeys: [ViewKeys]
3361 (viewKeys, allTLKShares) = try self.makeSharesForNewKeySets(ckksKeys: ckksKeys,
3362 tlkShares: tlkShares,
3363 egoPeerKeys: egoPeerKeys,
3364 egoPeerDynamicInfo: egoPeerDynamicInfo,
3365 epoch: Int(selfPermanentInfo.epoch))
3367 os_log("Unable to process keys before uploading: %@", log: tplogDebug, type: .default, error as CVarArg)
3372 let request = UpdateTrustRequest.with {
3373 $0.changeToken = self.containerMO.changeToken ?? ""
3374 $0.peerID = egoPeerID
3375 $0.tlkShares = allTLKShares
3376 $0.viewKeys = viewKeys
3379 self.cuttlefish.updateTrust(request) { response, error in
3380 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3382 guard error == nil else {
3387 let keyHierarchyRecords = response?.zoneKeyHierarchyRecords.compactMap { CKRecord($0) } ?? []
3388 os_log("Recevied %d CKRecords back", log: tplogDebug, type: .default, keyHierarchyRecords.count)
3389 reply(keyHierarchyRecords, nil)
3396 func getState(reply: @escaping (ContainerState) -> Void) {
3397 self.semaphore.wait()
3398 let reply: (ContainerState) -> Void = {
3399 os_log("getState complete: %@", log: tplogTrace, type: .info, $0.egoPeerID ?? "<NULL>")
3400 self.semaphore.signal()
3404 self.moc.performAndWait {
3405 var state = ContainerState()
3406 state.egoPeerID = self.containerMO.egoPeerID
3408 if self.containerMO.bottles != nil {
3409 self.containerMO.bottles!.forEach { bottle in
3410 state.bottles.insert(bottle as! BottleMO)
3414 self.model.allPeers().forEach { peer in
3415 state.peers[peer.peerID] = peer
3417 state.vouchers = Array(self.model.allVouchers())
3422 // This will only fetch changes if no changes have ever been fetched before
3423 private func fetchAndPersistChangesIfNeeded(reply: @escaping (Error?) -> Void) {
3424 self.moc.performAndWait {
3425 if self.containerMO.changeToken == nil {
3426 self.onqueueFetchAndPersistChanges(reply: reply)
3433 private func fetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3434 self.moc.performAndWait {
3435 self.onqueueFetchAndPersistChanges(reply: reply)
3439 private func onqueueFetchAndPersistChanges(reply: @escaping (Error?) -> Void) {
3440 let request = FetchChangesRequest.with {
3441 $0.changeToken = self.containerMO.changeToken ?? ""
3443 os_log("Fetching with change token: %@", log: tplogDebug, type: .default, request.changeToken.count > 0 ? request.changeToken : "empty")
3445 self.cuttlefish.fetchChanges(request) { response, error in
3446 os_log("FetchChanges(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3447 guard let response = response, error == nil else {
3449 case CuttlefishErrorMatcher(code: CuttlefishErrorCode.changeTokenExpired):
3450 os_log("change token is expired; resetting local CK storage", log: tplogDebug, type: .default)
3452 self.moc.performAndWait {
3454 try self.deleteLocalCloudKitData()
3456 os_log("Failed to reset local data: %@", log: tplogDebug, type: .default, error as CVarArg)
3461 self.fetchAndPersistChanges(reply: reply)
3466 os_log("Fetch error is an unknown error: %@", log: tplogDebug, type: .default, String(describing: error))
3469 os_log("Could not fetch changes: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3475 try self.persist(changes: response.changes)
3477 os_log("Could not persist changes: %@", log: tplogDebug, type: .default, error as CVarArg)
3482 if response.changes.more {
3483 os_log("persist: More changes indicated. Fetching...", log: tplogDebug, type: .default)
3484 self.fetchAndPersistChanges(reply: reply)
3487 os_log("persist: no more changes!", log: tplogDebug, type: .default)
3494 // Check for delta update in trust lists, that should lead to update of TLKShares
3496 private func haveTrustMemberChanges(newDynamicInfo: TPPeerDynamicInfo, oldDynamicInfo: TPPeerDynamicInfo?) -> Bool {
3497 guard let oldDynamicInfo = oldDynamicInfo else {
3500 if (newDynamicInfo.includedPeerIDs != oldDynamicInfo.includedPeerIDs) {
3503 if (newDynamicInfo.excludedPeerIDs != oldDynamicInfo.excludedPeerIDs) {
3506 if (newDynamicInfo.preapprovals != oldDynamicInfo.preapprovals) {
3512 // Fetch and persist changes from Cuttlefish. If this results
3513 // in us calculating a new dynamicInfo then give the new dynamicInfo
3514 // to Cuttlefish. If Cuttlefish returns more changes then persist
3515 // them locally, update dynamicInfo if needed, and keep doing that
3516 // until dynamicInfo is unchanged and there are no more changes to fetch.
3518 // Must be holding the semaphore to call this, and it remains
3519 // the caller's responsibility to release it after it completes
3520 // (i.e. after reply is invoked).
3521 internal func fetchChangesAndUpdateTrustIfNeeded(stableChanges: StableChanges? = nil,
3522 changesPending: Bool = false,
3523 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3524 self.fetchAndPersistChanges { error in
3525 if let error = error {
3526 os_log("fetchChangesAndUpdateTrustIfNeeded: fetching failed: %@", log: tplogDebug, type: .default, error as CVarArg)
3531 self.updateTrustIfNeeded(stableChanges: stableChanges, changesPending: changesPending, reply: reply)
3535 // If this results in us calculating a new dynamicInfo then,
3536 // upload the new dynamicInfo
3537 // to Cuttlefish. If Cuttlefish returns more changes then persist
3538 // them locally, update dynamicInfo if needed, and keep doing that
3539 // until dynamicInfo is unchanged and there are no more changes to fetch.
3541 // Must be holding the semaphore to call this, and it remains
3542 // the caller's responsibility to release it after it completes
3543 // (i.e. after reply is invoked).
3544 private func updateTrustIfNeeded(stableChanges: StableChanges? = nil,
3545 changesPending: Bool = false,
3546 reply: @escaping (TrustedPeersHelperPeerState?, Error?) -> Void) {
3547 self.moc.performAndWait {
3548 guard let egoPeerID = self.containerMO.egoPeerID else {
3549 // No identity, nothing to do
3550 os_log("updateTrustIfNeeded: No identity.", log: tplogDebug, type: .default)
3551 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false, osVersion: nil), nil)
3554 loadEgoKeyPair(identifier: signingKeyIdentifier(peerID: egoPeerID)) { signingKeyPair, error in
3555 guard let signingKeyPair = signingKeyPair else {
3556 os_log("updateTrustIfNeeded: no signing key pair: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3557 reply(TrustedPeersHelperPeerState(peerID: nil, isPreapproved: false, status: .unknown, memberChanges: changesPending, unknownMachineIDs: false, osVersion: nil), error)
3560 guard self.model.hasPeer(withID: egoPeerID) else {
3561 // Not in circle, nothing to do
3562 let isPreapproved = self.model.hasPotentiallyTrustedPeerPreapprovingKey(signingKeyPair.publicKey().spki())
3563 os_log("updateTrustIfNeeded: ego peer is not in model, is %@", log: tplogDebug, type: .default, isPreapproved ? "preapproved" : "not yet preapproved")
3564 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3565 isPreapproved: isPreapproved,
3567 memberChanges: changesPending,
3568 unknownMachineIDs: false,
3573 self.moc.performAndWait {
3574 let dynamicInfo: TPPeerDynamicInfo
3575 var stableInfo: TPPeerStableInfo?
3577 // FIXME We should be able to calculate the contents of dynamicInfo without the signingKeyPair,
3578 // and then only load the key if it has changed and we need to sign a new one. This would also
3579 // help make our detection of change immune from non-canonical serialization of dynamicInfo.
3580 dynamicInfo = try self.model.calculateDynamicInfoForPeer(withID: egoPeerID,
3582 removingPeerIDs: nil,
3583 preapprovedKeys: nil,
3584 signing: signingKeyPair,
3585 currentMachineIDs: self.onqueueCurrentMIDList())
3587 stableInfo = try self.createNewStableInfoIfNeeded(stableChanges: stableChanges,
3588 egoPeerID: egoPeerID,
3589 dynamicInfo: dynamicInfo,
3590 signingKeyPair: signingKeyPair)
3592 os_log("updateTrustIfNeeded: couldn't calculate dynamic info: %@", log: tplogDebug, type: .default, error as CVarArg)
3593 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3594 isPreapproved: false,
3595 status: self.model.statusOfPeer(withID: egoPeerID),
3596 memberChanges: changesPending,
3597 unknownMachineIDs: false,
3603 os_log("updateTrustIfNeeded: produced a stableInfo: %@", log: tplogDebug, type: .default, String(describing: stableInfo))
3604 os_log("updateTrustIfNeeded: produced a dynamicInfo: %@", log: tplogDebug, type: .default, dynamicInfo)
3606 let peer = self.model.peer(withID: egoPeerID)
3607 if (stableInfo == nil || stableInfo == peer?.stableInfo) &&
3608 dynamicInfo == peer?.dynamicInfo {
3609 os_log("updateTrustIfNeeded: complete.", log: tplogDebug, type: .default)
3610 // No change to the dynamicInfo: update the MID list now that we've reached a steady state
3612 self.onqueueUpdateMachineIDListFromModel(dynamicInfo: dynamicInfo)
3615 os_log("updateTrustIfNeeded: unable to remove untrusted MachineIDs: %@", log: tplogDebug, type: .default, error as CVarArg)
3618 reply(TrustedPeersHelperPeerState(peerID: egoPeerID,
3619 isPreapproved: false,
3620 status: self.model.statusOfPeer(withID: egoPeerID),
3621 memberChanges: changesPending,
3622 unknownMachineIDs: self.onqueueFullIDMSListWouldBeHelpful(),
3623 osVersion: peer?.stableInfo?.osVersion),
3627 // Check if we change that should trigger a notification that should trigger TLKShare updates
3628 let haveChanges = changesPending || self.haveTrustMemberChanges(newDynamicInfo: dynamicInfo, oldDynamicInfo: peer?.dynamicInfo)
3630 let signedDynamicInfo = SignedPeerDynamicInfo(dynamicInfo)
3631 os_log("updateTrustIfNeeded: attempting updateTrust for %@ with: %@", log: tplogDebug, type: .default, egoPeerID, dynamicInfo)
3632 var request = UpdateTrustRequest.with {
3633 $0.changeToken = self.containerMO.changeToken ?? ""
3634 $0.peerID = egoPeerID
3635 $0.dynamicInfoAndSig = signedDynamicInfo
3637 if let stableInfo = stableInfo {
3638 request.stableInfoAndSig = SignedPeerStableInfo(stableInfo)
3640 self.cuttlefish.updateTrust(request) { response, error in
3641 os_log("UpdateTrust(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3642 guard let response = response, error == nil else {
3643 os_log("UpdateTrust failed: %@", log: tplogDebug, type: .default, (error as CVarArg?) ?? "no error")
3644 reply(nil, error ?? ContainerError.cloudkitResponseMissing)
3649 try self.persist(changes: response.changes)
3651 os_log("updateTrust failed: %@", log: tplogDebug, String(describing: error))
3656 if response.changes.more {
3657 self.fetchChangesAndUpdateTrustIfNeeded(stableChanges: stableChanges,
3658 changesPending: haveChanges,
3661 self.updateTrustIfNeeded(stableChanges: stableChanges,
3662 changesPending: haveChanges,
3671 private func persist(changes: Changes) throws {
3672 // This is some nonsense: I can't figure out how to tell swift to throw an exception across performAndWait.
3673 // So, do it ourself
3674 var outsideBlockError: Error?
3676 self.moc.performAndWait {
3677 os_log("persist: Received %d peer differences, more: %d", log: tplogDebug, type: .default,
3678 changes.differences.count,
3680 os_log("persist: New change token: %@", log: tplogDebug, type: .default, changes.changeToken)
3683 try self.onQueuePersist(changes: changes)
3685 outsideBlockError = error
3689 if let outsideBlockError = outsideBlockError {
3690 throw outsideBlockError
3694 // Must be on moc queue to call this.
3695 // Changes are registered in the model and stored in moc.
3696 private func onQueuePersist(changes: Changes) throws {
3697 self.containerMO.changeToken = changes.changeToken
3698 self.containerMO.moreChanges = changes.more
3700 if changes.differences.count > 0 {
3701 self.model.clearViableBottles()
3704 try changes.differences.forEach { peerDifference in
3705 if let operation = peerDifference.operation {
3707 case .add(let peer):
3708 try self.addOrUpdate(peer: peer)
3710 case .update(let peer):
3711 try self.addOrUpdate(peer: peer)
3712 // Update containerMO ego data if it has changed.
3713 if peer.peerID == self.containerMO.egoPeerID {
3714 guard let stableInfoAndSig: TPPeerStableInfo = peer.stableInfoAndSig.toStableInfo() else {
3717 self.containerMO.egoPeerStableInfo = stableInfoAndSig.data
3718 self.containerMO.egoPeerStableInfoSig = stableInfoAndSig.sig
3721 case .remove(let peer):
3722 self.model.deletePeer(withID: peer.peerID)
3723 if let peerMO = try self.fetchPeerMO(peerID: peer.peerID) {
3724 self.moc.delete(peerMO)
3730 let signingKey = changes.recoverySigningPubKey
3731 let encryptionKey = changes.recoveryEncryptionPubKey
3733 if signingKey.count > 0 && encryptionKey.count > 0 {
3734 self.addOrUpdate(signingKey: signingKey, encryptionKey: encryptionKey)
3739 // Must be on moc queue to call this
3740 // Deletes all things that should come back from CloudKit. Keeps the egoPeer data, though.
3741 private func deleteLocalCloudKitData() throws {
3742 os_log("Deleting all CloudKit data", log: tplogDebug, type: .default)
3745 let peerRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
3746 peerRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3747 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: peerRequest))
3749 let bottleRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Bottle")
3750 bottleRequest.predicate = NSPredicate(format: "container == %@", self.containerMO)
3751 try self.moc.execute(NSBatchDeleteRequest(fetchRequest: bottleRequest))
3753 self.containerMO.peers = nil
3754 self.containerMO.bottles = nil
3755 self.containerMO.changeToken = nil
3756 self.containerMO.moreChanges = false
3758 self.model = Container.loadModel(from: self.containerMO)
3761 os_log("Local delete failed: %@", log: tplogDebug, type: .default, error as CVarArg)
3765 os_log("Saved model with %d peers", log: tplogDebug, type: .default, self.model.allPeerIDs().count)
3768 // Must be on moc queue to call this.
3769 private func addOrUpdate(signingKey: Data, encryptionKey: Data) {
3770 self.model.setRecoveryKeys(
3771 TPRecoveryKeyPair(signingSPKI: signingKey, encryptionSPKI: encryptionKey))
3774 // Must be on moc queue to call this.
3775 private func addOrUpdate(peer: Peer) throws {
3776 if !self.model.hasPeer(withID: peer.peerID) {
3778 guard let permanentInfo = peer.permanentInfoAndSig.toPermanentInfo(peerID: peer.peerID) else {
3779 // Ignoring bad peer
3782 let stableInfo = peer.stableInfoAndSig.toStableInfo()
3783 let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo()
3784 let vouchers = peer.vouchers.compactMap {
3785 TPVoucher(infoWith: $0.voucher, sig: $0.sig)
3787 let isEgoPeer = peer.peerID == self.containerMO.egoPeerID
3788 try self.registerPeerMO(permanentInfo: permanentInfo,
3789 stableInfo: stableInfo,
3790 dynamicInfo: dynamicInfo,
3792 isEgoPeer: isEgoPeer)
3795 // The assertion here is that every peer registered in model is also present in containerMO
3796 let peerMO = try self.fetchPeerMO(peerID: peer.peerID)!
3798 if let stableInfo = peer.stableInfoAndSig.toStableInfo() {
3799 try self.model.update(stableInfo, forPeerWithID: peer.peerID)
3800 // Pull the stableInfo back out of the model, and persist that.
3801 // The model checks signatures and clocks to prevent replay attacks.
3802 let modelPeer = self.model.peer(withID: peer.peerID)
3803 peerMO.stableInfo = modelPeer?.stableInfo?.data
3804 peerMO.stableInfoSig = modelPeer?.stableInfo?.sig
3806 if let dynamicInfo = peer.dynamicInfoAndSig.toDynamicInfo() {
3807 try self.model.update(dynamicInfo, forPeerWithID: peer.peerID)
3808 // Pull the dynamicInfo back out of the model, and persist that.
3809 // The model checks signatures and clocks to prevent replay attacks.
3810 let modelPeer = self.model.peer(withID: peer.peerID)
3811 peerMO.dynamicInfo = modelPeer?.dynamicInfo?.data
3812 peerMO.dynamicInfoSig = modelPeer?.dynamicInfo?.sig
3814 peer.vouchers.forEach {
3815 if let voucher = TPVoucher(infoWith: $0.voucher, sig: $0.sig) {
3816 self.model.register(voucher)
3817 let voucherMO = VoucherMO(context: self.moc)
3818 voucherMO.voucherInfo = voucher.data
3819 voucherMO.voucherInfoSig = voucher.sig
3820 peerMO.addToVouchers(voucherMO)
3826 // Must be on moc queue to call this.
3827 private func fetchPeerMO(peerID: String) throws -> PeerMO? {
3828 let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Peer")
3829 fetch.predicate = NSPredicate(format: "peerID == %@ && container == %@", peerID, self.containerMO)
3830 let peers = try self.moc.fetch(fetch)
3831 return peers.first as? PeerMO
3834 // Must be on moc queue to call this.
3835 private func getPolicyDoc(_ policyVersion: UInt64) throws -> TPPolicyDocument {
3836 guard let policyDoc = self.model.policy(withVersion: policyVersion) else {
3837 throw ContainerError.unknownPolicyVersion(policyVersion)
3839 assert(policyVersion == policyDoc.policyVersion)
3840 if policyVersion == prevailingPolicyVersion {
3841 assert(policyDoc.policyHash == prevailingPolicyHash)
3846 // Must be on moc queue to call this.
3847 private func createNewStableInfoIfNeeded(stableChanges: StableChanges?,
3849 dynamicInfo: TPPeerDynamicInfo,
3850 signingKeyPair: _SFECKeyPair) throws -> TPPeerStableInfo? {
3851 func noChange<T: Equatable>(_ change: T?, _ existing: T?) -> Bool {
3852 return (nil == change) || change == existing
3854 let existingStableInfo = self.model.peer(withID: egoPeerID)?.stableInfo
3855 if noChange(stableChanges?.deviceName, existingStableInfo?.deviceName) &&
3856 noChange(stableChanges?.serialNumber, existingStableInfo?.serialNumber) &&
3857 noChange(stableChanges?.osVersion, existingStableInfo?.osVersion) &&
3858 noChange(stableChanges?.policyVersion, existingStableInfo?.policyVersion) &&
3859 noChange(stableChanges?.policySecrets, existingStableInfo?.policySecrets) &&
3860 noChange(stableChanges?.recoverySigningPubKey, existingStableInfo?.recoverySigningPublicKey) &&
3861 noChange(stableChanges?.recoveryEncryptionPubKey, existingStableInfo?.recoveryEncryptionPublicKey) &&
3862 self.model.doesPeerRecoveryKeyMatchPeers(egoPeerID) {
3865 let policyHash: String?
3866 if let policyVersion = stableChanges?.policyVersion {
3867 let policyDoc = try self.getPolicyDoc(policyVersion)
3868 policyHash = policyDoc.policyHash
3873 // Determine which recovery key we'd like to be using, given our current idea of who to trust
3874 let newRecoveryKeys = self.model.bestRecoveryKey(with: dynamicInfo)
3876 return try self.model.createStableInfo(withPolicyVersion: stableChanges?.policyVersion ?? existingStableInfo?.policyVersion ?? prevailingPolicyVersion,
3877 policyHash: policyHash ?? existingStableInfo?.policyHash ?? prevailingPolicyHash,
3878 policySecrets: stableChanges?.policySecrets ?? existingStableInfo?.policySecrets,
3879 deviceName: stableChanges?.deviceName ?? existingStableInfo?.deviceName ?? "",
3880 serialNumber: stableChanges?.serialNumber ?? existingStableInfo?.serialNumber ?? "",
3881 osVersion: stableChanges?.osVersion ?? existingStableInfo?.osVersion ?? "",
3882 signing: signingKeyPair,
3883 recoverySigningPubKey: newRecoveryKeys?.signingSPKI ?? existingStableInfo?.recoverySigningPublicKey,
3884 recoveryEncryptionPubKey: newRecoveryKeys?.encryptionSPKI ?? existingStableInfo?.recoveryEncryptionPublicKey)
3887 private func assembleBottle(egoPeerID: String) throws -> Bottle {
3888 let bottleMoSet = containerMO.bottles as? Set<BottleMO>
3890 var bottleMOs = bottleMoSet?.filter {
3891 $0.peerID == egoPeerID
3894 if let count = bottleMOs?.count {
3896 throw ContainerError.tooManyBottlesForPeer
3897 } else if count == 0 {
3898 throw ContainerError.noBottleForPeer
3901 throw ContainerError.failedToAssembleBottle
3904 let BM: BottleMO? = (bottleMOs?.removeFirst())
3906 let bottle = try Bottle.with {
3907 $0.peerID = egoPeerID
3909 if let bottledPeerData = BM?.contents {
3910 $0.contents = bottledPeerData
3912 throw ContainerError.failedToAssembleBottle
3915 if let bottledPeerEscrowSigningSPKI = BM?.escrowedSigningSPKI {
3916 $0.escrowedSigningSpki = bottledPeerEscrowSigningSPKI
3918 throw ContainerError.failedToAssembleBottle
3921 if let bottledPeerSignatureUsingEscrowKey = BM?.signatureUsingEscrowKey {
3922 $0.signatureUsingEscrowKey = bottledPeerSignatureUsingEscrowKey
3924 throw ContainerError.failedToAssembleBottle
3927 if let bottledPeerSignatureUsingPeerKey = BM?.signatureUsingPeerKey {
3928 $0.signatureUsingPeerKey = bottledPeerSignatureUsingPeerKey
3930 throw ContainerError.failedToAssembleBottle
3933 if let bID = BM?.bottleID {
3936 throw ContainerError.failedToAssembleBottle
3943 func reportHealth(request: ReportHealthRequest, reply: @escaping (Error?) -> Void) {
3944 self.semaphore.wait()
3945 let reply: (Error?) -> Void = {
3946 os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0))
3947 self.semaphore.signal()
3951 var updatedRequest = request
3953 if let egoPeerID = self.containerMO.egoPeerID {
3954 loadEgoKeys(peerID: egoPeerID) { egoPeerKeys, _ in
3955 updatedRequest.octagonEgoIdentity = (egoPeerKeys != nil)
3959 self.moc.performAndWait {
3960 self.cuttlefish.reportHealth(updatedRequest) { response, error in
3961 os_log("reportHealth(%@): %@, error: %@", log: tplogDebug, "\(String(describing: request))", "\(String(describing: response))", "\(String(describing: error))")
3962 guard error == nil else {
3971 func pushHealthInquiry(reply: @escaping (Error?) -> Void) {
3972 self.semaphore.wait()
3973 let reply: (Error?) -> Void = {
3974 os_log("reportHealth complete %@", log: tplogTrace, type: .info, traceError($0))
3975 self.semaphore.signal()
3979 self.moc.performAndWait {
3980 self.cuttlefish.pushHealthInquiry(PushHealthInquiryRequest()) { response, error in
3981 os_log("pushHealthInquiry(): %@, error: %@", log: tplogDebug, "\(String(describing: response))", "\(String(describing: error))")
3982 guard error == nil else {