dotMacArchive.cpp   [plain text]


/* 
 * dotMacArchive.cpp - test and demonstrate use of dotmacp_tp.bundle to
 * manipulate Identity archives ont he .mac server. 
 */
#include <Security/Security.h>
#include <Security/SecImportExport.h>
#include <Security/SecCertificateRequest.h>
//#include <security_dotmac_tp/dotMacTp.h>
#include <dotMacTp.h>
#include "identSearch.h"
#include "dotMacTpAttach.h"
#include <unistd.h>
#include <security_cdsa_utils/cuCdsaUtils.h>
#include <security_cdsa_utils/cuFileIo.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>

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

/* 
 * Type of archive op
 */
typedef enum {
	AO_List,
	AO_Store,
	AO_Fetch,
	AO_Remove
} ArchiveOp;

static void usage(char **argv)
{
	printf("usage: %s op [options]\n", argv[0]);
	printf("Op:\n");
	printf("    l              -- list archive contents\n");
	printf("    s              -- store archive\n");
	printf("    f              -- fetch archive\n");
	printf("    r              -- remove archive(s)\n");
	printf("Options:\n");
	printf("   -u username     -- Default is %s\n", USER_DEFAULT);
	printf("   -Z password     -- default is %s\n", PWD_DEFAULT);
	printf("   -n archiveName  -- default is %s\n", ARCHIVE_NAME_DEFAULT);
	printf("   -k keychain     -- Source/destination of archive\n");
	printf("   -H hostname     -- Alternate .mac server host name (default %s)\n",
									HOST_DEFAULT);
	printf("   -o outFile      -- write P12 blob to outFile\n");
	printf("   -z p12Phrase    -- PKCS12 passphrase (default is GUI prompt)\n");
	printf("   -M              -- Pause for MallocDebug\n");
	printf("   -l              -- loop\n");
	exit(1);
}

/* print a string in the 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]);
	}
}


/* 
 * Post a .mac archive request, with a small number of options. 
 */
