/ keychain / SecureObjectSync / SOSPiggyback.m
SOSPiggyback.m
  1  /*
  2   * Copyright (c) 2015 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  #include <Security/SecureObjectSync/SOSCloudCircle.h>
 25  #include "keychain/SecureObjectSync/SOSInternal.h"
 26  #include "keychain/SecureObjectSync/SOSCircleDer.h"
 27  #include "keychain/SecureObjectSync/SOSAccountPriv.h"
 28  
 29  #include <Security/Security.h>
 30  #include <Security/SecKeyPriv.h>
 31  
 32  #include "keychain/securityd/SecItemSchema.h"
 33  #include <Security/SecItem.h>
 34  #include <Security/SecItemPriv.h>
 35  
 36  #include "SOSPiggyback.h"
 37  
 38  #include "utilities/der_date.h"
 39  #include "utilities/der_plist.h"
 40  #include <utilities/der_plist_internal.h>
 41  #include <corecrypto/ccder.h>
 42  
 43  static size_t SOSPiggyBackBlobGetDEREncodedSize(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFErrorRef *error) {
 44      size_t total_payload = 0;
 45  
 46      CFDataRef publicBytes = NULL;
 47      OSStatus result = SecKeyCopyPublicBytes(pubKey, &publicBytes);
 48  
 49      if (result != errSecSuccess) {
 50          SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to export public bytes"), NULL, error);
 51          return 0;
 52      }
 53  
 54      require_quiet(accumulate_size(&total_payload, der_sizeof_number(gencount, error)), errOut);
 55      require_quiet(accumulate_size(&total_payload, der_sizeof_data_or_null(publicBytes, error)), errOut);
 56      require_quiet(accumulate_size(&total_payload, der_sizeof_data_or_null(signature, error)), errOut);
 57      CFReleaseNull(publicBytes);
 58      return ccder_sizeof(CCDER_CONSTRUCTED_SEQUENCE, total_payload);
 59  
 60  errOut:
 61      SecCFDERCreateError(kSecDERErrorUnknownEncoding, CFSTR("don't know how to encode"), NULL, error);
 62      CFReleaseNull(publicBytes);
 63      return 0;
 64  }
 65  
 66  
 67  static uint8_t* SOSPiggyBackBlobEncodeToDER(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFErrorRef* error, const uint8_t* der, uint8_t* der_end) {
 68      CFDataRef publicBytes = NULL;
 69  
 70      OSStatus result = SecKeyCopyPublicBytes(pubKey, &publicBytes);
 71  
 72      if (result != errSecSuccess) {
 73          SOSCreateError(kSOSErrorBadKey, CFSTR("Failed to export public bytes"), NULL, error);
 74          return NULL;
 75      }
 76  
 77  
 78      der_end =  ccder_encode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, der_end, der,
 79                                             der_encode_number(gencount, error, der,
 80                                                               der_encode_data_or_null(publicBytes, error, der,
 81                                                                                       der_encode_data_or_null(signature, error, der, der_end))));
 82  
 83      CFReleaseNull(publicBytes);
 84  
 85      return der_end;
 86  }
 87  
 88  CFDataRef SOSPiggyBackBlobCopyEncodedData(SOSGenCountRef gencount, SecKeyRef pubKey, CFDataRef signature, CFErrorRef *error)
 89  {
 90      return CFDataCreateWithDER(kCFAllocatorDefault, SOSPiggyBackBlobGetDEREncodedSize(gencount, pubKey, signature, error), ^uint8_t*(size_t size, uint8_t *buffer) {
 91          return SOSPiggyBackBlobEncodeToDER(gencount, pubKey, signature, error, buffer, (uint8_t *) buffer + size);
 92      });
 93  }
 94  
 95  bool SOSPiggyBackAddToKeychain(NSArray<NSData*>* identities, NSArray<NSDictionary*>*  tlks)
 96  {
 97      [tlks enumerateObjectsUsingBlock:^(NSDictionary* item, NSUInteger idx, BOOL * _Nonnull stop) {
 98  
 99          NSData* v_data = item[(__bridge NSString*)kSecValueData];
100          NSString *acct = item[(__bridge NSString*)kSecAttrAccount];
101          NSString *srvr = item[(__bridge NSString*)kSecAttrServer];
102  
103          NSData* base64EncodedVData = [v_data base64EncodedDataWithOptions:0];
104  
105          NSMutableDictionary* query = [@{
106                                          (id)kSecClass : (id)kSecClassInternetPassword,
107                                          (id)kSecUseDataProtectionKeychain : @YES,
108                                          (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
109                                          (id)kSecAttrDescription: @"tlk-piggy",
110                                          (id)kSecAttrSynchronizable : (id)kCFBooleanFalse,
111                                          (id)kSecAttrSyncViewHint : (id)kSecAttrViewHintPCSMasterKey,
112                                          (id)kSecAttrServer: srvr,
113                                          (id)kSecAttrAccount: [NSString stringWithFormat: @"%@-piggy", acct],
114                                          (id)kSecAttrPath: acct,
115                                          (id)kSecAttrIsInvisible: @YES,
116                                          (id)kSecValueData : base64EncodedVData,
117                                          } mutableCopy];
118  
119          OSStatus status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
120  
121          if(status == errSecDuplicateItem) {
122              // Sure, okay, fine, we'll update.
123              NSMutableDictionary* update = [@{(id)kSecValueData: v_data,
124                                               } mutableCopy];
125  
126              status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
127          }
128  
129          if(status) {
130              secerror("Couldn't save tlks to keychain %d", (int)status);
131          }
132      }];
133      [identities enumerateObjectsUsingBlock:^(NSData *v_data, NSUInteger idx, BOOL *stop) {
134          SecKeyRef publicKey = NULL;
135          SecKeyRef privKey = NULL;
136          CFDataRef public_key_hash = NULL;
137          NSMutableDictionary* query = NULL;
138          CFStringRef peerid = NULL;
139          OSStatus status;
140  
141          NSDictionary *keyAttributes = @{
142                                          (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
143                                          (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
144                                          };
145  
146          privKey = SecKeyCreateWithData((__bridge CFDataRef)v_data, (__bridge CFDictionaryRef)keyAttributes, NULL);
147          require_action_quiet(privKey, exit, secnotice("piggy","privKey failed to be created"));
148  
149          publicKey = SecKeyCreatePublicFromPrivate(privKey);
150          require_action_quiet(publicKey, exit, secnotice("piggy","public key failed to be created"));
151  
152          public_key_hash = SecKeyCopyPublicKeyHash(publicKey);
153          require_action_quiet(public_key_hash, exit, secnotice("piggy","can't create public key hash"));
154  
155          peerid = SOSCopyIDOfKey(publicKey, NULL);
156  
157          query = [@{
158              (id)kSecClass : (id)kSecClassKey,
159              (id)kSecUseDataProtectionKeychain : @YES,
160              (id)kSecAttrAccessGroup: @"com.apple.security.sos",
161              (id)kSecAttrApplicationLabel : (__bridge NSData*)public_key_hash,
162              (id)kSecAttrLabel : [NSString stringWithFormat: @"Cloud Identity-piggy-%@", peerid],
163              (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
164              (id)kSecUseTombstones : (id)kCFBooleanTrue,
165              (id)kSecValueData : v_data,
166          } mutableCopy];
167  
168          status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
169  
170          if(status == errSecDuplicateItem) {
171              // Sure, okay, fine, we'll update.
172              NSMutableDictionary* update = [@{
173                                               (id)kSecValueData: v_data,
174                                               } mutableCopy];
175              query[(id)kSecValueData] = nil;
176              status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
177          }
178  
179          if(status) {
180              secerror("Couldn't save backupV0 to keychain %d", (int)status);
181          }
182  
183      exit:
184          CFReleaseNull(publicKey);
185          CFReleaseNull(privKey);
186          CFReleaseNull(peerid);
187          CFReleaseNull(public_key_hash);
188          secnotice("piggy","key not available");
189      }];
190  
191      return true;
192  }
193  
194  static const uint8_t *
195  piggy_decode_data(const uint8_t *der, const uint8_t *der_end, NSData **data)
196  {
197      size_t body_length = 0;
198      const uint8_t *body = ccder_decode_tl(CCDER_OCTET_STRING, &body_length, der, der_end);
199      if(body == NULL)
200          return NULL;
201      *data = [NSData dataWithBytes:body length:body_length];
202      return body + body_length;
203  
204  }
205  
206  static NSMutableArray *
207  parse_identies(const uint8_t *der, const uint8_t *der_end)
208  {
209      NSMutableArray<NSData *>* array = [NSMutableArray array];
210  
211      while (der != der_end) {
212          NSData *data = NULL;
213  
214          der = piggy_decode_data(der, der_end, &data);
215          if (der == NULL)
216              return NULL;
217          if (data)
218              [array addObject:data];
219      }
220  
221      return array;
222  }
223  
224  static NSMutableArray *
225  SOSPiggyCreateDecodedTLKs(const uint8_t *der, const uint8_t *der_end)
226  {
227      NSMutableArray *array = [NSMutableArray array];
228  
229      while (der != der_end) {
230          NSMutableDictionary<NSString *,id> *item = [NSMutableDictionary dictionary];
231          NSData *data = NULL;
232          size_t item_size = 0;
233  
234          const uint8_t *item_der = ccder_decode_tl(CCDER_CONSTRUCTED_SEQUENCE, &item_size, der, der_end);
235          if (item_der == NULL)
236              return NULL;
237          const uint8_t *end_item_der = item_der + item_size;
238  
239          item_der = piggy_decode_data(item_der, end_item_der, &data);
240          if (der == NULL)
241              return NULL;
242  
243          item[(__bridge id)kSecValueData] = data;
244          data = NULL;
245  
246          item_der = piggy_decode_data(item_der, end_item_der, &data);
247          if (item_der == NULL)
248              return NULL;
249          if ([data length] != sizeof(uuid_t)) {
250              return NULL;
251          }
252  
253          NSString *uuidString = [[[NSUUID alloc] initWithUUIDBytes:[data bytes]] UUIDString];
254          item[(__bridge id)kSecAttrAccount] = uuidString;
255  
256          NSString *view = NULL;
257          uint64_t r = 0;
258          const uint8_t *choice_der = NULL;
259          choice_der = ccder_decode_uint64(&r, item_der, end_item_der);
260          if (choice_der == NULL) {
261              /* try other branch of CHOICE, a string */
262              CFErrorRef localError = NULL;
263              CFStringRef string = NULL;
264  
265              choice_der = der_decode_string(NULL, &string, &localError, item_der, end_item_der);
266              if (choice_der == NULL || string == NULL) {
267                  CFReleaseNull(string);
268                  secnotice("piggy", "Failed to parse view name");
269                  return NULL;
270              }
271              CFReleaseNull(localError);
272              item_der = choice_der;
273              view = CFBridgingRelease(string);
274          } else {
275              if (r == kTLKManatee)
276                  view = @"Manatee";
277              else if (r == kTLKEngram)
278                  view = @"Engram";
279              else if (r == kTLKAutoUnlock)
280                  view = @"AutoUnlock";
281              else if (r == kTLKHealth)
282                  view = @"Health";
283              else {
284                  secnotice("piggy", "unexpected view number: %d", (int)r);
285                  return NULL;
286              }
287              item_der = choice_der;
288          }
289          item[(__bridge id)kSecAttrServer] = view;
290  
291          if (item_der != end_item_der) {
292              return NULL;
293          }
294          secnotice("piggy", "Adding %@ %@", view, uuidString);
295  
296          [array addObject:item];
297  
298          der = end_item_der;
299      }
300      return array;
301  }
302  
303  NSDictionary *
304  SOSPiggyCopyInitialSyncData(const uint8_t** der, const uint8_t *der_end)
305  {
306      NSMutableDictionary *results = [NSMutableDictionary dictionary];
307      size_t seq_size;
308  
309      const uint8_t *topSeq = ccder_decode_tl(CCDER_CONSTRUCTED_SEQUENCE, &seq_size, *der, der_end);
310      if(topSeq == NULL){
311          secnotice("piggy", "Failed to parse CONS SEQ");
312          return NULL;
313      }
314  
315      /* parse idents */
316      const uint8_t *ider = ccder_decode_tl(CCDER_CONSTRUCTED_SEQUENCE, &seq_size, topSeq, der_end);
317      if (ider == NULL){
318          secnotice("piggy", "Failed to parse CONS SEQ of ident");
319          return NULL;
320      }
321      NSArray *idents = parse_identies(ider, ider + seq_size);
322      if (idents)
323          results[@"idents"]  = idents;
324      topSeq = ider + seq_size;
325  
326      /* parse tlks */
327      const uint8_t *tder = ccder_decode_tl(CCDER_CONSTRUCTED_SEQUENCE, &seq_size, topSeq, der_end);
328      if (tder == NULL){
329          secnotice("piggy", "Failed to parse CONS SEQ of TLKs");
330          return NULL;
331      }
332      NSMutableArray *tlks = SOSPiggyCreateDecodedTLKs(tder, tder + seq_size);
333      if (tlks)
334          results[@"tlks"]  = tlks;
335      *der = tder + seq_size;
336  
337      /* Don't check length here so we can add more data */
338  
339      if(results.count == 0){
340          secnotice("piggy","NO DATA, falling back to waiting 5 minutes for initial sync to finish");
341          results = NULL;
342      }
343      
344      return results;
345  }
346  
347  bool
348  SOSPiggyBackBlobCreateFromDER(SOSGenCountRef  *retGencount,
349                                SecKeyRef *retPubKey,
350                                CFDataRef *retSignature,
351                                const uint8_t** der_p, const uint8_t *der_end,
352                                PiggyBackProtocolVersion version,
353                                bool *setInitialSyncTimeoutToV0,
354                                CFErrorRef *error)
355  {
356      const uint8_t *sequence_end;
357      SOSGenCountRef gencount = NULL;
358      CFDataRef signature = NULL;
359      CFDataRef publicBytes = NULL;
360      
361      bool res = true;
362  
363      *setInitialSyncTimeoutToV0 = true;
364  
365      *der_p = ccder_decode_constructed_tl(CCDER_CONSTRUCTED_SEQUENCE, &sequence_end, *der_p, der_end);
366      require_action_quiet(sequence_end != NULL, errOut,
367                           SOSCreateError(kSOSErrorBadFormat, CFSTR("Bad Blob DER"), (error != NULL) ? *error : NULL, error));
368      *der_p = der_decode_number(kCFAllocatorDefault, &gencount, error, *der_p, sequence_end);
369      *der_p = der_decode_data_or_null(kCFAllocatorDefault, &publicBytes, error, *der_p, sequence_end);
370      *der_p = der_decode_data_or_null(kCFAllocatorDefault, &signature, error, *der_p, sequence_end);
371      
372      if(version != kPiggyV0 && *der_p != der_end) {
373          NSDictionary* initialSyncDict = SOSPiggyCopyInitialSyncData(der_p, der_end);
374          if (initialSyncDict) {
375              NSArray* idents = initialSyncDict[@"idents"];
376              NSArray* tlks = initialSyncDict[@"tlks"];
377              secnotice("piggy", "Piggybacking include identities(%d) and tlks(%d)",
378                        (int)idents.count, (int)tlks.count);
379              SOSPiggyBackAddToKeychain(idents, tlks);
380              *setInitialSyncTimeoutToV0 = false;
381          }
382          /* Don't check length here so we can add more data */
383      }
384      else{ //V0
385          secnotice("piggy","Piggybacking version 0, setting initial sync timeout to 5 minutes");
386          *setInitialSyncTimeoutToV0 = true;
387          require_action_quiet(*der_p && *der_p == der_end, errOut,
388                               SOSCreateError(kSOSErrorBadFormat, CFSTR("Didn't consume all bytes for pbblob"), (error != NULL) ? *error : NULL, error));
389      }
390  
391      *retPubKey = SecKeyCreateFromPublicData(kCFAllocatorDefault, kSecECDSAAlgorithmID, publicBytes);
392      require_quiet(*retPubKey, errOut);
393      *retGencount = gencount;
394      *retSignature = signature;
395  
396      res = true;
397  
398  errOut:
399      if(!res) {
400          CFReleaseNull(gencount);
401          CFReleaseNull(signature);
402      }
403      CFReleaseNull(publicBytes);
404      
405      return res;
406  }
407  
408  bool
409  SOSPiggyBackBlobCreateFromData(SOSGenCountRef *gencount,
410                                 SecKeyRef *pubKey,
411                                 CFDataRef *signature,
412                                 CFDataRef blobData,
413                                 PiggyBackProtocolVersion version,
414                                 bool *setInitialSyncTimeoutToV0,
415                                 CFErrorRef *error)
416  {
417      size_t size = CFDataGetLength(blobData);
418      const uint8_t *der = CFDataGetBytePtr(blobData);
419      return SOSPiggyBackBlobCreateFromDER(gencount, pubKey, signature, &der, der + size, version, setInitialSyncTimeoutToV0, error);
420  }