kcExport.cpp   [plain text]


/*
 * kcExport.cpp - export keychain items using SecKeychainItemExport
 */
 
#include <Security/Security.h>
#include <Security/SecImportExport.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utilLib/common.h>

static void usage(char **argv)
{
    printf("Usage: %s keychain [option ...]\n", argv[0]);
	printf("Options:\n");
	printf("   -t <type>       itemType = certs|allKeys|pubKeys|privKeys|identities|all\n");
	printf("                   ...default itemType=all\n");
	printf("   -f <format>     format = openssl|openssh1|openssh2|bsafe|pkcs7|pkcs8|pkcs12|pemseq\n"
	       "                            ...default itemType is pemseq for aggregate, openssl\n"
		   "                            for single \n");
	printf("   -p              PEM encode\n");
	printf("   -w              Private keys are wrapped\n");
	printf("   -o outFileName  (default is stdout)\n");
	printf("   -z passphrase   (for PKCS12 and wrapped keys only)\n");
	printf("   -Z              Use secure passphrase\n");
	printf("   -q              Quiet\n");
	printf("   -h              help\n");
	exit(1);
}

typedef enum {
	IS_Certs,
	IS_AllKeys,
	IS_PubKeys,
	IS_PrivKeys,
	IS_Identities,
	IS_All
} ItemSpec;

/* 
 * Add all itmes of specified class from a keychain to an array.
 * Item class are things like kSecCertificateItemClass, and
 * CSSM_DL_DB_RECORD_PRIVATE_KEY. Identities are searched separately.
 */
static OSStatus addKcItems(
	SecKeychainRef kcRef,
	SecItemClass itemClass,		// kSecCertificateItemClass
	CFMutableArrayRef outArray)
{
	OSStatus ortn;
	SecKeychainSearchRef srchRef;
	
	ortn = SecKeychainSearchCreateFromAttributes(kcRef,
		itemClass,
		NULL,		// no attrs
		&srchRef);
	if(ortn) {
		cssmPerror("SecKeychainSearchCreateFromAttributes", ortn);
		return ortn;
	}
	for(;;) {
		SecKeychainItemRef itemRef;
		ortn = SecKeychainSearchCopyNext(srchRef, &itemRef);
		if(ortn) {
			if(ortn == errSecItemNotFound) {
				/* normal search end */
				ortn = noErr;
			}
			else {
				cssmPerror("SecKeychainSearchCopyNext", ortn);
			}
			break;
		}
		CFArrayAppendValue(outArray, itemRef);
		CFRelease(itemRef);		// array owns it
	}
	CFRelease(srchRef);
	return ortn;
}

/* 
 * Add all SecIdentityRefs from a keychain into an array.
 */
static OSStatus addIdentities(
	SecKeychainRef kcRef,
	CFMutableArrayRef outArray)
{
	/* Search for all identities */
	SecIdentitySearchRef srchRef;
	OSStatus ortn = SecIdentitySearchCreate(kcRef, 
		0,				// keyUsage - any
		&srchRef);
	if(ortn) {
		cssmPerror("SecIdentitySearchCreate", ortn);
		return ortn;
	}
	
	do {
		SecIdentityRef identity;
		ortn = SecIdentitySearchCopyNext(srchRef, &identity);
		if(ortn) {
			if(ortn == errSecItemNotFound) {
				/* normal search end */
				ortn = noErr;
			}
			else {
				cssmPerror("SecIdentitySearchCopyNext", ortn);
			}
			break;
		}
		CFArrayAppendValue(outArray, identity);
		
		/* the array has the retain count we need */
		CFRelease(identity);
	} while(ortn == noErr);
	CFRelease(srchRef);
	return ortn;
}