static CSSM_RETURN dotMacPostArchiveRequest(
	ArchiveOp			op,
	CSSM_TP_HANDLE		tpHand,
	/* required fields for all ops */
	const CSSM_DATA		*userName,		// REQUIRED, C string
	const CSSM_DATA		*password,		// REQUIRED, C string
	
	/* optional (per op, that is...) fields */
	const CSSM_DATA		*hostName,		// optional alternate host 
	const CSSM_DATA		*archiveName,	// required for store, fetch, remove
	const CSSM_DATA		*timeString,	// required for store
	const CSSM_DATA		*pfxIn,			// required for store
	CSSM_DATA			*pfxOut,		// required and RETURNED for fetch
	unsigned			*numArchives,	// required and RETURNED for list
	DotMacArchive		**archives)		// required and RETURNED for list
{
	CSSM_RETURN					crtn;
	CSSM_TP_AUTHORITY_ID		tpAuthority;
	CSSM_TP_AUTHORITY_ID		*tpAuthPtr = NULL;
	CSSM_NET_ADDRESS			tpNetAddrs;
	CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST	archReq;
	CSSM_TP_REQUEST_SET			reqSet;
	CSSM_TP_CALLERAUTH_CONTEXT	callerAuth;
	sint32						estTime;
	CSSM_DATA					refId = {0, NULL};
	CSSM_FIELD					policyField;
	const CSSM_OID				*opOid = NULL;
	
	if((tpHand == 0) || (userName == NULL) || (password == NULL)) {
		printf("dotMacPostArchiveRequest: illegal common args\n");
		return paramErr;
	}
	switch(op) {
		case AO_List:
			if((numArchives == NULL) || (archives == NULL)) {
				printf("dotMacPostArchiveRequest: illegal AO_List args\n");
				return paramErr;
			}
			opOid = &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_LIST;
			break;
		case AO_Store:
			if((archiveName == NULL) || (timeString == NULL) || (pfxIn == NULL)) {
				printf("dotMacPostArchiveRequest: illegal AO_Store args\n");
				return paramErr;
			}
			opOid = &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_STORE;
			break;
		case AO_Fetch:
			if((archiveName == NULL) || (pfxOut == NULL)) {
				printf("dotMacPostArchiveRequest: illegal AO_Fetch args\n");
				return paramErr;
			}
			opOid = &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_FETCH;
			break;
		case AO_Remove:
			if(archiveName == NULL) {
				printf("dotMacPostArchiveRequest: illegal AO_Remove args\n");
				return paramErr;
			}
			opOid = &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_REMOVE;
			break;
	}
	
	/* 
	 * The main job here is bundling up the arguments into a
	 * CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST 
	 */
	memset(&archReq, 0, sizeof(archReq));
	archReq.version = CSSM_DOT_MAC_TP_ARCHIVE_REQ_VERSION;
	archReq.userName = *userName;
	archReq.password = *password;
	if(archiveName) {
		archReq.archiveName = *archiveName;
	}
	if(timeString) {
		archReq.timeString = *timeString;
	}
	if(pfxIn) {
		archReq.pfx = *pfxIn;
	}
	
	/* remaining arguments for TP call... */
	if((hostName != NULL) && (hostName->Data != NULL)) {
		tpAuthority.AuthorityCert = NULL;
		tpAuthority.AuthorityLocation = &tpNetAddrs;
		tpNetAddrs.AddressType = CSSM_ADDR_NAME;
		tpNetAddrs.Address = *hostName;
		tpAuthPtr = &tpAuthority;
	}

	reqSet.NumberOfRequests = 1;
	reqSet.Requests = &archReq;
	
	policyField.FieldOid = *opOid;
	policyField.FieldValue.Data = NULL;
	policyField.FieldValue.Length = 0;
	memset(&callerAuth, 0, sizeof(callerAuth));
	callerAuth.Policy.NumberOfPolicyIds = 1;
	callerAuth.Policy.PolicyIds = &policyField;

	crtn = CSSM_TP_SubmitCredRequest (tpHand,
		tpAuthPtr,		
		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,	// CSSM_TP_AUTHORITY_REQUEST_TYPE 
		&reqSet,	// const CSSM_TP_REQUEST_SET *RequestInput,
		&callerAuth,
		&estTime,   // sint32 *EstimatedTime,
		&refId);	// CSSM_DATA_PTR ReferenceIdentifier
	if(crtn) {
		cssmPerror("CSSM_TP_SubmitCredRequest", crtn);
	}
	
	/* success: post-process */
	switch(op) {
		case AO_List:
			*numArchives = archReq.numArchives;
			*archives = archReq.archives;
			break;
		case AO_Store:
			break;
		case AO_Fetch:
			*pfxOut = archReq.pfx;
			break;
		case AO_Remove:
			break;
	}

	return CSSM_OK;
}

static void cStringToCssmData(
	const char	*cstr,
	CSSM_DATA	*cdata)
{
	if(cstr) {
		cdata->Data = (uint8 *)cstr;
		cdata->Length = strlen(cstr);
	}
	else {
		cdata->Data = NULL;
		cdata->Length = 0;
	}
}

