--- /dev/null
+/*
+ * 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;
+}
+