CKKSProcessReceivedKeysOperation.m
1 /* 2 * Copyright (c) 2016 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 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 11 * file. 12 * 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. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24 #if OCTAGON 25 26 #import <AssertMacros.h> 27 28 #import "CKKSKeychainView.h" 29 #import "CKKSCurrentKeyPointer.h" 30 #import "CKKSKey.h" 31 #import "CKKSProcessReceivedKeysOperation.h" 32 #import "keychain/ckks/CKKSOperationDependencies.h" 33 #import "keychain/ckks/CloudKitCategories.h" 34 #import "keychain/categories/NSError+UsefulConstructors.h" 35 36 @implementation CKKSProcessReceivedKeysOperation 37 @synthesize intendedState = _intendedState; 38 39 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies 40 intendedState:(OctagonState*)intendedState 41 errorState:(OctagonState*)errorState 42 { 43 if(self = [super init]) { 44 _deps = dependencies; 45 _intendedState = intendedState; 46 _nextState = errorState; 47 } 48 return self; 49 } 50 51 - (void)main { 52 NSArray<CKKSPeerProviderState*>* currentTrustStates = self.deps.currentTrustStates; 53 54 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult { 55 bool ok = [self _onqueueMain:currentTrustStates]; 56 57 return ok ? CKKSDatabaseTransactionCommit : CKKSDatabaseTransactionRollback; 58 }]; 59 } 60 61 - (bool)_onqueueMain:(NSArray<CKKSPeerProviderState*>*)currentTrustStates 62 { 63 NSError* error = nil; 64 CKKSKey* tlk = nil; 65 CKKSKey* topKey = nil; 66 67 // The synckeys table contains everything that's in CloudKit, if looked at correctly. 68 // Updates from CloudKit are marked 'remote'; everything else is 'local'. 69 70 // Step 1. Find all remote keys. 71 NSArray<CKKSKey*>* remoteKeys = [CKKSKey remoteKeys:self.deps.zoneID error:&error]; 72 if(!remoteKeys) { 73 ckkserror("ckkskey", self.deps.zoneID, "couldn't fetch list of remote keys: %@", error); 74 self.error = error; 75 self.nextState = SecCKKSZoneKeyStateError; 76 return false; 77 } 78 79 if([remoteKeys count] == 0u) { 80 ckksnotice("ckkskey", self.deps.zoneID, "No remote keys? Quitting."); 81 // Not a ready state, more of a quizzical one? The key state machine will know what to do. 82 self.error = error; 83 self.nextState = SecCKKSZoneKeyStateBecomeReady; 84 return false; 85 } 86 87 ckksinfo("ckkskey", self.deps.zoneID, "remote keys: %@", remoteKeys); 88 89 // current TLK record: 90 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:self.deps.zoneID error:&error]; 91 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:self.deps.zoneID error:&error]; 92 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:self.deps.zoneID error:&error]; 93 94 // Do these pointers point at anything? 95 NSError* localerror = nil; 96 CKKSKey* suggestedTLK = currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentTLKPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil; 97 CKKSKey* suggestedClassA = currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassAPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil; 98 CKKSKey* suggestedClassC = currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassCPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil; 99 100 if(!currentTLKPointer || !currentClassAPointer || !currentClassCPointer || 101 !currentTLKPointer.currentKeyUUID || !currentClassAPointer.currentKeyUUID || !currentClassCPointer.currentKeyUUID || 102 !suggestedTLK || !suggestedClassA || !suggestedClassC) { 103 ckkserror("ckkskey", self.deps.zoneID, "no current pointer for some keyclass: tlk:%@ a:%@ c:%@ %@ %@", 104 currentTLKPointer, currentClassAPointer, currentClassCPointer, error, localerror); 105 self.error = error; 106 self.nextState = SecCKKSZoneKeyStateBadCurrentPointers; 107 return true; 108 } 109 110 for(CKKSKey* key in remoteKeys) { 111 // Find the active TLK. 112 if([key.uuid isEqualToString: currentTLKPointer.currentKeyUUID]) { 113 if([key wrapsSelf]) { 114 tlk = key; 115 } else { 116 NSError *newError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSKeyNotSelfWrapped description:[NSString stringWithFormat: @"current TLK doesn't wrap itself: %@ %@", key, key.parentKeyUUID] underlying:error]; 117 ckkserror("ckkskey", self.deps.zoneID, "%@", error); 118 self.error = newError; 119 self.nextState = SecCKKSZoneKeyStateUnhealthy; 120 return true; 121 } 122 } 123 } 124 125 if(!tlk) { 126 ckkserror("ckkskey", self.deps.zoneID, "couldn't find active TLK: %@", currentTLKPointer); 127 self.error = error; 128 self.nextState = SecCKKSZoneKeyStateUnhealthy; 129 return true; 130 } 131 132 if(![tlk validTLK:&error]) { 133 // Something has gone horribly wrong. Enter error state. 134 ckkserror("ckkskey", self.deps.zoneID, "CKKS claims %@ is not a valid TLK: %@", tlk, error); 135 self.error = [NSError errorWithDomain:CKKSErrorDomain code:CKKSInvalidTLK description:@"invalid TLK from CloudKit" underlying:error]; 136 self.nextState = SecCKKSZoneKeyStateError; 137 return true; 138 } 139 140 // This key is our proposed TLK. 141 if(![tlk tlkMaterialPresentOrRecoverableViaTLKShare:currentTrustStates 142 error:&error]) { 143 // TLK is valid, but not present locally 144 if(error && [self.deps.lockStateTracker isLockedError:error]) { 145 ckksnotice("ckkskey", self.deps.zoneID, "Received a TLK(%@), but keybag appears to be locked. Entering a waiting state.", tlk); 146 self.nextState = SecCKKSZoneKeyStateWaitForUnlock; 147 } else { 148 ckksnotice("ckkskey", self.deps.zoneID, "Received a TLK(%@) which we don't have in the local keychain: %@", tlk, error); 149 self.error = error; 150 self.nextState = SecCKKSZoneKeyStateTLKMissing; 151 } 152 return true; 153 } 154 155 // Ensure that new keys wrap to the TLK. 156 for(CKKSKey* key in remoteKeys) { 157 if(key == tlk) { 158 continue; 159 } 160 161 topKey = [key topKeyInAnyState:&error]; 162 163 if(error != nil || ![topKey.uuid isEqual: tlk.uuid]) { 164 ckkserror("ckkskey", self.deps.zoneID, "new key %@ is orphaned (%@)", key, error); 165 // TODO: possibly re-fetch. Maybe not an actual error state. 166 self.error = [NSError errorWithDomain:CKKSErrorDomain 167 code:CKKSOrphanedKey 168 description:[NSString stringWithFormat:@"orphaned key(%@) in hierarchy", topKey] 169 underlying:error]; 170 self.nextState = SecCKKSZoneKeyStateError; 171 return true; 172 173 } 174 175 // Okay, it wraps to the TLK. Can we unwrap it? 176 if(![key unwrapViaKeyHierarchy:&error] || error != nil) { 177 if(error && [self.deps.lockStateTracker isLockedError:error]) { 178 ckksnotice("ckkskey", self.deps.zoneID, "Couldn't unwrap new key (%@), but keybag appears to be locked. Entering waitforunlock.", key); 179 self.error = error; 180 self.nextState = SecCKKSZoneKeyStateWaitForUnlock; 181 return true; 182 } else { 183 ckkserror("ckkskey", self.deps.zoneID, "new key %@ claims to wrap to TLK, but we can't unwrap it: %@", topKey, error); 184 self.error = [NSError errorWithDomain:CKKSErrorDomain 185 code:CKKSOrphanedKey 186 description:[NSString stringWithFormat:@"unwrappable key(%@) in hierarchy: %@", topKey, error] 187 underlying:error]; 188 self.nextState = SecCKKSZoneKeyStateError; 189 return true; 190 } 191 } 192 193 ckksnotice("ckkskey", self.deps.zoneID, "New key %@ wraps to tlk %@", key, tlk); 194 } 195 196 197 // We're happy with this key hierarchy. Save it. 198 for(CKKSKey* key in remoteKeys) { 199 key.state = SecCKKSProcessedStateLocal; 200 201 if([key.uuid isEqualToString: currentClassAPointer.currentKeyUUID] || 202 [key.uuid isEqualToString: currentClassCPointer.currentKeyUUID]) { 203 [key saveToDatabaseAsOnlyCurrentKeyForClassAndState: &error]; 204 } else { 205 [key saveToDatabase: &error]; 206 } 207 208 [key saveKeyMaterialToKeychain: &error]; 209 210 211 if(error) { 212 if([self.deps.lockStateTracker isLockedError:error]) { 213 ckksnotice("ckkskey", self.deps.zoneID, "Couldn't save newly local key %@ keychain, due to lock state. Entering a waiting state; %@", key, error); 214 self.nextState = SecCKKSZoneKeyStateWaitForUnlock; 215 } else { 216 ckkserror("ckkskey", self.deps.zoneID, "couldn't save newly local key %@ to database: %@", key, error); 217 self.nextState = SecCKKSZoneKeyStateError; 218 } 219 self.error = error; 220 return false; 221 } 222 } 223 224 // New key hierarchy? Get it backed up! 225 // TLKs are now saved in the local keychain; fire off a backup 226 CKKSNearFutureScheduler* tlkNotifier = self.deps.savedTLKNotifier; 227 ckksnotice("ckkstlk", self.deps.zoneID, "triggering new TLK notification: %@", tlkNotifier); 228 [tlkNotifier trigger]; 229 230 if(!error) { 231 ckksnotice("ckkskey", self.deps.zoneID, "Accepted new key hierarchy"); 232 self.nextState = self.intendedState; 233 } else { 234 ckkserror("ckkskey", self.deps.zoneID, "error accepting new key hierarchy: %@", error); 235 self.error = error; 236 self.nextState = SecCKKSZoneKeyStateError; 237 } 238 return true; 239 } 240 241 @end; 242 243 #endif