/ SecurityTool / macOS / keychain_export.m
keychain_export.m
  1  /*
  2   * Copyright (c) 2003-2004,2006,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_export.c
 24   */
 25  
 26  #import <Foundation/Foundation.h>
 27  
 28  #include "keychain_export.h"
 29  #include "keychain_utilities.h"
 30  #include "security_tool.h"
 31  
 32  #include <errno.h>
 33  #include <string.h>
 34  #include <unistd.h>
 35  #include <Security/SecImportExport.h>
 36  #include <Security/SecKeychainItem.h>
 37  #include <Security/SecKeychainSearch.h>
 38  #include <Security/SecIdentitySearch.h>
 39  #include <Security/SecKey.h>
 40  #include <Security/SecKeyPriv.h>
 41  #include <Security/SecCertificate.h>
 42  #include <Security/SecCertificatePriv.h>
 43  #include <Security/SecItem.h>
 44  #include <Security/SecAccessControl.h>
 45  #include <Security/SecAccessControlPriv.h>
 46  #include <security_cdsa_utils/cuFileIo.h>
 47  #include <CoreFoundation/CoreFoundation.h>
 48  #include <stdio.h>
 49  
 50  typedef enum {
 51  	IS_Certs,
 52  	IS_AllKeys,
 53  	IS_PubKeys,
 54  	IS_PrivKeys,
 55  	IS_Identities,
 56  	IS_All
 57  } ItemSpec;
 58  
 59  /*
 60   * Add all itmes of specified class from a keychain to an array.
 61   * Item class are things like kSecCertificateItemClass, and
 62   * CSSM_DL_DB_RECORD_PRIVATE_KEY. Identities are searched separately.
 63   */
 64  static OSStatus addKcItems(
 65  	SecKeychainRef kcRef,
 66  	SecItemClass itemClass,		// kSecCertificateItemClass
 67  	CFMutableArrayRef outArray,
 68  	unsigned *numItems)			// UPDATED on return
 69  {
 70  	OSStatus ortn;
 71  	SecKeychainSearchRef srchRef;
 72  
 73  	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
 74  		itemClass,
 75  		NULL,		// no attrs
 76  		&srchRef);
 77  	if(ortn) {
 78  		sec_perror("SecKeychainSearchCreateFromAttributes", ortn);
 79  		return ortn;
 80  	}
 81  	for(;;) {
 82  		SecKeychainItemRef itemRef;
 83  		ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
 84  		if(ortn) {
 85  			if(ortn == errSecItemNotFound) {
 86  				/* normal search end */
 87  				ortn = noErr;
 88  			}
 89  			else {
 90  				sec_perror("SecIdentitySearchCopyNext", ortn);
 91  			}
 92  			break;
 93  		}
 94  		CFArrayAppendValue(outArray, itemRef);
 95  		CFRelease(itemRef);		// array owns the item
 96  		(*numItems)++;
 97  	}
 98  	CFRelease(srchRef);
 99  	return ortn;
