4 #import "keychain/categories/NSError+UsefulConstructors.h"
5 #import "keychain/ckks/CKKSCheckKeyHierarchyOperation.h"
6 #import "keychain/ckks/CKKSMirrorEntry.h"
7 #import "keychain/ot/OTDefines.h"
8 #import "utilities/SecTrace.h"
10 @implementation CKKSCheckKeyHierarchyOperation
11 @synthesize nextState = _nextState;
12 @synthesize intendedState = _intendedState;
14 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
15 intendedState:(OctagonState*)intendedState
16 errorState:(OctagonState*)errorState
18 if(self = [super init]) {
21 _intendedState = intendedState;
22 _nextState = errorState;
28 NSArray<CKKSPeerProviderState*>* currentTrustStates = [self.deps currentTrustStates];
30 __block CKKSCurrentKeySet* set = nil;
32 [self.deps.databaseProvider dispatchSyncWithReadOnlySQLTransaction:^{
33 set = [CKKSCurrentKeySet loadForZone:self.deps.zoneID];
36 // Drop off the sql queue: we can do the rest of this function with what we've already loaded
38 if(set.error && !([set.error.domain isEqual: @"securityd"] && set.error.code == errSecItemNotFound)) {
39 ckkserror("ckkskey", self.deps.zoneID, "Error examining existing key hierarchy: %@", set.error);
42 if(!set.currentTLKPointer && !set.currentClassAPointer && !set.currentClassCPointer) {
43 ckkserror("ckkskey", self.deps.zoneID, "Error examining existing key hierarchy (missing all CKPs, likely no hierarchy exists): %@", set);
44 self.nextState = SecCKKSZoneKeyStateWaitForTLKCreation;
49 if(!set.tlk || !set.classA || !set.classC) {
50 ckkserror("ckkskey", self.deps.zoneID, "Error examining existing key hierarchy (missing at least one key): %@", set);
51 self.error = set.error;
52 self.nextState = SecCKKSZoneKeyStateUnhealthy;
56 NSError* localerror = nil;
57 bool probablyOkIfUnlocked = false;
59 // keychain being locked is not a fatal error here
60 [set.tlk loadKeyMaterialFromKeychain:&localerror];
61 if(localerror && ![self.deps.lockStateTracker isLockedError:localerror]) {
62 ckkserror("ckkskey", self.deps.zoneID, "Error loading TLK(%@): %@", set.tlk, localerror);
63 self.error = localerror;
64 self.nextState = SecCKKSZoneKeyStateUnhealthy;
66 } else if(localerror) {
67 ckkserror("ckkskey", self.deps.zoneID, "Soft error loading TLK(%@), maybe locked: %@", set.tlk, localerror);
68 probablyOkIfUnlocked = true;
72 // keychain being locked is not a fatal error here
73 [set.classA loadKeyMaterialFromKeychain:&localerror];
74 if(localerror && ![self.deps.lockStateTracker isLockedError:localerror]) {
75 ckkserror("ckkskey", self.deps.zoneID, "Error loading classA key(%@): %@", set.classA, localerror);
76 self.error = localerror;
77 self.nextState = SecCKKSZoneKeyStateUnhealthy;
79 } else if(localerror) {
80 ckkserror("ckkskey", self.deps.zoneID, "Soft error loading classA key(%@), maybe locked: %@", set.classA, localerror);
81 probablyOkIfUnlocked = true;
85 // keychain being locked is a fatal error here, since this is class C
86 [set.classC loadKeyMaterialFromKeychain:&localerror];
88 ckkserror("ckkskey", self.deps.zoneID, "Error loading classC(%@): %@", set.classC, localerror);
89 self.error = localerror;
90 self.nextState = SecCKKSZoneKeyStateUnhealthy;
94 // Check that the classA and classC keys point to the current TLK
95 if(![set.classA.parentKeyUUID isEqualToString: set.tlk.uuid]) {
96 localerror = [NSError errorWithDomain:CKKSServerExtensionErrorDomain
97 code:CKKSServerUnexpectedSyncKeyInChain
99 NSLocalizedDescriptionKey: @"Current class A key does not wrap to current TLK",
101 ckkserror("ckkskey", self.deps.zoneID, "Key hierarchy unhealthy: %@", localerror);
102 self.error = localerror;
103 self.nextState = SecCKKSZoneKeyStateUnhealthy;
106 if(![set.classC.parentKeyUUID isEqualToString: set.tlk.uuid]) {
107 localerror = [NSError errorWithDomain:CKKSServerExtensionErrorDomain
108 code:CKKSServerUnexpectedSyncKeyInChain
110 NSLocalizedDescriptionKey: @"Current class C key does not wrap to current TLK",
112 ckkserror("ckkskey", self.deps.zoneID, "Key hierarchy unhealthy: %@", localerror);
113 self.error = localerror;
114 self.nextState = SecCKKSZoneKeyStateUnhealthy;
118 // Now that we're pretty sure we have the keys, are they shared appropriately?
119 // We need trust in order to proceed here
120 if(currentTrustStates.count == 0u) {
121 ckkserror("ckkskey", self.deps.zoneID, "Can't check TLKShares due to missing trust states");
122 [self.deps provideKeySet:set];
123 self.nextState = SecCKKSZoneKeyStateLoseTrust;
127 // If we've reached this point, we have a workable keyset. Let's provide it to all waiters.
128 [self.deps provideKeySet:set];
130 if(probablyOkIfUnlocked) {
131 ckkserror("ckkskey", self.deps.zoneID, "Can't check TLKShares due to lock state");
132 [self.deps provideKeySet:set];
133 self.nextState = SecCKKSZoneKeyStateReadyPendingUnlock;
137 // Check that every trusted peer has at least one TLK share
138 // If any trust state check works, don't error out
139 bool anyTrustStateSucceeded = false;
140 for(CKKSPeerProviderState* trustState in currentTrustStates) {
141 NSSet<id<CKKSPeer>>* missingShares = [trustState findPeersMissingTLKSharesFor:set
144 if(localerror && [self.deps.lockStateTracker isLockedError:localerror]) {
145 ckkserror("ckkskey", self.deps.zoneID, "Couldn't find missing TLK shares due to lock state: %@", localerror);
148 } else if(([localerror.domain isEqualToString:TrustedPeersHelperErrorDomain] && localerror.code == TrustedPeersHelperErrorNoPreparedIdentity) ||
149 ([localerror.domain isEqualToString:CKKSErrorDomain] && localerror.code == CKKSLackingTrust) ||
150 ([localerror.domain isEqualToString:CKKSErrorDomain] && localerror.code == CKKSNoPeersAvailable)) {
151 ckkserror("ckkskey", self.deps.zoneID, "Couldn't find missing TLK shares due some trust issue: %@", localerror);
153 if(trustState.essential) {
154 ckkserror("ckkskey", self.deps.zoneID, "Trust state is considered essential; entering waitfortrust: %@", trustState);
156 // Octagon can reinform us when it thinks we should start again
157 self.nextState = SecCKKSZoneKeyStateLoseTrust;
161 ckkserror("ckkskey", self.deps.zoneID, "Peer provider is considered nonessential; ignoring error: %@", trustState);
165 } else if(localerror) {
166 ckkserror("ckkskey", self.deps.zoneID, "Error finding missing TLK shares: %@", localerror);
170 if(!missingShares || missingShares.count != 0u) {
171 ckksnotice("ckksshare", self.deps.zoneID, "TLK (%@) is not shared correctly for trust state %@, but we believe AKS is locked", set.tlk, trustState.peerProviderID);
173 self.error = [NSError errorWithDomain:CKKSErrorDomain
174 code:CKKSMissingTLKShare
175 description:[NSString stringWithFormat:@"Missing shares for %lu peers", (unsigned long)missingShares.count]];
176 self.nextState = SecCKKSZoneKeyStateHealTLKShares;
179 ckksnotice("ckksshare", self.deps.zoneID, "TLK (%@) is shared correctly for trust state %@", set.tlk, trustState.peerProviderID);
182 anyTrustStateSucceeded |= true;
185 if(!anyTrustStateSucceeded) {
186 self.error = localerror;
187 self.nextState = SecCKKSZoneKeyStateError;
192 // Got to the bottom? Cool! All keys are present and accounted for.
193 self.nextState = SecCKKSZoneKeyStateReady;