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 }