2 * Copyright (c) 2003-2004,2006,2012,2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
26 #include "keychain_export.h"
27 #include "keychain_utilities.h"
28 #include "security_tool.h"
33 #include <Security/SecImportExport.h>
34 #include <Security/SecKeychainItem.h>
35 #include <Security/SecKeychainSearch.h>
36 #include <Security/SecIdentitySearch.h>
37 #include <Security/SecKey.h>
38 #include <Security/SecCertificate.h>
39 #include <Security/SecItem.h>
40 #include <Security/SecAccessControl.h>
41 #include <Security/SecAccessControlPriv.h>
42 #include <security_cdsa_utils/cuFileIo.h>
43 #include <CoreFoundation/CoreFoundation.h>
56 * Add all itmes of specified class from a keychain to an array.
57 * Item class are things like kSecCertificateItemClass, and
58 * CSSM_DL_DB_RECORD_PRIVATE_KEY. Identities are searched separately.
60 static OSStatus
addKcItems(
62 SecItemClass itemClass
, // kSecCertificateItemClass
63 CFMutableArrayRef outArray
,
64 unsigned *numItems
) // UPDATED on return
67 SecKeychainSearchRef srchRef
;
69 ortn
= SecKeychainSearchCreateFromAttributes(kcRef
,
74 sec_perror("SecKeychainSearchCreateFromAttributes", ortn
);
78 SecKeychainItemRef itemRef
;
79 ortn
= SecKeychainSearchCopyNext(srchRef
, &itemRef
);
81 if(ortn
== errSecItemNotFound
) {
82 /* normal search end */
86 sec_perror("SecIdentitySearchCopyNext", ortn
);
90 CFArrayAppendValue(outArray
, itemRef
);
91 CFRelease(itemRef
); // array owns the item
99 * Add all SecIdentityRefs from a keychain into an array.
101 static OSStatus
addIdentities(
102 SecKeychainRef kcRef
,
103 CFMutableArrayRef outArray
,
104 unsigned *numItems
) // UPDATED on return
106 /* Search for all identities */
107 SecIdentitySearchRef srchRef
;
108 OSStatus ortn
= SecIdentitySearchCreate(kcRef
,
112 sec_perror("SecIdentitySearchCreate", ortn
);
117 SecIdentityRef identity
;
118 ortn
= SecIdentitySearchCopyNext(srchRef
, &identity
);
120 if(ortn
== errSecItemNotFound
) {
121 /* normal search end */
125 sec_perror("SecIdentitySearchCopyNext", ortn
);
129 CFArrayAppendValue(outArray
, identity
);
131 /* the array has the retain count we need */
134 } while(ortn
== noErr
);
139 static int do_keychain_export(
140 SecKeychainRef kcRef
,
141 SecExternalFormat externFormat
,
143 const char *passphrase
,
145 const char *fileName
)
149 unsigned numPrivKeys
= 0;
150 unsigned numPubKeys
= 0;
151 unsigned numCerts
= 0;
152 unsigned numIdents
= 0;
154 uint32 expFlags
= 0; // SecItemImportExportFlags
155 SecKeyImportExportParameters keyParams
;
156 CFStringRef passStr
= NULL
;
157 CFDataRef outData
= NULL
;
161 CFMutableArrayRef exportItems
= CFArrayCreateMutable(NULL
, 0,
162 &kCFTypeArrayCallBacks
);
165 ortn
= addKcItems(kcRef
, kSecCertificateItemClass
, exportItems
, &numCerts
);
173 ortn
= addKcItems(kcRef
, CSSM_DL_DB_RECORD_PRIVATE_KEY
, exportItems
,
182 ortn
= addKcItems(kcRef
, CSSM_DL_DB_RECORD_PUBLIC_KEY
, exportItems
,
191 ortn
= addKcItems(kcRef
, CSSM_DL_DB_RECORD_PRIVATE_KEY
, exportItems
,
197 ortn
= addKcItems(kcRef
, CSSM_DL_DB_RECORD_PUBLIC_KEY
, exportItems
,
206 /* No public keys here - PKCS12 doesn't support them */
207 ortn
= addKcItems(kcRef
, kSecCertificateItemClass
, exportItems
, &numCerts
);
212 ortn
= addKcItems(kcRef
, CSSM_DL_DB_RECORD_PRIVATE_KEY
, exportItems
,
221 ortn
= addIdentities(kcRef
, exportItems
, &numIdents
);
227 numPrivKeys
+= numIdents
;
228 numCerts
+= numIdents
;
232 sec_error("Internal error parsing item_spec");
237 numItems
= CFArrayGetCount(exportItems
);
238 if(externFormat
== kSecFormatUnknown
) {
239 /* Use default export format per set of items */
241 externFormat
= kSecFormatPEMSequence
;
244 externFormat
= kSecFormatX509Cert
;
247 externFormat
= kSecFormatOpenSSL
;
251 expFlags
|= kSecItemPemArmour
;
255 * Key related arguments, ignored if we're not exporting keys.
256 * Always specify some kind of passphrase - default is secure passkey.
258 memset(&keyParams
, 0, sizeof(keyParams
));
259 keyParams
.version
= SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION
;
260 if(passphrase
!= NULL
) {
261 passStr
= CFStringCreateWithCString(NULL
, passphrase
, kCFStringEncodingASCII
);
262 keyParams
.passphrase
= passStr
;
265 keyParams
.flags
= kSecKeySecurePassphrase
;
269 ortn
= SecKeychainItemExport(exportItems
, externFormat
, expFlags
, &keyParams
,
272 sec_perror("SecKeychainItemExport", ortn
);
277 len
= CFDataGetLength(outData
);
279 int rtn
= writeFileSizet(fileName
, CFDataGetBytePtr(outData
), len
);
282 fprintf(stderr
, "...%lu bytes written to %s\n", len
, fileName
);
286 sec_error("Error writing to %s: %s", fileName
, strerror(errno
));
291 size_t irtn
= write(STDOUT_FILENO
, CFDataGetBytePtr(outData
), len
);
292 if(irtn
!= (size_t)len
) {
298 CFRelease(exportItems
);
310 keychain_export(int argc
, char * const *argv
)
314 char *outFile
= NULL
;
316 SecKeychainRef kcRef
= NULL
;
317 SecExternalFormat externFormat
= kSecFormatUnknown
;
318 ItemSpec itemSpec
= IS_All
;
321 const char *passphrase
= NULL
;
323 while ((ch
= getopt(argc
, argv
, "k:o:t:f:P:wph")) != -1)
334 if(!strcmp("certs", optarg
)) {
337 else if(!strcmp("allKeys", optarg
)) {
338 itemSpec
= IS_AllKeys
;
340 else if(!strcmp("pubKeys", optarg
)) {
341 itemSpec
= IS_PubKeys
;
343 else if(!strcmp("privKeys", optarg
)) {
344 itemSpec
= IS_PrivKeys
;
346 else if(!strcmp("identities", optarg
)) {
347 itemSpec
= IS_Identities
;
349 else if(!strcmp("all", optarg
)) {
353 return 2; /* @@@ Return 2 triggers usage message. */
357 if(!strcmp("openssl", optarg
)) {
358 externFormat
= kSecFormatOpenSSL
;
360 else if(!strcmp("openssh1", optarg
)) {
361 externFormat
= kSecFormatSSH
;
363 else if(!strcmp("openssh2", optarg
)) {
364 externFormat
= kSecFormatSSHv2
;
366 else if(!strcmp("bsafe", optarg
)) {
367 externFormat
= kSecFormatBSAFE
;
369 else if(!strcmp("raw", optarg
)) {
370 externFormat
= kSecFormatRawKey
;
372 else if(!strcmp("pkcs7", optarg
)) {
373 externFormat
= kSecFormatPKCS7
;
375 else if(!strcmp("pkcs8", optarg
)) {
376 externFormat
= kSecFormatWrappedPKCS8
;
378 else if(!strcmp("pkcs12", optarg
)) {
379 externFormat
= kSecFormatPKCS12
;
381 else if(!strcmp("netscape", optarg
)) {
382 externFormat
= kSecFormatNetscapeCertSequence
;
384 else if(!strcmp("x509", optarg
)) {
385 externFormat
= kSecFormatX509Cert
;
387 else if(!strcmp("pemseq", optarg
)) {
388 externFormat
= kSecFormatPEMSequence
;
391 return 2; /* @@@ Return 2 triggers usage message. */
405 return 2; /* @@@ Return 2 triggers usage message. */
410 switch(externFormat
) {
411 case kSecFormatOpenSSL
:
412 case kSecFormatUnknown
: // i.e., use default
413 externFormat
= kSecFormatWrappedOpenSSL
;
416 externFormat
= kSecFormatWrappedSSH
;
418 case kSecFormatSSHv2
:
419 /* there is no wrappedSSHv2 */
420 externFormat
= kSecFormatWrappedOpenSSL
;
422 case kSecFormatWrappedPKCS8
:
426 sec_error("Don't know how to wrap in specified format/type");
427 return 2; /* @@@ Return 2 triggers usage message. */
432 kcRef
= keychain_open(kcName
);
437 result
= do_keychain_export(kcRef
, externFormat
, itemSpec
,
438 passphrase
, doPem
, outFile
);
447 CFMutableStringRef str
;
448 } ctk_dict2str_context
;
452 ctk_obj_to_str(CFTypeRef obj
, char *buf
, int bufLen
, Boolean key
);
455 ctk_dict2str(const void *key
, const void *value
, void *context
)
457 char keyBuf
[64] = { 0 };
458 ctk_obj_to_str(key
, keyBuf
, sizeof(keyBuf
), true);
460 char valueBuf
[1024] = { 0 };
461 ctk_obj_to_str(value
, valueBuf
, sizeof(valueBuf
), false);
463 CFStringRef str
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("\n\t\t\t%s : %s,"), keyBuf
, valueBuf
);
464 CFStringAppend(((ctk_dict2str_context
*)context
)->str
, str
);
469 ctk_obj_to_str(CFTypeRef obj
, char *buf
, int bufLen
, Boolean key
)
471 CFStringRef str
= NULL
;
473 if(CFGetTypeID(obj
) == CFStringGetTypeID()) {
474 // CFStringRef - print the string as is (for keys) or quoted (values)
475 str
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, key
? CFSTR("%@") : CFSTR("\"%@\""), obj
);
476 } else if(CFGetTypeID(obj
) == CFNumberGetTypeID()) {
477 // CFNumber - print the value using current locale
478 CFNumberRef num
= (CFNumberRef
)obj
;
480 CFLocaleRef locale
= CFLocaleCopyCurrent();
481 CFNumberFormatterRef fmt
= CFNumberFormatterCreate(kCFAllocatorDefault
, locale
, kCFNumberFormatterDecimalStyle
);
484 str
= CFNumberFormatterCreateStringWithNumber(kCFAllocatorDefault
, fmt
, num
);
486 } else if(CFGetTypeID(obj
) == CFDataGetTypeID()) {
487 // CFData - print the data as <hex bytes>
488 CFDataRef data
= (CFDataRef
)obj
;
490 CFMutableStringRef hexStr
= CFStringCreateMutable(kCFAllocatorDefault
, CFDataGetLength(data
) * 3);
492 for(int i
= 0; i
< CFDataGetLength(data
); i
++) {
493 CFStringRef hexByte
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("%02x "), *(CFDataGetBytePtr(data
) + i
));
494 CFStringAppend(hexStr
, hexByte
);
498 // Get rid of the last excessive space.
499 if(CFDataGetLength(data
)) {
500 CFStringDelete(hexStr
, CFRangeMake(CFStringGetLength(hexStr
) - 1, 1));
503 str
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("<%@>"), hexStr
);
505 } else if(CFGetTypeID(obj
) == CFBooleanGetTypeID()) {
506 // CFBoolean - print true/false
507 CFBooleanRef cfbool
= (CFBooleanRef
)obj
;
509 str
= CFStringCreateWithCString(kCFAllocatorDefault
, CFBooleanGetValue(cfbool
) ? "true" : "false", kCFStringEncodingUTF8
);
510 } else if(CFGetTypeID(obj
) == SecAccessControlGetTypeID()) {
511 // SecAccessControlRef - print the constraints dictionary
512 SecAccessControlRef ac
= (SecAccessControlRef
)obj
;
514 CFDictionaryRef constraints
= SecAccessControlGetConstraints(ac
);
515 CFMutableStringRef constraintsStr
= CFStringCreateMutable(kCFAllocatorDefault
, 1024);
516 if(constraints
&& CFDictionaryGetCount(constraints
)) {
517 ctk_dict2str_context context
;
518 context
.str
= constraintsStr
;
519 CFDictionaryApplyFunction(constraints
, ctk_dict2str
, &context
);
520 CFStringReplace(constraintsStr
, CFRangeMake(CFStringGetLength(constraintsStr
) - 1, 1), CFSTR("\n\t\t"));
523 CFDictionaryRef protection
= SecAccessControlGetProtection(ac
);
524 CFMutableStringRef protectionStr
= CFStringCreateMutable(kCFAllocatorDefault
, 512);
525 if(protection
&& CFDictionaryGetCount(protection
)) {
526 ctk_dict2str_context context
;
527 context
.str
= protectionStr
;
528 CFDictionaryApplyFunction(protection
, ctk_dict2str
, &context
);
529 CFStringReplace(protectionStr
, CFRangeMake(CFStringGetLength(protectionStr
) - 1, 1), CFSTR("\n\t\t"));
532 str
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("constraints: {%@}\n\t\tprotection: {%@}"), constraintsStr
, protectionStr
);
533 CFRelease(constraintsStr
);
534 CFRelease(protectionStr
);
537 // Fill the provided buffer with the converted string.
539 Boolean success
= CFStringGetCString(str
, buf
, bufLen
, kCFStringEncodingUTF8
);
547 // Use object description as fallback...
548 CFStringRef description
= CFCopyDescription(obj
);
549 if(!CFStringGetCString(description
, buf
, bufLen
, kCFStringEncodingUTF8
)) {
550 // ...or else we don't know.
551 strncpy(buf
, "<?>", bufLen
);
554 CFRelease(description
);
564 ctk_print_dict(const void *key
, const void *value
, void *context
)
566 char keyBuf
[64] = { 0 };
567 ctk_obj_to_str(key
, keyBuf
, sizeof(keyBuf
), true);
569 char valueBuf
[1024] = { 0 };
570 ctk_obj_to_str(value
, valueBuf
, sizeof(valueBuf
), false);
572 printf("\t%s : %s\n", keyBuf
, valueBuf
);
576 ctk_dump_item_header(ctk_print_context
*ctx
)
579 printf("==== %s #%d\n", ctx
->name
, ctx
->i
);
583 ctk_dump_item_footer(ctk_print_context
*ctx
)
589 ctk_dump_item(CFTypeRef item
, ctk_print_context
*ctx
)
591 OSStatus stat
= errSecSuccess
;
593 CFTypeID tid
= CFGetTypeID(item
);
594 if(tid
== CFDictionaryGetTypeID()) {
595 // We expect a dictionary containing item attributes.
596 ctk_dump_item_header(ctx
);
597 CFDictionaryApplyFunction((CFDictionaryRef
)item
, ctk_print_dict
, ctx
);
598 ctk_dump_item_footer(ctx
);
600 stat
= errSecInternalComponent
;
601 printf("Unexpected item type ID: %lu\n", tid
);
608 ctk_dump_items(CFArrayRef items
, CFTypeRef secClass
, const char *name
)
610 OSStatus stat
= errSecSuccess
;
612 ctk_print_context ctx
= { 1, name
};
614 for(CFIndex i
= 0; i
< CFArrayGetCount(items
); i
++) {
615 CFTypeRef item
= CFArrayGetValueAtIndex(items
, i
);
616 stat
= ctk_dump_item(item
, &ctx
);
628 ctk_dump(CFTypeRef secClass
, const char *name
, const char *tid
)
630 OSStatus stat
= errSecSuccess
;
631 CFDictionaryRef query
= NULL
;
632 CFTypeRef result
= NULL
;
634 const void *keys
[] = {
638 kSecReturnAttributes
,
641 const void *values
[] = {
644 kSecAttrAccessGroupToken
,
648 // Query attributes of items of the requested secClass.
649 query
= CFDictionaryCreate(kCFAllocatorDefault
,
652 sizeof(values
) / sizeof(values
[0]),
653 &kCFTypeDictionaryKeyCallBacks
,
654 &kCFTypeDictionaryValueCallBacks
);
657 CFMutableDictionaryRef updatedQuery
= CFDictionaryCreateMutableCopy(kCFAllocatorDefault
, CFDictionaryGetCount(query
) + 1, query
);
658 CFStringRef tidStr
= CFStringCreateWithCString(kCFAllocatorDefault
, tid
, kCFStringEncodingUTF8
);
659 CFDictionaryAddValue(updatedQuery
, kSecAttrTokenID
, tidStr
);
663 query
= updatedQuery
;
666 stat
= SecItemCopyMatching(query
, (CFTypeRef
*)&result
);
668 sec_error("SecItemCopyMatching: %x (%d) - %s",
669 stat
, stat
, sec_errstr(stat
));
673 // We expect an array of dictionaries containing item attributes as result.
674 if(CFGetTypeID(result
) == CFArrayGetTypeID()) {
675 stat
= ctk_dump_items((CFArrayRef
)result
, secClass
, name
);
677 stat
= errSecInternalComponent
;
693 ctk_export(int argc
, char * const *argv
)
695 OSStatus stat
= errSecSuccess
;
697 ItemSpec itemSpec
= IS_All
;
698 const char *tid
= NULL
;
701 while ((ch
= getopt(argc
, argv
, "i:t:h")) != -1) {
704 if(!strcmp("certs", optarg
)) {
707 else if(!strcmp("privKeys", optarg
)) {
708 itemSpec
= IS_PrivKeys
;
710 else if(!strcmp("identities", optarg
)) {
711 itemSpec
= IS_Identities
;
713 else if(!strcmp("all", optarg
)) {
717 return 2; /* @@@ Return 2 triggers usage message. */
726 return 2; /* @@@ Return 2 triggers usage message. */
730 CFTypeRef classes
[] = { kSecClassCertificate
, kSecClassKey
, kSecClassIdentity
};
731 const char* names
[] = { "certificate", "private key", "identity" };
732 ItemSpec specs
[] = { IS_Certs
, IS_PrivKeys
, IS_Identities
};
734 for(int i
= 0; i
< sizeof(classes
)/sizeof(classes
[0]); i
++) {
735 if(specs
[i
] == itemSpec
|| itemSpec
== IS_All
) {
736 stat
= ctk_dump(classes
[i
], names
[i
], tid
);