int main(int argc, char **argv)
{
	if(argc < 4) {
		usage(argv);
	}
	
	const char *kcName = argv[1];
	SecKeychainRef kcRef = NULL;
	OSStatus ortn = SecKeychainOpen(kcName, &kcRef);
	if(ortn) {
		cssmPerror("SecKeychainOpen", ortn);
		exit(1);
	}
	
	/* user specified options */
	ItemSpec itemSpec = IS_All;		// default
	SecExternalFormat exportForm = kSecFormatUnknown;
	bool pemEncode = false;
	const char *outFile = NULL;
	CFStringRef passphrase = NULL;
	bool securePassphrase = false;
	bool quiet = false;
	bool wrapPrivKeys = false;
	
	extern int optind;
	extern char *optarg;
	int arg;
	optind = 2;
	
	while ((arg = getopt(argc, argv, "t:f:po:z:Zhqw")) != -1) {
		switch (arg) {
			case 't':
				if(!strcmp("certs", optarg)) {
					itemSpec = IS_Certs;
				}
				else if(!strcmp("allKeys", optarg)) {
					itemSpec = IS_AllKeys;
				}
				else if(!strcmp("pubKeys", optarg)) {
					itemSpec = IS_PubKeys;
				}
				else if(!strcmp("privKeys", optarg)) {
					itemSpec = IS_PrivKeys;
				}
				else if(!strcmp("identities", optarg)) {
					itemSpec = IS_Identities;
				}
				else if(!strcmp("all", optarg)) {
					itemSpec = IS_All;
				}
				else {
					usage(argv);
				}
				break;
			case 'f':
				if(!strcmp("openssl", optarg)) {
					exportForm = kSecFormatOpenSSL;
				}
				else if(!strcmp("openssh1", optarg)) {
					exportForm = kSecFormatSSH;
				}
				else if(!strcmp("openssh2", optarg)) {
					exportForm = kSecFormatSSHv2;
				}
				else if(!strcmp("bsafe", optarg)) {
					exportForm = kSecFormatBSAFE;
				}
				else if(!strcmp("pkcs7", optarg)) {
					exportForm = kSecFormatPKCS7;
				}
				else if(!strcmp("pkcs8", optarg)) {
					exportForm = kSecFormatWrappedPKCS8;
				}
				else if(!strcmp("pkcs12", optarg)) {
					exportForm = kSecFormatPKCS12;
				}
				else if(!strcmp("pemseq", optarg)) {
					exportForm = kSecFormatPEMSequence;
				}
				else {
					usage(argv);
				}
				break;
			case 'p':
				pemEncode = true;
				break;
			case 'o':
				outFile = optarg;
				break;
			case 'z':
				passphrase = CFStringCreateWithCString(NULL, optarg,
					kCFStringEncodingASCII);
				break;
			case 'Z':
				securePassphrase = true;
				break;
			case 'w':
				wrapPrivKeys = true;
				break;
			case 'q':
				quiet = true;
				break;
			case '?':
			case 'h':
				default:
					usage(argv);
		}
		
	}
	if(optind != argc) {
		/* getopt does not return '?' */
		usage(argv);
	}
	if(wrapPrivKeys) {
		switch(exportForm) {
			case kSecFormatOpenSSL:
			case kSecFormatUnknown:		// i.e., use default
				exportForm = kSecFormatWrappedOpenSSL;
				break;
			case kSecFormatSSH:
				exportForm = kSecFormatWrappedSSH;
				break;
			case kSecFormatSSHv2:
				/* there is no wrappedSSHv2 */
				exportForm = kSecFormatWrappedOpenSSL;
				break;
			case kSecFormatWrappedPKCS8:
				/* proceed */
				break;
			default:
				printf("Don't know how to wrap in specified format/type.\n");
				exit(1);
		}
	}
	
	/* gather items */
	CFMutableArrayRef exportItems = CFArrayCreateMutable(NULL, 0, 
		&kCFTypeArrayCallBacks);
	switch(itemSpec) {
		case IS_Certs:
			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
			
		case IS_PrivKeys:
			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
			
		case IS_PubKeys:
			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
			
		case IS_AllKeys:
			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
			if(ortn) {
				exit(1);
			}
			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PUBLIC_KEY, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
			
		case IS_All:
			/* No public keys here - PKCS12 doesn't support them */
			ortn = addKcItems(kcRef, kSecCertificateItemClass, exportItems);
			if(ortn) {
				exit(1);
			}
			ortn = addKcItems(kcRef, CSSM_DL_DB_RECORD_PRIVATE_KEY, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
			
		case IS_Identities:
			ortn = addIdentities(kcRef, exportItems);
			if(ortn) {
				exit(1);
			}
			break;
		default:
			printf("Huh? Bogus itemSpec!\n");
			exit(1);
	}
	
	CFIndex numItems = CFArrayGetCount(exportItems);
	
	if(exportForm == kSecFormatUnknown) {
		/* Use default export format per set of items */
		if(numItems > 1) {
			exportForm = kSecFormatPEMSequence;
		}
		else {
			exportForm = kSecFormatOpenSSL;
		}
	}
	uint32 expFlags = 0;		// SecItemImportExportFlags
	if(pemEncode) {
		expFlags |= kSecItemPemArmour;
	}
	
	/* optional key related arguments */
	SecKeyImportExportParameters keyParams;
	SecKeyImportExportParameters *keyParamPtr = NULL;
	if((passphrase != NULL) || securePassphrase) {
		memset(&keyParams, 0, sizeof(keyParams));
		keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
		if(securePassphrase) {
			/* give this precedence */
			keyParams.flags |= kSecKeySecurePassphrase;
		}
		else {
			keyParams.passphrase = passphrase;		// may be NULL
		}
		keyParamPtr = &keyParams;
	}
	
	/* GO */
	CFDataRef outData = NULL;
	ortn = SecKeychainItemExport(exportItems, exportForm, expFlags, keyParamPtr,
		&outData);
	if(ortn) {
		cssmPerror("SecKeychainItemExport", ortn);
		exit(1);
	}
		
	unsigned len = CFDataGetLength(outData);
	if(outFile) {
		int rtn = writeFile(outFile, CFDataGetBytePtr(outData), len);
		if(rtn == 0) {
			if(!quiet) {
				printf("...%u bytes written to %s\n", len, outFile);
			}
		}
		else {
			printf("***Error writing to %s\n", outFile);
		}
	}
	else {
		int irtn = write(STDOUT_FILENO, CFDataGetBytePtr(outData), len);
		if(irtn != (int)len) {
			perror("write");
		}
	}
	if(!quiet) {
		fprintf(stderr, "\n%u items exported.\n", (unsigned)numItems);
	}
	if(exportItems) {
		/* FIXME this in conjunction with the release of the KC crashes */
		CFRelease(exportItems);
	}
	if(passphrase) {
		CFRelease(passphrase);
	}
	if(outData) {
		CFRelease(outData);
	}
	if(kcRef) {
		CFRelease(kcRef);
	}
	return 0;
}