int main(int argc, char **argv)
{
	SecKeychainRef	kcRef = NULL;
	CSSM_RETURN		crtn;
	CSSM_TP_HANDLE	tpHand = 0;
	OSStatus		ortn;
	
	/* user-spec'd variables */
	ArchiveOp		op = AO_List;
	char			*keychainName = NULL;
	const char		*userName = USER_DEFAULT;
	const char		*password = PWD_DEFAULT;
	const char		*archName = ARCHIVE_NAME_DEFAULT;
	const char		*hostName = HOST_DEFAULT;
	char			*outFile = NULL;
	bool			doPause = false;
	bool			loop = false;
	char			*p12Phrase = NULL;
	
	if(argc < 2) {
		usage(argv);
	}
	switch(argv[1][0]) {
		case 'l':
			op = AO_List;
			break;
		case 's':
			op = AO_Store;
			break;
		case 'f':
			op = AO_Fetch;
			break;
		case 'r':
			op = AO_Remove;
			break;
		default:
			usage(argv);
	}

	extern char *optarg;
	extern int optind;
	optind = 2;
	int arg;
	while ((arg = getopt(argc, argv, "u:Z:n:k:H:Nlo:z:")) != -1) {
		switch (arg) {
			case 'u':
				userName = optarg;
				break;
			case 'Z':
				password = optarg;
				break;
			case 'n':
				archName = optarg;
				break;
			case 'k':
				keychainName = optarg;
				break;
			case 'M':
				doPause = true;
				break;
			case 'H':
				hostName = optarg;
				break;
			case 'l':
				loop = true;
				break;
			case 'o':
				outFile = optarg;
				break;
			case 'z':
				p12Phrase = optarg;
				break;
			case 'h':
				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);
		}
	}
	
	/* bundle up our crufty C string args into CSSM_DATAs needed at the TP SPI */
	CSSM_DATA userNameData;
	CSSM_DATA passwordData;
	CSSM_DATA hostNameData;
	CSSM_DATA archNameData;
	CSSM_DATA timeStringData;
	
	cStringToCssmData(userName, &userNameData);
	cStringToCssmData(password, &passwordData);
	cStringToCssmData(hostName, &hostNameData);
	cStringToCssmData(archName, &archNameData);
	
	/* time in seconds since the epoch, sprintf'd in base 10 */
	char timeStr[20];
	time_t nowTime = time(NULL);
	printf("...nowTime = %lu\n", nowTime);
	//nowTime += (60 * 60 * 24 * 26);	// fails
	nowTime += (60 * 60 * 24 * 25);		// works
	printf("...expirationTime = %lu\n", nowTime);
	sprintf(timeStr, "%lu", nowTime);
	timeStringData.Data = (uint8 *)timeStr;
	timeStringData.Length = strlen(timeStr);
	
	/* other data needed by dotMacPostArchiveRequest() */
	CFDataRef		p12 = NULL;
	CSSM_DATA		pfxInData = {0, NULL};
	CSSM_DATA		pfxOutData = {0, NULL};
	unsigned		numArchives = 0;
	DotMacArchive	*archives = NULL;	
	
	/* Store op: get identity in p12 form */
	if(op == AO_Store) {
		CFStringRef cfPhrase = NULL;

		/* Cert attribute - email address - contains the "@mac.com" */
		char emailAddr[500];
		strcpy(emailAddr, userName);
		// nope strcat(emailAddr, "@mac.com");
		
		/* find an identity for that email address */
		SecIdentityRef idRef = NULL;
		OSStatus ortn = findIdentity(emailAddr, strlen(emailAddr), kcRef, &idRef);
		if(ortn) {
			printf("***Could not find an identity to store. Aborting.\n");
			goto errOut;
		}
		
		/* convert that identity to p12 */
		SecKeyImportExportParameters keyParams;
		memset(&keyParams, 0, sizeof(keyParams));
		keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
		if(p12Phrase) {
			cfPhrase = CFStringCreateWithCString(NULL, p12Phrase, 
				kCFStringEncodingUTF8);
			keyParams.passphrase = cfPhrase;
		}
		else {
			keyParams.flags = kSecKeySecurePassphrase;
		}
		keyParams.alertTitle = CFSTR(".mac Identity Backup");
		keyParams.alertPrompt = 
			CFSTR("Enter passphrase for encrypting your .mac private key");
		ortn = SecKeychainItemExport(idRef, kSecFormatPKCS12, kSecItemPemArmour, 
			&keyParams, &p12);
		if(ortn) {
			cssmPerror("SecKeychainItemExport", crtn);
			printf("***Error obtaining .mac identity in PKCS12 format. Aborting.\n");
			goto errOut;
		}
		
		pfxInData.Data = (uint8 *)CFDataGetBytePtr(p12);
		pfxInData.Length = CFDataGetLength(p12);
		printf("...preparing to store archive of %lu bytes\n", pfxInData.Length);
		
		if(outFile) {
			if(writeFile(outFile, pfxInData.Data, pfxInData.Length)) {
				printf("***Error writing P12 to %s\n", outFile);
			}
			else {
				printf("...wrote %lu bytes to %s\n", pfxInData.Length, outFile);
			}
		}
		if(cfPhrase) {
			CFRelease(cfPhrase);
		}
	}
	
	/* attach to the TP */
	tpHand = dotMacTpAttach();
	if(tpHand == 0) {
		printf("***Error attaching to .mac TP; aborting.\n");
		ortn = -1;
		goto errOut;
	}
	
	/* go */
	crtn = dotMacPostArchiveRequest(op, tpHand, &userNameData, &passwordData,
		hostName ? &hostNameData : NULL,
		&archNameData,
		&timeStringData,
		&pfxInData,
		&pfxOutData,
		&numArchives,
		&archives);
	if(crtn) {
		printf("***Error performing archive request; aborting.\n");
		goto errOut;
	}
	
	/* post-request processing */
	
	switch(op) {
		case AO_List:
		{
			printf("=== List request complete; numArchives = %u ===\n", numArchives);
			for(unsigned dex=0; dex<numArchives; dex++) {
				DotMacArchive *dmarch = &archives[dex];
				printf("Archive %u:\n", dex);
				printf("   name : "); 
				printString(&dmarch->archiveName); 
				printf("\n");
				
				printf("   time : "); 
				printString(&dmarch->timeString); 
				printf("\n");
				
				/* now free what the TP allocated on our behalf */
				APP_FREE(dmarch->archiveName.Data);
				APP_FREE(dmarch->timeString.Data);
			}
			APP_FREE(archives);
			break;
		}

		case AO_Store:
			printf("=== archive \'%s\' backup complete ===\n", archName);
			break;
		case AO_Fetch:
		{
			bool didSomething = false;
			if(pfxOutData.Length == 0) {
				printf("***Archive fetch claimed to succeed, but no data seen\n");
				ortn = -1;
				goto errOut;
			}
			
			/* 
			 * OK, we have a blob of PKCS12 data. Import to keychain and/or write it 
			 * to a file.
			 */
			printf("=== %lu bytes of archive fetched ===\n", pfxOutData.Length);
			if(outFile) {
				if(writeFile(outFile, pfxOutData.Data, pfxOutData.Length)) {
					printf("***Error writing P12 to %s\n", outFile);
				}
				else {
					printf("...wrote %lu bytes to %s\n", pfxOutData.Length, outFile);
					didSomething = true;
				}
			}
			if(kcRef) {
				/* Note we avoid importing to default keychain - user must really want 
				 * to perform this step */
				CFDataRef p12Data = CFDataCreate(NULL, pfxOutData.Data, pfxOutData.Length);
				SecExternalFormat extForm = kSecFormatPKCS12;
				SecKeyImportExportParameters keyParams;
				memset(&keyParams, 0, sizeof(keyParams));
				keyParams.version = SEC_KEY_IMPORT_EXPORT_PARAMS_VERSION;
				CFStringRef cfPhrase = NULL;
				if(p12Phrase) {
					cfPhrase = CFStringCreateWithCString(NULL, p12Phrase, 
						kCFStringEncodingUTF8);
					keyParams.passphrase = cfPhrase;
				}
				else {
					keyParams.flags = kSecKeySecurePassphrase;
				}
				keyParams.alertTitle = CFSTR(".mac Identity Restore");
				keyParams.alertPrompt = 
					CFSTR("Enter passphrase for decrypting your .mac private key");
					
				/* go... */
				ortn = SecKeychainItemImport(p12Data, 
					NULL,		// filename - passing kSecFormatPKCS12 is definitely enough
					&extForm,
					NULL,		// itemType - import'll figure it out
					0,			// SecItemImportExportFlags
					&keyParams,
					kcRef,
					NULL);		// we don't want any items returned
				if(ortn) {
					cssmPerror("SecKeychainItemImport", ortn);
					printf("***Error importing p12 into keychain %s\n", keychainName);
				}
				else {
					printf("...archive successfully imported into keychain %s\n",
						keychainName);
					didSomething = true;
				}
				if(cfPhrase) {
					CFRelease(cfPhrase);
				}
			}
			if(!didSomething) {
				printf("...note we got an archive from the server but didn't have a "
					"place to put it.\n");
			}
			break;
		}
		case AO_Remove:
			printf("=== Archive %s removed ===\n", archName);
			break;
	}

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

errOut:
	if(kcRef) {
		CFRelease(kcRef);
	}

	if(tpHand) {
		dotMacTpDetach(tpHand);
	}
	return ortn;
}