dotMacRequest.cpp   [plain text]


/* 
 * dotMacRequest.cpp - simple illustration of using SecCertificateRequestCreate() and
 *				       SecCertificateRequestSubmit to post a request for a .mac cert. 
 */
#include <Security/Security.h>
#include <Security/SecCertificateRequest.h>
#include <security_dotmac_tp/dotMacTp.h>
#include <clAppUtils/keyPicker.h>
#include <unistd.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>

/*
 * Defaults for the test setup du jour 
 */
#define USER_DEFAULT		"dmitch10"
#define PWD_DEFAULT			"password"
#define HOST_DEFAULT		"certmgmt.mac.com"

/* 
 * Type of cert to request
 */
typedef enum {
	CRT_Identity,		/* actually, now "iChat encryption", not "identity" */
	CRT_EmailSign,
	CRT_EmailEncrypt
} CertRequestType;

static void usage(char **argv)
{
	printf("usage: %s op [options]\n", argv[0]);
	printf("Op:\n");
	printf("    i              -- generate iChat encryption cert\n");
	printf("    s              -- generate email signing cert\n");
	printf("    e              -- generate email encrypting cert\n");
	printf("    I              -- search/retrieve request for iChat encryption cert\n");
	printf("    S              -- search/retrieve request for signing cert\n");
	printf("    E              -- search/retrieve request for encrypting cert\n");
	printf("    p              -- pending request poll (via -u)\n");
	printf("Options:\n");
	printf("   -u username     -- Default is %s\n", USER_DEFAULT);
	printf("   -Z password     -- default is %s\n", PWD_DEFAULT);
	printf("   -p              -- pick key pair from existing (default is generate)\n");
	printf("   -k keychain     -- Source/destination of keys\n");
	printf("   -r              -- Renew (default is new)\n");
	printf("   -a              -- async (default is synchronous)\n");
	printf("   -H hostname     -- Alternate .mac server host name (default %s)\n",
									HOST_DEFAULT);
	printf("   -M              -- Pause for MallocDebug\n");
	printf("   -l              -- loop\n");
	exit(1);
}

/* print a string int he form of a CSSM_DATA */
static void printString(
	const CSSM_DATA *str)
{
	for(unsigned dex=0; dex<str->Length; dex++) {
		printf("%c", str->Data[dex]);
	}
}

/* basic "generate keypair" routine */
static OSStatus genKeyPair(
	SecKeychainRef  kcRef,		// optional, NULL means the default list
	SecKeyRef		*pubKey,	// RETURNED
	SecKeyRef		*privKey)   // RETURNED
{
	OSStatus ortn;
	
	ortn = SecKeyCreatePair(kcRef,
		DOT_MAC_KEY_ALG,
		DOT_MAC_KEY_SIZE,
		0,						// context handle
		/* public key usage and attrs */
		CSSM_KEYUSE_ANY,	
		CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE,
		/* private key usage and attrs */
		CSSM_KEYUSE_ANY,	
		CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE |
			CSSM_KEYATTR_SENSITIVE,
		NULL,					// initial access
		pubKey,
		privKey);
	if(ortn) {
		cssmPerror("SecKeyCreatePair", ortn);
	}
	return ortn;
}

/* max number of oid/value pairs */
#define MAX_ATTRS		5

/* 
 * search for a pending .mac cert request, get current status. 
 */
