/ keychain / ckks / CKKSProcessReceivedKeysOperation.m
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