1/* 2 * Copyright (c) 2003-2009,2012,2014 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 * keychain_import.c 24 */ 25 26#include "keychain_import.h" 27#include "keychain_utilities.h" 28#include "access_utils.h" 29#include "security.h" 30 31#include <errno.h> 32#include <unistd.h> 33#include <stdio.h> 34#include <Security/SecImportExport.h> 35#include <Security/SecIdentity.h> 36#include <Security/SecKey.h> 37#include <Security/SecCertificate.h> 38#include <Security/SecKeychainItemExtendedAttributes.h> 39#include <security_cdsa_utils/cuFileIo.h> 40#include <CoreFoundation/CoreFoundation.h> 41 42// SecTrustedApplicationCreateApplicationGroup 43#include <Security/SecTrustedApplicationPriv.h> 44 45#define KC_IMPORT_KEY_PASSWORD_MESSAGE CFSTR("Enter the password for \"%1$@\":") 46#define KC_IMPORT_KEY_PASSWORD_RETRYMESSAGE CFSTR("Sorry, you entered an invalid password.\n\nEnter the password for \"%1$@\":") 47 48static int do_keychain_import( 49 SecKeychainRef kcRef, 50 CFDataRef inData, 51 SecExternalFormat externFormat, 52 SecExternalItemType itemType, 53 SecAccessRef access, 54 Boolean nonExtractable, 55 const char *passphrase, 56 const char *fileName, 57 char **attrNames, 58 char **attrValues, 59 unsigned numExtendedAttributes) 60{ 61 SecKeyImportExportParameters keyParams; 62 OSStatus ortn; 63 CFStringRef fileStr; 64 CFArrayRef outArray = NULL; 65 int result = 0; 66 int numCerts = 0; 67 int numKeys = 0; 68 int numIdentities = 0; 69 int tryCount = 0; 70 CFIndex dex; 71 CFIndex numItems = 0; 72 CFStringRef passStr = NULL; 73 CFStringRef promptStr = NULL; 74 CFStringRef retryStr = NULL; 75 76 /* 77 * Specify some kind of passphrase in case caller doesn't know this 78 * is a wrapped object 79 */ 80 memset(&keyParams, 0, sizeof(SecKeyImportExportParameters)); 81 keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION; 82 if(passphrase != NULL) { 83 passStr = CFStringCreateWithCString(NULL, passphrase, kCFStringEncodingASCII); 84 keyParams.passphrase = passStr; 85 } 86 else { 87 keyParams.flags = kSecKeySecurePassphrase; 88 } 89 if(nonExtractable) { 90 // explicitly set the key attributes, omitting the CSSM_KEYATTR_EXTRACTABLE bit 91 keyParams.keyAttributes = CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE; 92 } 93 keyParams.accessRef = access; 94 95 fileStr = CFStringCreateWithCString(NULL, fileName, kCFStringEncodingUTF8); 96 if (fileStr) { 97 CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, fileStr, kCFURLPOSIXPathStyle, FALSE); 98 if (fileURL) { 99 CFStringRef nameStr = CFURLCopyLastPathComponent(fileURL); 100 if (nameStr) { 101 safe_CFRelease(&fileStr); 102 fileStr = nameStr; 103 } 104 safe_CFRelease(&fileURL); 105 } 106 } 107 promptStr = CFStringCreateWithFormat(NULL, NULL, KC_IMPORT_KEY_PASSWORD_MESSAGE, fileStr); 108 retryStr = CFStringCreateWithFormat(NULL, NULL, KC_IMPORT_KEY_PASSWORD_RETRYMESSAGE, fileStr); 109 110 while (TRUE) 111 { 112 keyParams.alertPrompt = (tryCount == 0) ? promptStr : retryStr; 113 114 ortn = SecKeychainItemImport(inData, 115 fileStr, 116 &externFormat, 117 &itemType, 118 0, /* flags not used (yet) */ 119 &keyParams, 120 kcRef, 121 &outArray); 122 123 if(ortn) { 124 if (ortn == errSecPkcs12VerifyFailure && ++tryCount < 3) { 125 continue; 126 } 127 sec_perror("SecKeychainItemImport", ortn); 128 result = 1; 129 goto cleanup; 130 } 131 break; 132 } 133 134 /* 135 * Parse returned items & report to user 136 */ 137 if(outArray == NULL) { 138 sec_error("No keychain items found"); 139 result = 1; 140 goto cleanup; 141 } 142 numItems = CFArrayGetCount(outArray); 143 for(dex=0; dex<numItems; dex++) { 144 CFTypeRef item = CFArrayGetValueAtIndex(outArray, dex); 145 CFTypeID itemType = CFGetTypeID(item); 146 if(itemType == SecIdentityGetTypeID()) { 147 numIdentities++; 148 } 149 else if(itemType == SecCertificateGetTypeID()) { 150 numCerts++; 151 } 152 else if(itemType == SecKeyGetTypeID()) { 153 numKeys++; 154 } 155 else { 156 sec_error("Unexpected item type returned from SecKeychainItemImport"); 157 result = 1; 158 goto cleanup; 159 } 160 } 161 if(numIdentities) { 162 char *str; 163 if(numIdentities > 1) { 164 str = "identities"; 165 } 166 else { 167 str = "identity"; 168 } 169 fprintf(stdout, "%d %s imported.\n", numIdentities, str); 170 } 171 if(numKeys) { 172 char *str; 173 if(numKeys > 1) { 174 str = "keys"; 175 } 176 else { 177 str = "key"; 178 } 179 fprintf(stdout, "%d %s imported.\n", numKeys, str); 180 } 181 if(numCerts) { 182 char *str; 183 if(numCerts > 1) { 184 str = "certificates"; 185 } 186 else { 187 str = "certificate"; 188 } 189 fprintf(stdout, "%d %s imported.\n", numCerts, str); 190 } 191 192 /* optionally apply extended attributes */ 193 if(numExtendedAttributes) { 194 unsigned attrDex; 195 for(attrDex=0; attrDex<numExtendedAttributes; attrDex++) { 196 CFStringRef attrNameStr = CFStringCreateWithCString(NULL, attrNames[attrDex], 197 kCFStringEncodingASCII); 198 CFDataRef attrValueData = CFDataCreate(NULL, (const UInt8 *)attrValues[attrDex], 199 strlen(attrValues[attrDex])); 200 for(dex=0; dex<numItems; dex++) { 201 SecKeychainItemRef itemRef = 202 (SecKeychainItemRef)CFArrayGetValueAtIndex(outArray, dex); 203 ortn = SecKeychainItemSetExtendedAttribute(itemRef, attrNameStr, attrValueData); 204 if(ortn) { 205 cssmPerror("SecKeychainItemSetExtendedAttribute", ortn); 206 result = 1; 207 break; 208 } 209 } /* for each imported item */ 210 CFRelease(attrNameStr); 211 CFRelease(attrValueData); 212 if(result) { 213 break; 214 } 215 } /* for each extended attribute */ 216 } 217 218cleanup: 219 safe_CFRelease(&fileStr); 220 safe_CFRelease(&outArray); 221 safe_CFRelease(&passStr); 222 safe_CFRelease(&promptStr); 223 safe_CFRelease(&retryStr); 224 225 return result; 226} 227 228int 229keychain_import(int argc, char * const *argv) 230{ 231 int ch, result = 0; 232 233 char *inFile = NULL; 234 char *kcName = NULL; 235 SecKeychainRef kcRef = NULL; 236 SecExternalFormat externFormat = kSecFormatUnknown; 237 SecExternalItemType itemType = kSecItemTypeUnknown; 238 Boolean wrapped = FALSE; 239 Boolean nonExtractable = FALSE; 240 const char *passphrase = NULL; 241 unsigned char *inFileData = NULL; 242 unsigned inFileLen = 0; 243 CFDataRef inData = NULL; 244 unsigned numExtendedAttributes = 0; 245 char **attrNames = NULL; 246 char **attrValues = NULL; 247 Boolean access_specified = FALSE; 248 Boolean always_allow = FALSE; 249 SecAccessRef access = NULL; 250 CFMutableArrayRef trusted_list = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 251 252 if(argc < 2) { 253 result = 2; /* @@@ Return 2 triggers usage message. */ 254 goto cleanup; 255 } 256 inFile = argv[1]; 257 if((argc == 2) && (inFile[0] == '-')) { 258 result = 2; 259 goto cleanup; 260 } 261 optind = 2; 262 263 while ((ch = getopt(argc, argv, "k:t:f:P:wxa:hAT:")) != -1) 264 { 265 switch (ch) 266 { 267 case 'k': 268 kcName = optarg; 269 break; 270 case 't': 271 if(!strcmp("pub", optarg)) { 272 itemType = kSecItemTypePublicKey; 273 } 274 else if(!strcmp("priv", optarg)) { 275 itemType = kSecItemTypePrivateKey; 276 } 277 else if(!strcmp("session", optarg)) { 278 itemType = kSecItemTypeSessionKey; 279 } 280 else if(!strcmp("cert", optarg)) { 281 itemType = kSecItemTypeCertificate; 282 } 283 else if(!strcmp("agg", optarg)) { 284 itemType = kSecItemTypeAggregate; 285 } 286 else { 287 result = 2; /* @@@ Return 2 triggers usage message. */ 288 goto cleanup; 289 } 290 break; 291 case 'f': 292 if(!strcmp("openssl", optarg)) { 293 externFormat = kSecFormatOpenSSL; 294 } 295 else if(!strcmp("openssh1", optarg)) { 296 externFormat = kSecFormatSSH; 297 } 298 else if(!strcmp("openssh2", optarg)) { 299 externFormat = kSecFormatSSHv2; 300 } 301 else if(!strcmp("bsafe", optarg)) { 302 externFormat = kSecFormatBSAFE; 303 } 304 else if(!strcmp("raw", optarg)) { 305 externFormat = kSecFormatRawKey; 306 } 307 else if(!strcmp("pkcs7", optarg)) { 308 externFormat = kSecFormatPKCS7; 309 } 310 else if(!strcmp("pkcs8", optarg)) { 311 externFormat = kSecFormatWrappedPKCS8; 312 } 313 else if(!strcmp("pkcs12", optarg)) { 314 externFormat = kSecFormatPKCS12; 315 } 316 else if(!strcmp("netscape", optarg)) { 317 externFormat = kSecFormatNetscapeCertSequence; 318 } 319 else if(!strcmp("x509", optarg)) { 320 externFormat = kSecFormatX509Cert; 321 } 322 else if(!strcmp("pemseq", optarg)) { 323 externFormat = kSecFormatPEMSequence; 324 } 325 else { 326 result = 2; /* @@@ Return 2 triggers usage message. */ 327 goto cleanup; 328 } 329 break; 330 case 'w': 331 wrapped = TRUE; 332 break; 333 case 'x': 334 nonExtractable = TRUE; 335 break; 336 case 'P': 337 passphrase = optarg; 338 break; 339 case 'a': 340 /* this takes an additional argument */ 341 if(optind > (argc - 1)) { 342 result = 2; /* @@@ Return 2 triggers usage message. */ 343 goto cleanup; 344 } 345 attrNames = (char **)realloc(attrNames, numExtendedAttributes * sizeof(char *)); 346 attrValues = (char **)realloc(attrValues, numExtendedAttributes * sizeof(char *)); 347 attrNames[numExtendedAttributes] = optarg; 348 attrValues[numExtendedAttributes] = argv[optind]; 349 numExtendedAttributes++; 350 optind++; 351 break; 352 case 'A': 353 always_allow = TRUE; 354 access_specified = TRUE; 355 break; 356 case 'T': 357 if (optarg[0]) 358 { 359 SecTrustedApplicationRef app = NULL; 360 OSStatus status = noErr; 361 /* check whether the argument specifies an application group */ 362 const char *groupPrefix = "group://"; 363 size_t prefixLen = strlen(groupPrefix); 364 if (strlen(optarg) > prefixLen && !memcmp(optarg, groupPrefix, prefixLen)) { 365 const char *groupName = &optarg[prefixLen]; 366 if ((status = SecTrustedApplicationCreateApplicationGroup(groupName, NULL, &app)) != noErr) { 367 sec_error("SecTrustedApplicationCreateApplicationGroup %s: %s", optarg, sec_errstr(status)); 368 } 369 } else { 370 if ((status = SecTrustedApplicationCreateFromPath(optarg, &app)) != noErr) { 371 sec_error("SecTrustedApplicationCreateFromPath %s: %s", optarg, sec_errstr(status)); 372 } 373 } 374 375 if (status) { 376 result = 1; 377 goto cleanup; 378 } 379 380 CFArrayAppendValue(trusted_list, app); 381 CFRelease(app); 382 } 383 access_specified = TRUE; 384 break; 385 case '?': 386 default: 387 result = 2; /* @@@ Return 2 triggers usage message. */ 388 goto cleanup; 389 } 390 } 391 392 if(wrapped) { 393 switch(externFormat) { 394 case kSecFormatOpenSSL: 395 case kSecFormatUnknown: // i.e., use default 396 externFormat = kSecFormatWrappedOpenSSL; 397 break; 398 case kSecFormatSSH: 399 externFormat = kSecFormatWrappedSSH; 400 break; 401 case kSecFormatSSHv2: 402 /* there is no wrappedSSHv2 */ 403 externFormat = kSecFormatWrappedOpenSSL; 404 break; 405 case kSecFormatWrappedPKCS8: 406 /* proceed */ 407 break; 408 default: 409 fprintf(stderr, "Don't know how to wrap in specified format/type\n"); 410 result = 2; /* @@@ Return 2 triggers usage message. */ 411 goto cleanup; 412 } 413 } 414 415 if(kcName) { 416 kcRef = keychain_open(kcName); 417 if(kcRef == NULL) { 418 return 1; 419 } 420 } 421 if(readFile(inFile, &inFileData, &inFileLen)) { 422 sec_error("Error reading infile %s: %s", inFile, strerror(errno)); 423 result = 1; 424 goto cleanup; 425 } 426 inData = CFDataCreate(NULL, inFileData, inFileLen); 427 if(inData == NULL) { 428 result = 1; 429 goto cleanup; 430 } 431 free(inFileData); 432 433 if(access_specified) 434 { 435 char *accessName = NULL; 436 CFStringRef fileStr = CFStringCreateWithCString(NULL, inFile, kCFStringEncodingUTF8); 437 if (fileStr) { 438 CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, fileStr, kCFURLPOSIXPathStyle, FALSE); 439 if (fileURL) { 440 CFStringRef nameStr = CFURLCopyLastPathComponent(fileURL); 441 if (nameStr) { 442 CFIndex nameLen = CFStringGetLength(nameStr); 443 CFIndex bufLen = 1 + CFStringGetMaximumSizeForEncoding(nameLen, kCFStringEncodingUTF8); 444 accessName = (char *)malloc(bufLen); 445 if (!CFStringGetCString(nameStr, accessName, bufLen-1, kCFStringEncodingUTF8)) 446 accessName[0]=0; 447 nameLen = strlen(accessName); 448 char *p = &accessName[nameLen]; // initially points to terminating null 449 while (--nameLen > 0) { 450 if (*p == '.') { // strip extension, if any 451 *p = '\0'; 452 break; 453 } 454 p--; 455 } 456 safe_CFRelease(&nameStr); 457 } 458 safe_CFRelease(&fileURL); 459 } 460 safe_CFRelease(&fileStr); 461 } 462 463 result = create_access(accessName, always_allow, trusted_list, &access); 464 465 if (accessName) { 466 free(accessName); 467 } 468 if (result != 0) { 469 goto cleanup; 470 } 471 } 472 473 result = do_keychain_import(kcRef, inData, externFormat, itemType, access, 474 nonExtractable, passphrase, inFile, attrNames, 475 attrValues, numExtendedAttributes); 476 477cleanup: 478 safe_CFRelease(&trusted_list); 479 safe_CFRelease(&access); 480 safe_CFRelease(&kcRef); 481 safe_CFRelease(&inData); 482 483 return result; 484} 485