static OSStatus dotMacGetPendingRequest(
	/* required fields */
	const char			*userName,		// REQUIRED, C string
	const char			*password,		// REQUIRED, C string
	CertRequestType		requestType,

	/* optional fields */
	const char			*hostName,		// C string
	SecKeychainRef		keychain)		// destination of created cert (if !async) 
{
	SecCertificateRequestAttribute		attrs[MAX_ATTRS];
	SecCertificateRequestAttribute		*attrp = attrs;
	SecCertificateRequestAttributeList	attrList;

	attrList.count = 0;
	attrList.attr = attrs;
	
	/* user name */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME;
	attrp->value.Data = (uint8 *)userName;
	attrp->value.Length = strlen(userName);
	attrp++;
	attrList.count++;
	
	/* password */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD;
	attrp->value.Data = (uint8 *)password;
	attrp->value.Length = strlen(password);
	attrp++;
	attrList.count++;
	
	/* options */

	if(hostName) {
		attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME;
		attrp->value.Data = (uint8 *)hostName;
		attrp->value.Length = strlen(hostName);
		attrp++;
		attrList.count++;
	}

	/* map CertRequestType to a policy OID */
	const CSSM_OID *policy;
	switch(requestType) {
		case CRT_Identity:
			policy = &CSSMOID_DOTMAC_CERT_REQ_IDENTITY;
			break;
		case CRT_EmailSign:
			policy = &CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN;
			break;
		case CRT_EmailEncrypt:
			policy = &CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT;
			break;
		default:
			printf("GAK! Bad cert type.\n");
			return -1;
	}
	OSStatus ortn;
	SecCertificateRequestRef certReq = NULL;
	SecCertificateRef certRef = NULL;
	sint32 estTime;
	
	printf("...calling SecCertificateFindRequest\n");
	ortn = SecCertificateFindRequest(policy, 
		CSSM_CERT_X_509v3, 
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
		NULL, NULL,			// no keys needed
		&attrList,
		&certReq);
	if(ortn) {
		cssmPerror("SecCertificateFindRequest", ortn);
		return ortn;
	}
	
	printf("...calling SecCertificateRequestGetResult\n");
	ortn = SecCertificateRequestGetResult(certReq, keychain, &estTime, &certRef);
	if(ortn) {
		cssmPerror("SecCertificateRequestGetResult", ortn);
	}
	else {
		printf("...SecCertificateRequestGetResult succeeded; estTime %d; cert %s\n",
			(int)estTime, certRef ? "OBTAINED" : "NOT OBTAINED");
	}
	if(certRef) {
		CFRelease(certRef);
	}
	if(certReq) {
		CFRelease(certReq);
	}
	return ortn;
}

/* 
 * Do an "is there a pending request for this user?" poll.
 * That function - via SecCertificateFindRequest() always returns an error;
 * *we* only return an error if the result is something other than the
 * expected two results:
 * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING
 * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING
 */
static OSStatus dotMacPostPendingReqPoll(
	const char *userName, 
	const char *password, 
	const char *hostName)
{
	SecCertificateRequestAttribute		attrs[MAX_ATTRS];
	SecCertificateRequestAttribute		*attrp = attrs;
	SecCertificateRequestAttributeList	attrList;
	uint8								oneBit = 1;

	attrList.count = 0;
	attrList.attr = attrs;
	
	/* user name, required */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME;
	attrp->value.Data = (uint8 *)userName;
	attrp->value.Length = strlen(userName);
	attrp++;
	attrList.count++;
	
	/* password, required */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD;
	attrp->value.Data = (uint8 *)password;
	attrp->value.Length = strlen(password);
	attrp++;
	attrList.count++;

	/* the "poll the server" indicator */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_IS_PENDING;
	/* true ::= any nonzero data  */
	attrp->value.Data = &oneBit;
	attrp->value.Length = 1;
	attrp++;
	attrList.count++;

	/* options */

	if(hostName) {
		attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME;
		attrp->value.Data = (uint8 *)hostName;
		attrp->value.Length = strlen(hostName);
		attrp++;
		attrList.count++;
	}

	/* policy, not technically needed; use this one by convention */
	const CSSM_OID *policy = &CSSMOID_DOTMAC_CERT_REQ_IDENTITY;

	OSStatus ortn;
	SecCertificateRequestRef certReq = NULL;
	
	printf("...calling SecCertificateFindRequest\n");
	ortn = SecCertificateFindRequest(policy, 
		CSSM_CERT_X_509v3, 
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
		NULL, NULL,			// no keys needed
		&attrList,
		&certReq);
		
	switch(ortn) {
		case CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING:
			printf("...result: REQ_IS_PENDING\n");
			ortn = noErr;
			break;
		case CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING:
			printf("...result: NO_REQ_PENDING\n");
			ortn = noErr;
			break;
		case noErr:
			/* should never happen */
			printf("...UNEXPECTED SUCCESS on SecCertificateFindRequest\n");
			ortn = internalComponentErr;
			if(certReq != NULL) {
				/* Somehow, it got created */
				CFRelease(certReq);
			}
			break;
		default:
			cssmPerror("SecCertificateFindRequest", ortn);
			break;
	}
	return ortn;
}

/* 
 * Post a .mac cert request, with a small number of options. 
 */