100  }
101  
102  /*
103   * Add all SecIdentityRefs from a keychain into an array.
104   */
105  static OSStatus addIdentities(
106  	SecKeychainRef kcRef,
107  	CFMutableArrayRef outArray,
108  	unsigned *numItems)			// UPDATED on return
109  {
110  	/* Search for all identities */
111  	SecIdentitySearchRef srchRef;
112  	OSStatus ortn = SecIdentitySearchCreate(kcRef,
113  		0,				// keyUsage - any
114  		&srchRef);
115  	if(ortn) {
116  		sec_perror("SecIdentitySearchCreate", ortn);
117  		return ortn;
118  	}
119  
120  	do {
121  		SecIdentityRef identity;
122  		ortn = SecIdentitySearchCopyNext(srchRef, &identity);
123  		if(ortn) {
124  			if(ortn == errSecItemNotFound) {
125  				/* normal search end */
126  				ortn = noErr;
127  			}
128  			else {
129  				sec_perror("SecIdentitySearchCopyNext", ortn);
130  			}
131  			break;
132  		}
133  		CFArrayAppendValue(outArray, identity);
134  
135  		/* the array has the retain count we need */
136  		CFRelease(identity);
137  		(*numItems)++;
138  	} while(ortn == noErr);
139  	CFRelease(srchRef);
140  	return ortn;
141  }
142  
143  static int do_keychain_export(
144  	SecKeychainRef		kcRef,
145  	SecExternalFormat   externFormat,
146  	ItemSpec			itemSpec,
147  	const char			*passphrase,
148  	int					doPem,
149  	const char			*fileName)
150  {
151  	int result = 0;
152  	CFIndex numItems;
153  	unsigned numPrivKeys = 0;
154  	unsigned numPubKeys = 0;
155  	unsigned numCerts = 0;
156  	unsigned numIdents = 0;
157  	OSStatus ortn;
158  	uint32 expFlags = 0;		// SecItemImportExportFlags
159  	SecKeyImportExportParameters keyParams;
160  	CFStringRef	passStr = NULL;
161  	CFDataRef outData = NULL;
162  	size_t len;
163  
164  	/* gather items */
165  	CFMutableArrayRef exportItems = CFArrayCreateMutable(NULL, 0,
166  		&kCFTypeArrayCallBacks);
167  	switch(itemSpec) {
168  		case IS_Certs:
169  			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems, &numCerts);
170  			if(ortn) {
171  				result = 1;
172  				goto loser;
173  			}
174  			break;
175  
176  		case IS_PrivKeys:
177  			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
178  				&numPrivKeys);
179  			if(ortn) {
180  				result = 1;
181  				goto loser;
182  			}
183  			break;
184  
185  		case IS_PubKeys:
186  			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems,
187  				&numPubKeys);
188  			if(ortn) {
189  				result = 1;
190  				goto loser;
191  			}
192  			break;
193  
194  		case IS_AllKeys:
195  			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
196  				&numPrivKeys);
197  			if(ortn) {
198  				result = 1;
199  				goto loser;
200  			}
201  			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems,
202  				&numPubKeys);
203  			if(ortn) {
204  				result = 1;
205  				goto loser;
206  			}
207  			break;
208  
209  		case IS_All:
210  			/* No public keys here - PKCS12 doesn't support them */
211  			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems, &numCerts);
212  			if(ortn) {
213  				result = 1;
214  				goto loser;
215  			}
216  			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems,
217  				&numPrivKeys);
218  			if(ortn) {
219  				result = 1;
220  				goto loser;
221  			}
222  			break;
223  
224  		case IS_Identities:
225  			ortn = addIdentities(kcRef, exportItems, &numIdents);
226  			if(ortn) {
227  				result = 1;
228  				goto loser;
229  			}
230  			if(numIdents) {
231  				numPrivKeys += numIdents;
232  				numCerts    += numIdents;
233  			}
234  			break;
235  		default:
236  			sec_error("Internal error parsing item_spec");
237  			result = 1;
238  			goto loser;
239  	}
240  
241  	numItems = CFArrayGetCount(exportItems);
242  	if(externFormat == kSecFormatUnknown) {
243  		/* Use default export format per set of items */
244  		if(numItems > 1) {
245  			externFormat = kSecFormatPEMSequence;
246  		}
247  		else if(numCerts) {
248  			externFormat = kSecFormatX509Cert;
249  		}
250  		else {
251  			externFormat = kSecFormatOpenSSL;
252  		}
253  	}
254  	if(doPem) {
255  		expFlags |= kSecItemPemArmour;
256  	}
257  
258  	/*
259  	 * Key related arguments, ignored if we're not exporting keys.
260  	 * Always specify some kind of passphrase - default is secure passkey.
261  	 */
262  	memset(&keyParams, 0, sizeof(keyParams));
263  	keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
264  	if(passphrase != NULL) {
265  		passStr = CFStringCreateWithCString(NULL, passphrase, kCFStringEncodingASCII);
266  		keyParams.passphrase = passStr;
267  	}
268  	else {
269  		keyParams.flags = kSecKeySecurePassphrase;
270  	}
271  
272  	/* Go */
273  	ortn = SecKeychainItemExport(exportItems, externFormat, expFlags, &keyParams,
274  		&outData);
275  	if(ortn) {
276  		sec_perror("SecKeychainItemExport", ortn);
277  		result = 1;
278  		goto loser;
279  	}
280  
281  	len = CFDataGetLength(outData);
282  	if(fileName) {
283  		int rtn = writeFileSizet(fileName, CFDataGetBytePtr(outData), len);
284  		if(rtn == 0) {
285  			if(!do_quiet) {
286  				fprintf(stderr, "...%lu bytes written to %s\n", len, fileName);
287  			}
288  		}
289  		else {
290  			sec_error("Error writing to %s: %s", fileName, strerror(errno));
291  			result = 1;
292  		}
293  	}
294  	else {
295  		size_t irtn = write(STDOUT_FILENO, CFDataGetBytePtr(outData), len);
296  		if(irtn != (size_t)len) {
297  			perror("write");
298  		}
299  	}
300  loser:
301  	if(exportItems) {
302  		CFRelease(exportItems);
303  	}
304  	if(passStr) {
305  		CFRelease(passStr);
306  	}
307  	if(outData) {
308  		CFRelease(outData);
309  	}
310  	return result;
311  }
312  
313  int
314  keychain_export(int argc, char * const *argv)
315  {
316  	int ch, result = 0;
317  
318  	char *outFile = NULL;
319  	char *kcName = NULL;
320  	SecKeychainRef kcRef = NULL;
321  	SecExternalFormat externFormat = kSecFormatUnknown;
322  	ItemSpec itemSpec = IS_All;
323  	int wrapped = 0;
324  	int doPem = 0;
325  	const char *passphrase = NULL;
326  
327      while ((ch = getopt(argc, argv, "k:o:t:f:P:wph")) != -1)
328  	{
329  		switch  (ch)
330  		{
331  		case 'k':
332  			kcName = optarg;
333  			break;
334  		case 'o':
335  			outFile = optarg;
336  			break;
337  		case 't':
338  			if(!strcmp("certs", optarg)) {
339  				itemSpec = IS_Certs;
340  			}
341  			else if(!strcmp("allKeys", optarg)) {
342  				itemSpec = IS_AllKeys;
343  			}
344  			else if(!strcmp("pubKeys", optarg)) {
345  				itemSpec = IS_PubKeys;
346  			}
347  			else if(!strcmp("privKeys", optarg)) {
348  				itemSpec = IS_PrivKeys;
349  			}
350  			else if(!strcmp("identities", optarg)) {
351  				itemSpec = IS_Identities;
352  			}
353  			else if(!strcmp("all", optarg)) {
354  				itemSpec = IS_All;
355  			}
356  			else {
357  				return SHOW_USAGE_MESSAGE;
358  			}
359  			break;
360  		case 'f':
361  			if(!strcmp("openssl", optarg)) {
362  				externFormat = kSecFormatOpenSSL;
363  			}
364  			else if(!strcmp("openssh1", optarg)) {
365  				externFormat = kSecFormatSSH;
366  			}
367  			else if(!strcmp("openssh2", optarg)) {
368  				externFormat = kSecFormatSSHv2;
369  			}
370  			else if(!strcmp("bsafe", optarg)) {
371  				externFormat = kSecFormatBSAFE;
372  			}
373  			else if(!strcmp("raw", optarg)) {
374  				externFormat = kSecFormatRawKey;
375  			}
376  			else if(!strcmp("pkcs7", optarg)) {
377  				externFormat = kSecFormatPKCS7;
378  			}
379  			else if(!strcmp("pkcs8", optarg)) {
380  				externFormat = kSecFormatWrappedPKCS8;
381  			}
382  			else if(!strcmp("pkcs12", optarg)) {
383  				externFormat = kSecFormatPKCS12;
384  			}
385  			else if(!strcmp("netscape", optarg)) {
386  				externFormat = kSecFormatNetscapeCertSequence;
387  			}
388  			else if(!strcmp("x509", optarg)) {
389  				externFormat = kSecFormatX509Cert;
390  			}
391  			else if(!strcmp("pemseq", optarg)) {
392  				externFormat = kSecFormatPEMSequence;
393  			}
394  			else {
395  				return SHOW_USAGE_MESSAGE;
396  			}
397  			break;
398  		case 'w':
399  			wrapped = 1;
400  			break;
401  		case 'p':
402  			doPem = 1;
403  			break;
404  		case 'P':
405  			passphrase = optarg;
406  			break;
407  		case '?':
408  		default:
409  			return SHOW_USAGE_MESSAGE;
410  		}
411  	}
412  
413  	if(wrapped) {
414  		switch(externFormat) {
415  			case kSecFormatOpenSSL:
416  			case kSecFormatUnknown:		// i.e., use default
417  				externFormat = kSecFormatWrappedOpenSSL;
418  				break;
419  			case kSecFormatSSH:
420  				externFormat = kSecFormatWrappedSSH;
421  				break;
422  			case kSecFormatSSHv2:
423  				/* there is no wrappedSSHv2 */
424  				externFormat = kSecFormatWrappedOpenSSL;
425  				break;
426  			case kSecFormatWrappedPKCS8:
427  				/* proceed */
428  				break;
429  			default:
430  				sec_error("Don't know how to wrap in specified format/type");
431  				return SHOW_USAGE_MESSAGE;
432  		}
433  	}
434  
435  	if(kcName) {
436  		kcRef = keychain_open(kcName);
437  		if(kcRef == NULL) {
438  			return 1;
439  		}
440  	}
441  	result = do_keychain_export(kcRef, externFormat, itemSpec,
442  		passphrase, doPem, outFile);
443  
444  	if(kcRef) {
445  		CFRelease(kcRef);
446  	}
447  	return result;
448  }
449  
450  typedef struct {
451      CFMutableStringRef str;
452  } ctk_dict2str_context;
453  
454  
455  static void
456  ctk_obj_to_str(CFTypeRef obj, char *buf, int bufLen, Boolean key);
457  
458  static void
459  ctk_dict2str(const void *key, const void *value, void *context)
460  {
461      char keyBuf[64] = { 0 };
462      ctk_obj_to_str(key, keyBuf, sizeof(keyBuf), true);
463  
464      char valueBuf[1024] = { 0 };
465      ctk_obj_to_str(value, valueBuf, sizeof(valueBuf), false);
466  
467      CFStringRef str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("\n\t\t\t%s : %s,"), keyBuf, valueBuf);
468      CFStringAppend(((ctk_dict2str_context *)context)->str, str);
469      CFRelease(str);
470  }
471  
472  static void
473  ctk_obj_to_str(CFTypeRef obj, char *buf, int bufLen, Boolean key)
474  {
475      CFStringRef str = NULL;
476  
477      if(CFGetTypeID(obj) == CFStringGetTypeID()) {
478          // CFStringRef - print the string as is (for keys) or quoted (values)
479          str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, key ? CFSTR("%@") : CFSTR("\"%@\""), obj);
480      } else if(CFGetTypeID(obj) == CFNumberGetTypeID()) {
481          // CFNumber - print the value using current locale
482          CFNumberRef num = (CFNumberRef)obj;
483  
484          CFLocaleRef locale = CFLocaleCopyCurrent();
485          CFNumberFormatterRef fmt = CFNumberFormatterCreate(kCFAllocatorDefault, locale, kCFNumberFormatterDecimalStyle);
486          CFRelease(locale);
487          
488          str = CFNumberFormatterCreateStringWithNumber(kCFAllocatorDefault, fmt, num);
489          CFRelease(fmt);
490      } else if(CFGetTypeID(obj) == CFDataGetTypeID()) {
491          // CFData - print the data as <hex bytes>
492          CFDataRef data = (CFDataRef)obj;
493  
494          CFMutableStringRef hexStr = CFStringCreateMutable(kCFAllocatorDefault, CFDataGetLength(data) * 3);
495          
496          for(int i = 0; i < CFDataGetLength(data); i++) {
497              CFStringRef hexByte = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%02x "), *(CFDataGetBytePtr(data) + i));
498              CFStringAppend(hexStr, hexByte);
499              CFRelease(hexByte);
500          }
501  
502          // Get rid of the last excessive space.
503          if(CFDataGetLength(data)) {
504              CFStringDelete(hexStr, CFRangeMake(CFStringGetLength(hexStr) - 1, 1));
505          }
506  
507          str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%@>"), hexStr);
508          CFRelease(hexStr);
509      } else if(CFGetTypeID(obj) == CFBooleanGetTypeID()) {
510          // CFBoolean - print true/false
511          CFBooleanRef cfbool = (CFBooleanRef)obj;
512  
513          str = CFStringCreateWithCString(kCFAllocatorDefault, CFBooleanGetValue(cfbool) ? "true" : "false", kCFStringEncodingUTF8);
514      } else if(CFGetTypeID(obj) == SecAccessControlGetTypeID()) {
515          // SecAccessControlRef - print the constraints dictionary
516          SecAccessControlRef ac = (SecAccessControlRef)obj;
517  
518          CFDictionaryRef constraints = SecAccessControlGetConstraints(ac);
519          CFMutableStringRef constraintsStr = CFStringCreateMutable(kCFAllocatorDefault, 1024);
520          if(constraints && CFDictionaryGetCount(constraints)) {
521              ctk_dict2str_context context;
522              context.str = constraintsStr;
523              CFDictionaryApplyFunction(constraints, ctk_dict2str, &context);
524              CFStringReplace(constraintsStr, CFRangeMake(CFStringGetLength(constraintsStr) - 1, 1), CFSTR("\n\t\t"));
525          }
526  
527          CFDictionaryRef protection = SecAccessControlGetProtection(ac);
528          CFMutableStringRef protectionStr = CFStringCreateMutable(kCFAllocatorDefault, 512);
529          if(protection && CFDictionaryGetCount(protection)) {
530              ctk_dict2str_context context;
531              context.str = protectionStr;
532              CFDictionaryApplyFunction(protection, ctk_dict2str, &context);
533              CFStringReplace(protectionStr, CFRangeMake(CFStringGetLength(protectionStr) - 1, 1), CFSTR("\n\t\t"));
534          }
535  
536          str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("constraints: {%@}\n\t\tprotection: {%@}"), constraintsStr, protectionStr);
537          CFRelease(constraintsStr);
538          CFRelease(protectionStr);
539      }
540  
541      // Fill the provided buffer with the converted string.
542      if(str) {
543          Boolean success = CFStringGetCString(str, buf, bufLen, kCFStringEncodingUTF8);
544          CFRelease(str);
545  
546          if(success) {
547              return;
548          }
549      }
550  
551      // Use object description as fallback...
552      CFStringRef description = CFCopyDescription(obj);
553      if(!CFStringGetCString(description, buf, bufLen, kCFStringEncodingUTF8)) {
554          // ...or else we don't know.
555          strncpy(buf, "<?>", bufLen);
556      }
557  
558      CFRelease(description);
559  }
560  
561  typedef struct {
562      int i;
563      NSString *name;
564  } ctk_print_context;
565  
566  OSStatus
567  ctk_dump_item(CFTypeRef item, ctk_print_context *ctx);
568  
569  static void
570  ctk_print_dict(const void *key, const void *value, void *context)
571  {
572      char keyBuf[64] = { 0 };
573      ctk_obj_to_str(key, keyBuf, sizeof(keyBuf), true);
574  
575      char valueBuf[1024] = { 0 };
576      ctk_obj_to_str(value, valueBuf, sizeof(valueBuf), false);
577  
578      printf("\t%s : %s\n", keyBuf, valueBuf);
579  }
580  
581  static void
582  ctk_dump_item_header(ctk_print_context *ctx)
583  {
584      printf("\n");
585      printf("==== %s #%d\n", ctx->name.UTF8String, ctx->i);
586  }
587  
588  static void
589  ctk_dump_item_footer(ctk_print_context *ctx)
590  {
591      printf("====\n");
592  }
593  
594  OSStatus
595  ctk_dump_item(CFTypeRef item, ctk_print_context *ctx)
596  {
597      OSStatus stat = errSecSuccess;
598  
599      CFTypeID tid = CFGetTypeID(item);
600      if(tid == CFDictionaryGetTypeID()) {
601          // We expect a dictionary containing item attributes.
602          ctk_dump_item_header(ctx);
603          CFDictionaryApplyFunction((CFDictionaryRef)item, ctk_print_dict, ctx);
604          ctk_dump_item_footer(ctx);
605      } else {
606          stat = errSecInternalComponent;
607          printf("Unexpected item type ID: %lu\n", tid);
608      }
609  
610      return stat;
611  }
612  
613  static OSStatus
614  ctk_dump_items(CFArrayRef items, id secClass, NSString *name)
615  {
616      OSStatus stat = errSecSuccess;
617  
618      ctk_print_context ctx = { 1, name };
619  
620      for(CFIndex i = 0; i < CFArrayGetCount(items); i++) {
621          CFTypeRef item = CFArrayGetValueAtIndex(items, i);
622          stat = ctk_dump_item(item, &ctx);
623          ctx.i++;
624  
625          if(stat) {
626              break;
627          }
628      }
629  
630      return stat;
631  }
632  
633  static void
634  exportData(NSData *dataToExport, NSString *fileName, NSString *exportPath, NSString *elementName) {
635      NSMutableString *pem = [NSMutableString new];
636      [pem appendString:[NSString stringWithFormat:@"-----BEGIN %@-----\n", elementName]];
637      NSString *base64Cert = [dataToExport base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength | NSDataBase64EncodingEndLineWithLineFeed];
638      [pem appendString:base64Cert];
639      [pem appendString:[NSString stringWithFormat:@"\n-----END %@-----\n", elementName]];
640      
641      NSString *fullName = [NSString stringWithFormat:@"%@/%@.pem", exportPath, fileName];
642      NSError *error;
643      [pem writeToFile:fullName atomically:YES encoding:NSUTF8StringEncoding error:&error];
644      if (error) {
645          fprintf(stderr, "%s\n", [NSString stringWithFormat:@"%@", error].UTF8String);
646      }
647  }
648  
649  static OSStatus
650  ctk_dump(id secClass, NSString *name, NSString *tid, NSString *exportPath)
651  {
652      NSArray *result;
653      BOOL returnRef = NO;
654  
655      if ([secClass isEqual:(id)kSecClassIdentity] || [secClass isEqual:(id)kSecClassCertificate])
656          returnRef = YES;
657  
658      NSDictionary *query = @{
659          (id)kSecClass : secClass,
660          (id)kSecMatchLimit : (id)kSecMatchLimitAll,
661          (id)kSecAttrAccessGroup : (id)kSecAttrAccessGroupToken,
662          (id)kSecReturnAttributes : @YES,
663          (id)kSecReturnRef : @(returnRef)
664      };
665  
666      if(tid) {
667          NSMutableDictionary *updatedQuery = [NSMutableDictionary dictionaryWithDictionary:query];
668          updatedQuery[(id)kSecAttrTokenID] = tid;
669          query = updatedQuery;
670      }
671  
672      OSStatus stat = SecItemCopyMatching((__bridge CFTypeRef)query, (void *)&result);
673      if(stat) {
674          if (stat == errSecItemNotFound) {
675              fprintf(stderr, "No items found.\n");
676          } else {
677              sec_error("SecItemCopyMatching: %x (%d) - %s", stat, stat, sec_errstr(stat));
678          }
679          return stat;
680      }
681  
682      // We expect an array of dictionaries containing item attributes as result.
683      if([result isKindOfClass:[NSArray class]]) {
684          if (returnRef) {
685              NSMutableArray *updatedResult = [NSMutableArray array];
686              for (NSDictionary *dict in result) {
687                  NSMutableDictionary *updatedItem = [NSMutableDictionary dictionaryWithDictionary:dict];
688                  id itemRef = updatedItem[(id)kSecValueRef];
689                  if ([secClass isEqual:(id)kSecClassIdentity]) {
690                      id certificateRef;
691                      if (SecIdentityCopyCertificate((__bridge SecIdentityRef)itemRef, (void *)&certificateRef) != errSecSuccess)
692                          continue;
693                      itemRef = certificateRef;
694                  }
695  
696                  NSData *certDigest = (__bridge NSData*)SecCertificateGetSHA1Digest((__bridge SecCertificateRef)itemRef);
697                  updatedItem[@"sha1"] = certDigest;
698                  [updatedItem removeObjectForKey:(id)kSecValueRef];
699                  [updatedResult addObject:updatedItem];
700                  
701                  if (exportPath) {
702                      NSData *certData = (__bridge_transfer NSData *)SecCertificateCopyData((__bridge SecCertificateRef)itemRef);
703                      exportData(certData, updatedItem[(id)kSecAttrLabel], exportPath, @"CERTIFICATE");
704                      id publicKey = (__bridge_transfer id)SecCertificateCopyKey((__bridge SecCertificateRef)itemRef);
705                      NSData *pubKeyInfo = (__bridge_transfer NSData *)SecKeyCopySubjectPublicKeyInfo((__bridge SecKeyRef)publicKey);
706                      exportData(pubKeyInfo, [NSString stringWithFormat:@"Public Key - %@", updatedItem[(id)kSecAttrLabel]], exportPath, @"PUBLIC KEY");
707                  }
708              }
709              result = updatedResult;
710          }
711  
712          if (!exportPath) {
713              stat = ctk_dump_items((__bridge CFArrayRef)result, secClass, name);
714          }
715      } else {
716          stat = errSecInternalComponent;
717      }
718      return stat;
719  }
720  
721  int
722  ctk_export(int argc, char * const *argv)
723  {
724      __block OSStatus stat = errSecSuccess;
725  
726      ItemSpec itemSpec = IS_All;
727      NSString *tid;
728      NSString *exportPath;
729      int ch;
730      BOOL optT = NO;
731      BOOL optE = NO;
732  
733      while ((ch = getopt(argc, argv, "i:t:e:h")) != -1) {
734          switch  (ch) {
735              case 't':
736                  if(!strcmp("certs", optarg)) {
737                      itemSpec = IS_Certs;
738                  }
739                  else if(!strcmp("privKeys", optarg)) {
740                      itemSpec = IS_PrivKeys;
741                  }
742                  else if(!strcmp("identities", optarg)) {
743                      itemSpec = IS_Identities;
744                  }
745                  else if(!strcmp("all", optarg)) {
746                      itemSpec = IS_All;
747                  }
748                  else {
749                      return SHOW_USAGE_MESSAGE;
750                  }
751                  optT = YES;
752                  break;
753              case 'i':
754                  tid = [NSString stringWithUTF8String:optarg];
755                  break;
756              case 'e':
757                  exportPath = [NSString stringWithUTF8String:optarg];
758                  itemSpec = IS_Certs;
759                  optE = YES;
760                  break;
761  
762              case '?':
763              default:
764                  return SHOW_USAGE_MESSAGE;
765          }
766      }
767      
768      if (optT && optE) {
769          return SHOW_USAGE_MESSAGE;
770      }
771  
772      NSDictionary<id, NSArray *> *classesAndNames = @{ (id)kSecClassCertificate : @[ @"certificate", @(IS_Certs) ],
773                                                        (id)kSecClassKey : @[ @"private key", @(IS_PrivKeys) ],
774                                                        (id)kSecClassIdentity : @[ @"identity", @(IS_Identities) ] };
775      
776      [classesAndNames enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, NSArray * _Nonnull obj, BOOL * _Nonnull stop) {
777          if (itemSpec == IS_All || itemSpec == ((NSNumber *)obj[1]).unsignedIntegerValue) {
778              stat = ctk_dump(key, obj[0], tid, exportPath);
779              if(stat) {
780                  *stop = YES;
781              }
782          }
783      }];
784  
785      return stat;
786  }