static OSStatus dotMacPostCertRequest(
	/* required fields */
	const char			*userName,		// REQUIRED, C string
	const char			*password,		// REQUIRED, C string
	SecKeyRef			privKey,		// REQUIRED
	SecKeyRef			pubKey,
	CertRequestType		requestType,
	bool				renew,			// false: new cert
										// true : renew existing
	bool				async,			// false: wait for result
										// true : just post request and return
	/* optional fields */
	const char			*hostName,		// C string
	SecKeychainRef		keychain)		// destination of created cert (if !async) 
{

	/* the main job here is bundling up the arguments in an array of OID/value pairs */
	SecCertificateRequestAttribute		attrs[MAX_ATTRS];
	SecCertificateRequestAttribute		*attrp = attrs;
	SecCertificateRequestAttributeList	attrList;
	uint8								oneBit = 1;
	
	attrList.count = 0;
	attrList.attr = attrs;
	
	/* user name */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME;
	attrp->value.Data = (uint8 *)userName;
	attrp->value.Length = strlen(userName);
	attrp++;
	attrList.count++;
	
	/* password */
	attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD;
	attrp->value.Data = (uint8 *)password;
	attrp->value.Length = strlen(password);
	attrp++;
	attrList.count++;
	
	/* options */

	if(hostName) {
		attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME;
		attrp->value.Data = (uint8 *)hostName;
		attrp->value.Length = strlen(hostName);
		attrp++;
		attrList.count++;
	}
	
	if(renew) {
		attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_RENEW;
		/* true ::= any nonzero data  */
		attrp->value.Data = &oneBit;
		attrp->value.Length = 1;
		attrp++;
		attrList.count++;
	}
	
	if(async) {
		attrp->oid = CSSMOID_DOTMAC_CERT_REQ_VALUE_ASYNC;
		/* true ::= any nonzero data  */
		attrp->value.Data = &oneBit;
		attrp->value.Length = 1;
		attrp++;
		attrList.count++;
	}

	/* map CertRequestType to a policy OID */
	const CSSM_OID *policy;
	switch(requestType) {
		case CRT_Identity:
			policy = &CSSMOID_DOTMAC_CERT_REQ_IDENTITY;
			break;
		case CRT_EmailSign:
			policy = &CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN;
			break;
		case CRT_EmailEncrypt:
			policy = &CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT;
			break;
		default:
			printf("GAK! Bad cert type.\n");
			return -1;
	}
	OSStatus ortn;
	SecCertificateRequestRef certReq = NULL;
	
	ortn = SecCertificateRequestCreate(policy, 
		CSSM_CERT_X_509v3, 
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
		privKey,
		pubKey,
		&attrList,
		&certReq);
	if(ortn) {
		cssmPerror("SecCertificateRequestCreate", ortn);
		return ortn;
	}
	
	printf("...submitting request to .mac server\n");
	sint32 estTime = 0;
	ortn = SecCertificateRequestSubmit(certReq, &estTime);
	switch(ortn) {
		case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT:
		{
			/* 
			 * A special case; the server is redirecting the calling app to 
			 * a URL which we fetch and report like so:
			 */
			CSSM_DATA url = {0, NULL};
			ortn = SecCertificateRequestGetData(certReq, &url);
			if(ortn) {
				cssmPerror("SecCertificateRequestGetData", ortn);
				printf("***APPLE_DOTMAC_REQ_REDIRECT obtained but no URL availalble.\n");
			}
			else {
				printf("***APPLE_DOTMAC_REQ_REDIRECT obtained; redirect URL is: ");
				printString(&url);
				printf("\n");
			}
			break;
		}
		
		case CSSM_OK:
			printf("...cert request submitted; estimatedTime %d.\n", (int)estTime);
			break;
		default:
			cssmPerror("SecCertificateRequestSubmit", ortn);
			break;
	}
	if(ortn || async) {
		/* we're done */
		CFRelease(certReq);
		return ortn;
	}
	
	/* 
	 * Running synchronously, and the submit succeeded. Try to get a result.
	 * In the real world this would be polled, every so often....
	 */
	SecCertificateRef certRef = NULL;
	printf("...attempting to get result of cert request...\n");
	ortn = SecCertificateRequestGetResult(certReq, keychain, &estTime, &certRef);
	if(ortn) {
		cssmPerror("SecCertificateRequestGetResult", ortn);
	}
	else {
		printf("...SecCertificateRequestGetResult succeeded; estTime %d; cert %s\n",
			(int)estTime, certRef ? "OBTAINED" : "NOT OBTAINED");
	}
	if(certRef) {
		CFRelease(certRef);
		CFRelease(certReq);
	}
	return ortn;
}

#define ALWAYS_DO_SUBMIT		0


int main(int argc, char **argv)
{
	SecKeyRef		pubKeyRef = NULL;
	SecKeyRef		privKeyRef = NULL;
	SecKeychainRef	kcRef = NULL;
	OSStatus		ortn;
	
	/* user-spec'd variables */
	bool			genKeys = true;			/* true: generate; false: pick 'em */
	char			*keychainName = NULL;
	char			*userName = USER_DEFAULT;
	char			*password = PWD_DEFAULT;
	char			*hostName = NULL;		/* leave as the default! = HOST_DEFAULT; */	
	/* 
	 * WARNING: doing a renew operation requires that you delete your *current* 
	 * .mac cert from the destination keychain. The DB attrs of the old and new certs
	 * are the same!
	 */
	bool			doRenew = false;
	CertRequestType reqType = CRT_Identity;
	bool			doPause = false;
	bool			async = false;
	bool			doSearch = false;		/* false: post cert request 
											 * true : search for existing request, get 
											 *   status for it */
	bool			loop = false;
	bool			doPendingReqPoll = false;

	if(argc < 2) {
		usage(argv);
	}
	switch(argv[1][0]) {
		case 'i':
			reqType = CRT_Identity;
			break;
		case 's':
			reqType = CRT_EmailSign;
			break;
		case 'e':
			reqType = CRT_EmailEncrypt;
			break;
		case 'I':
			doSearch = true;
			reqType = CRT_Identity;
			break;
		case 'S':
			doSearch = true;
			reqType = CRT_EmailSign;
			break;
		case 'E':
			doSearch = true;
			reqType = CRT_EmailEncrypt;
			break;
		case 'p':
			doPendingReqPoll = true;
			break;
		default:
			usage(argv);
	}

	extern char *optarg;
	extern int optind;
	optind = 2;
	int arg;
	while ((arg = getopt(argc, argv, "u:Z:pk:rMH:al")) != -1) {
		switch (arg) {
			case 'u':
				userName = optarg;
				break;
			case 'Z':
				password = optarg;
				break;
			case 'p':
				genKeys = false;
				break;
			case 'k':
				keychainName = optarg;
				break;
			case 'r':
				doRenew = true;
				break;
			case 'M':
				doPause = true;
				break;
			case 'H':
				hostName = optarg;
				break;
			case 'a':
				async = true;
				break;
			case 'l':
				loop = true;
				break;
			case 'h':
			default:
				usage(argv);
		}
	}
	if(optind != argc) {
		usage(argv);
	}
	
	if(doPause) {
		fpurge(stdin);
		printf("Pausing for MallocDebug attach; CR to continue: ");
		getchar();
	}
	
	if(keychainName != NULL) {
		/* pick a keychain (optional) */
		ortn = SecKeychainOpen(keychainName, &kcRef);
		if(ortn) {
			cssmPerror("SecKeychainOpen", ortn);
			exit(1);
		}
		
		/* make sure it's there since a successful SecKeychainOpen proves nothing */
		SecKeychainStatus kcStat;
		ortn = SecKeychainGetStatus(kcRef, &kcStat);
		if(ortn) {
			cssmPerror("SecKeychainGetStatus", ortn);
			exit(1);
		}
	}
	
	if((!doSearch || ALWAYS_DO_SUBMIT) && !doPendingReqPoll) {
		/* get a key pair, somehow */
		if(genKeys) {
			ortn = genKeyPair(kcRef, &pubKeyRef, &privKeyRef);
		}
		else {
			ortn = keyPicker(kcRef, &pubKeyRef, &privKeyRef);
		}
		if(ortn) {
			printf("Can't proceed without a keypair. Aborting.\n");
			exit(1);
		}
	}
	
	/* go */
	do {
		if(doSearch) {
			#if ALWAYS_DO_SUBMIT
			/* debug only */
			dotMacPostCertRequest(userName, password, privKeyRef, pubKeyRef,
				reqType, doRenew, async, hostName, kcRef);
			#endif
			
			/* end */
			ortn = dotMacGetPendingRequest(userName, password, reqType, hostName, kcRef);
		}
		else if(doPendingReqPoll) {
			ortn = dotMacPostPendingReqPoll(userName, password, hostName);
		}
		else {
			ortn = dotMacPostCertRequest(userName, password, privKeyRef, pubKeyRef,
				reqType, doRenew, async, hostName, kcRef);
		}
		if(doPause) {
			fpurge(stdin);
			printf("Pausing for MallocDebug attach; CR to continue: ");
			getchar();
		}
	} while(loop);
	if(privKeyRef) {
		CFRelease(privKeyRef);
	}
	if(pubKeyRef) {
		CFRelease(pubKeyRef);
	}
	if(kcRef) {
		CFRelease(kcRef);
	}

	if(doPause) {
		fpurge(stdin);
		printf("Pausing at end of test for MallocDebug attach; CR to continue: ");
		getchar();
	}

	return ortn;
}