--- /dev/null
+/*
+ * Copyright (c) 2002-2004,2011-2014 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+//
+// CertificateRequest.cpp
+//
+#include <security_keychain/CertificateRequest.h>
+#include <Security/oidsalg.h>
+#include <Security/SecKey.h>
+#include <Security/SecKeyPriv.h>
+#include <Security/cssmapi.h>
+#include <string.h>
+#include <dotMacTp.h>
+#include <Security/oidsattr.h>
+#include <security_utilities/simpleprefs.h>
+#include <SecBase.h>
+
+/* one top-level prefs file for all of .mac cert requests */
+#define DOT_MAC_REQ_PREFS "com.apple.security.certreq"
+
+/*
+ * Within that dictionary is a set of per-policy dictionaries; the key in the
+ * top-level prefs for these dictionaries is the raw policy OID data encoded
+ * as an ASCII string.
+ *
+ * Within one per-policy dictionary exists a number of per-user dictionaries,
+ * with the username key as a string. Note that this user name, the one passed to the
+ * .mac server, does NOT have to have any relation to the current Unix user name; one
+ * Unix user can have multiple .mac accounts.
+ *
+ *
+ * Within the per-policy, per user dictionary are these two values, both stored
+ * as raw data (CFData) blobs.
+ */
+#define DOT_MAC_REF_ID_KEY "refId"
+#define DOT_MAC_CERT_KEY "certificate"
+
+/* Domain for .mac cert requests */
+#define DOT_MAC_DOMAIN_KEY "domain"
+#define DOT_MAC_DOMAIN "mac.com"
+
+/* Hosts for .mac cert requests */
+#define DOT_MAC_MGMT_HOST "certmgmt"
+#define DOT_MAC_INFO_HOST "certinfo"
+
+/*
+ * Compare two CSSM_DATAs (or two CSSM_OIDs), return true if identical.
+ */
+static
+bool nssCompareCssmData(
+ const CSSM_DATA *data1,
+ const CSSM_DATA *data2)
+{
+ if((data1 == NULL) || (data1->Data == NULL) ||
+ (data2 == NULL) || (data2->Data == NULL) ||
+ (data1->Length != data2->Length)) {
+ return false;
+ }
+ if(data1->Length != data2->Length) {
+ return false;
+ }
+ if(memcmp(data1->Data, data2->Data, data1->Length) == 0) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/* any nonzero value means true */
+static bool attrBoolValue(
+ const SecCertificateRequestAttribute *attr)
+{
+ if((attr->value.Data != NULL) &&
+ (attr->value.Length != 0) &&
+ (attr->value.Data[0] != 0)) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+static void tokenizeName(
+ const CSSM_DATA *inName, /* required */
+ CSSM_DATA *outName, /* required */
+ CSSM_DATA *outDomain) /* optional */
+{
+ if (!inName || !outName) return;
+ CSSM_SIZE idx = 0;
+ CSSM_SIZE stopIdx = inName->Length;
+ uint8 *p = inName->Data;
+ *outName = *inName;
+ if (outDomain) {
+ outDomain->Length = idx;
+ outDomain->Data = p;
+ }
+ if (!p) return;
+ while (idx < stopIdx) {
+ if (*p++ == '@') {
+ outName->Length = idx;
+ if (outDomain) {
+ outDomain->Length = inName->Length - (idx + 1);
+ outDomain->Data = p;
+ }
+ break;
+ }
+ idx++;
+ }
+}
+
+using namespace KeychainCore;
+
+CertificateRequest::CertificateRequest(const CSSM_OID &policy,
+ CSSM_CERT_TYPE certificateType,
+ CSSM_TP_AUTHORITY_REQUEST_TYPE requestType,
+ SecKeyRef privateKeyItemRef,
+ SecKeyRef publicKeyItemRef,
+ const SecCertificateRequestAttributeList *attributeList,
+ bool isNew /* = true */)
+ : mAlloc(Allocator::standard()),
+ mTP(gGuidAppleDotMacTP),
+ mCL(gGuidAppleX509CL),
+ mPolicy(mAlloc, policy.Data, policy.Length),
+ mCertType(certificateType),
+ mReqType(requestType),
+ mPrivKey(NULL),
+ mPubKey(NULL),
+ mEstTime(0),
+ mRefId(mAlloc),
+ mCertState(isNew ? CRS_New : CRS_Reconstructed),
+ mCertData(mAlloc),
+ mUserName(mAlloc),
+ mPassword(mAlloc),
+ mHostName(mAlloc),
+ mDomain(mAlloc),
+ mDoRenew(false),
+ mIsAsync(false),
+ mMutex(Mutex::recursive)
+{
+ StLock<Mutex>_(mMutex);
+ certReqDbg("CertificateRequest construct");
+
+ /* Validate policy OID. */
+ if(!(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy))) {
+ certReqDbg("CertificateRequest(): unknown policy oid");
+ MacOSError::throwMe(errSecParam);
+ }
+ if(privateKeyItemRef) {
+ mPrivKey = privateKeyItemRef;
+ CFRetain(mPrivKey);
+ }
+ if(publicKeyItemRef) {
+ mPubKey = publicKeyItemRef;
+ CFRetain(mPubKey);
+ }
+
+ /* parse attr array */
+ if(attributeList == NULL) {
+ return;
+ }
+
+ bool doPendingRequest = false;
+ for(unsigned dex=0; dex<attributeList->count; dex++) {
+ const SecCertificateRequestAttribute *attr = &attributeList->attr[dex];
+
+ if((attr->oid.Data == NULL) || (attr->value.Data == NULL)) {
+ MacOSError::throwMe(errSecParam);
+ }
+ if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME, &attr->oid)) {
+ CSSM_DATA userName = { 0, NULL };
+ CSSM_DATA domainName = { 0, NULL };
+ tokenizeName(&attr->value, &userName, &domainName);
+ if (!domainName.Length || !domainName.Data) {
+ domainName.Length = strlen(DOT_MAC_DOMAIN);
+ domainName.Data = (uint8*) DOT_MAC_DOMAIN;
+ }
+ mUserName.copy(userName);
+ mDomain.copy(domainName);
+ }
+ else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD, &attr->oid)) {
+ mPassword.copy(attr->value);
+ }
+ else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME, &attr->oid)) {
+ mHostName.copy(attr->value);
+ }
+ else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_RENEW, &attr->oid)) {
+ /*
+ * any nonzero value means true
+ * FIXME: this is deprecated, Treadstone doesn't allow this. Reject this
+ * request? Ignore?
+ */
+ mDoRenew = attrBoolValue(attr);
+ }
+ else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_ASYNC, &attr->oid)) {
+ /* any nonzero value means true */
+ mIsAsync = attrBoolValue(attr);
+ }
+ else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_IS_PENDING, &attr->oid)) {
+ /* any nonzero value means true */
+ doPendingRequest = attrBoolValue(attr);
+ }
+
+ else {
+ certReqDbg("CertificateRequest(): unknown name/value oid");
+ MacOSError::throwMe(errSecParam);
+ }
+ }
+ if(mCertState == CRS_Reconstructed) {
+ /* see if we have a refId or maybe even a cert in prefs */
+ retrieveResults();
+ if(mCertData.data() != NULL) {
+ mCertState = CRS_HaveCert;
+ }
+ else if(mRefId.data() != NULL) {
+ mCertState = CRS_HaveRefId;
+ }
+ else if(doPendingRequest) {
+ /* ask the server if there's a request pending */
+ postPendingRequest();
+ /* NOT REACHED - that always throws */
+ }
+ else {
+ certReqDbg("CertificateRequest(): nothing in prefs");
+ /* Nothing found in prefs; nothing to go by */
+ MacOSError::throwMe(errSecItemNotFound);
+ }
+ }
+}
+
+CertificateRequest::~CertificateRequest() throw()
+{
+ StLock<Mutex>_(mMutex);
+ certReqDbg("CertificateRequest destruct");
+
+ if(mPrivKey) {
+ CFRelease(mPrivKey);
+ }
+ if(mPubKey) {
+ CFRelease(mPubKey);
+ }
+}
+
+#pragma mark ----- cert request submit -----
+
+void CertificateRequest::submit(
+ sint32 *estimatedTime)
+{
+ StLock<Mutex>_(mMutex);
+ CSSM_DATA &policy = mPolicy.get();
+ if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
+ return submitDotMac(estimatedTime);
+ }
+ else {
+ /* shouldn't be here, we already validated policy in constructor */
+ assert(0);
+ certReqDbg("CertificateRequest::submit(): bad policy");
+ MacOSError::throwMe(errSecParam);
+ }
+}
+
+void CertificateRequest::submitDotMac(
+ sint32 *estimatedTime)
+{
+ StLock<Mutex>_(mMutex);
+ CSSM_RETURN crtn;
+ CSSM_TP_AUTHORITY_ID tpAuthority;
+ CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL;
+ CSSM_NET_ADDRESS tpNetAddrs;
+ CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq;
+ CSSM_TP_REQUEST_SET reqSet;
+ CSSM_CSP_HANDLE cspHand = 0;
+ CSSM_X509_TYPE_VALUE_PAIR tvp;
+ CSSM_TP_CALLERAUTH_CONTEXT callerAuth;
+ CSSM_FIELD policyField;
+ CSSM_DATA refId = {0, NULL};
+ const CSSM_KEY *privKey;
+ const CSSM_KEY *pubKey;
+ OSStatus ortn;
+
+ if(mCertState != CRS_New) {
+ certReqDbg("CertificateRequest: can only submit a new request");
+ MacOSError::throwMe(errSecParam);
+ }
+ if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
+ certReqDbg("CertificateRequest: user name and password required");
+ MacOSError::throwMe(errSecParam);
+ }
+
+ /* get keys and CSP handle in CSSM terms */
+ if((mPrivKey == NULL) || (mPubKey == NULL)) {
+ certReqDbg("CertificateRequest: pub and priv keys required");
+ MacOSError::throwMe(errSecParam);
+ }
+ ortn = SecKeyGetCSSMKey(mPrivKey, &privKey);
+ if(ortn) {
+ MacOSError::throwMe(ortn);
+ }
+ ortn = SecKeyGetCSSMKey(mPubKey, &pubKey);
+ if(ortn) {
+ MacOSError::throwMe(ortn);
+ }
+ ortn = SecKeyGetCSPHandle(mPrivKey, &cspHand);
+ if(ortn) {
+ MacOSError::throwMe(ortn);
+ }
+
+ /*
+ * CSSM_X509_TYPE_VALUE_PAIR_PTR - one pair for now.
+ * Caller passes in user name like "johnsmith"; in the CSR,
+ * we write "johnsmith@mac.com".
+ */
+ tvp.type = CSSMOID_CommonName;
+ tvp.valueType = BER_TAG_PKIX_UTF8_STRING;
+ CssmAutoData fullUserName(mAlloc);
+ size_t nameLen = mUserName.length();
+ size_t domainLen = mDomain.length();
+ fullUserName.malloc(nameLen + 1 + domainLen);
+ tvp.value = fullUserName.get();
+ memmove(tvp.value.Data, mUserName.data(), nameLen);
+ memmove(tvp.value.Data + nameLen, "@", 1);
+ memmove(tvp.value.Data + nameLen + 1, mDomain.data(), domainLen);
+
+ /* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */
+ memset(&certReq, 0, sizeof(certReq));
+ certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
+ certReq.cspHand = cspHand;
+ certReq.clHand = mCL->handle();
+ certReq.numTypeValuePairs = 1;
+ certReq.typeValuePairs = &tvp;
+ certReq.publicKey = const_cast<CSSM_KEY_PTR>(pubKey);
+ certReq.privateKey = const_cast<CSSM_KEY_PTR>(privKey);
+ certReq.userName = mUserName.get();
+ certReq.password = mPassword.get();
+ if(mDoRenew) {
+ certReq.flags |= CSSM_DOTMAC_TP_SIGN_RENEW;
+ }
+ /* we don't deal with CSR here, input or output */
+
+ /* now the rest of the args for CSSM_TP_SubmitCredRequest() */
+ reqSet.Requests = &certReq;
+ reqSet.NumberOfRequests = 1;
+ policyField.FieldOid = mPolicy;
+ policyField.FieldValue.Data = NULL;
+ policyField.FieldValue.Length = 0;
+ memset(&callerAuth, 0, sizeof(callerAuth));
+ callerAuth.Policy.NumberOfPolicyIds = 1;
+ callerAuth.Policy.PolicyIds = &policyField;
+ ortn = SecKeyGetCredentials(mPrivKey,
+ CSSM_ACL_AUTHORIZATION_SIGN,
+ kSecCredentialTypeDefault,
+ const_cast<const CSSM_ACCESS_CREDENTIALS **>(&callerAuth.CallerCredentials));
+ if(ortn) {
+ certReqDbg("CertificateRequest: SecKeyGetCredentials error");
+ MacOSError::throwMe(ortn);
+ }
+
+ CssmAutoData hostName(mAlloc);
+ tpAuthority.AuthorityCert = NULL;
+ tpAuthority.AuthorityLocation = &tpNetAddrs;
+ tpNetAddrs.AddressType = CSSM_ADDR_NAME;
+ if(mHostName.data() != NULL) {
+ tpNetAddrs.Address = mHostName.get();
+ } else {
+ unsigned hostLen = strlen(DOT_MAC_MGMT_HOST);
+ hostName.malloc(hostLen + 1 + domainLen);
+ tpNetAddrs.Address = hostName.get();
+ memmove(tpNetAddrs.Address.Data, DOT_MAC_MGMT_HOST, hostLen);
+ memmove(tpNetAddrs.Address.Data + hostLen, ".", 1);
+ memmove(tpNetAddrs.Address.Data + hostLen + 1, mDomain.data(), domainLen);
+ }
+ tpAuthPtr = &tpAuthority;
+
+ /* go */
+ crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
+ tpAuthPtr,
+ CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
+ &reqSet,
+ &callerAuth,
+ &mEstTime,
+ &refId); // CSSM_DATA_PTR ReferenceIdentifier
+
+ /* handle return, store results */
+ switch(crtn) {
+ case CSSM_OK:
+ /* refID is a cert, we have to store it in prefs for later retrieval. */
+ certReqDbg("submitDotMac: full success, storing cert");
+ if(!mIsAsync) {
+ /* store in prefs if not running in async mode */
+ ortn = storeResults(NULL, &refId);
+ if(ortn) {
+ crtn = ortn;
+ }
+ }
+ /* but keep a local copy too */
+ mCertData.copy(refId);
+ mCertState = CRS_HaveCert;
+ if(estimatedTime) {
+ /* it's ready right now */
+ *estimatedTime = 0;
+ }
+ break;
+
+ case CSSMERR_APPLE_DOTMAC_REQ_QUEUED:
+ /* refID is the blob we use in CSSM_TP_RetrieveCredResult() */
+ certReqDbg("submitDotMac: queued, storing refId");
+ mRefId.copy(refId);
+ /* return success - this crtn is not visible at API */
+ crtn = CSSM_OK;
+ if(!mIsAsync) {
+ /* store in prefs if not running in async mode */
+ ortn = storeResults(&refId, NULL);
+ if(ortn) {
+ crtn = ortn;
+ }
+ }
+ mCertState = CRS_HaveRefId;
+ if(estimatedTime) {
+ *estimatedTime = mEstTime;
+ }
+ break;
+
+ case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT:
+ /* refID is a URL, caller obtains via getReturnData() */
+ certReqDbg("submitDotMac: redirect");
+ mRefId.copy(refId);
+ mCertState = CRS_HaveOtherData;
+ break;
+
+ default:
+ /* all others are fatal errors, thrown below */
+ break;
+ }
+ if(refId.Data) {
+ /* mallocd on our behalf by TP */
+ free(refId.Data);
+ }
+ if(crtn) {
+ CssmError::throwMe(crtn);
+ }
+}
+
+#pragma mark ----- cert request get result -----
+
+void CertificateRequest::getResult(
+ sint32 *estimatedTime, // optional
+ CssmData &certData)
+{
+ StLock<Mutex>_(mMutex);
+ CSSM_DATA &policy = mPolicy.get();
+ if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
+ nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
+ return getResultDotMac(estimatedTime, certData);
+ }
+ else {
+ /* shouldn't be here, we already validated policy in constructor */
+ assert(0);
+ certReqDbg("CertificateRequest::getResult(): bad policy");
+ MacOSError::throwMe(errSecParam);
+ }
+}
+
+void CertificateRequest::getResultDotMac(
+ sint32 *estimatedTime, // optional
+ CssmData &certData)
+{
+ StLock<Mutex>_(mMutex);
+ switch(mCertState) {
+ case CRS_HaveCert:
+ /* trivial case, we already have what caller is looking for */
+ certReqDbg("getResultDotMac: have the cert right now");
+ assert(mCertData.data() != NULL);
+ certData = mCertData.get();
+ if(estimatedTime) {
+ *estimatedTime = 0;
+ }
+ break;
+ case CRS_HaveRefId:
+ {
+ /* ping the server */
+ certReqDbg("getResultDotMac: CRS_HaveRefId; polling server");
+ assert(mRefId.data() != NULL);
+ CSSM_BOOL ConfirmationRequired;
+ CSSM_TP_RESULT_SET_PTR resultSet = NULL;
+ CSSM_RETURN crtn;
+
+ crtn = CSSM_TP_RetrieveCredResult(mTP->handle(),
+ &mRefId.get(),
+ NULL, // CallerAuthCredentials
+ &mEstTime,
+ &ConfirmationRequired,
+ &resultSet);
+ switch(crtn) {
+ case CSSM_OK:
+ break;
+ case CSSMERR_TP_CERT_NOT_VALID_YET:
+ /*
+ * By convention, this means "not ready yet".
+ * The dot mac server does not have a way of telling us the
+ * estimated time on a straight lookup like this (we only get
+ * an estimated completion time on the initial request), so we
+ * fake it.
+ */
+ certReqDbg("getResultDotMac: polled server, not ready yet");
+ if(estimatedTime) {
+ *estimatedTime = (mEstTime) ? mEstTime : 1;
+ }
+ MacOSError::throwMe(CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING);
+ default:
+ certReqDbg("CSSM_TP_RetrieveCredResult error");
+ CssmError::throwMe(crtn);
+ }
+ if(resultSet == NULL) {
+ certReqDbg("***CSSM_TP_RetrieveCredResult OK, but no result set");
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ if(resultSet->NumberOfResults != 1) {
+ certReqDbg("***CSSM_TP_RetrieveCredResult OK, NumberOfResults (%lu)",
+ (unsigned long)resultSet->NumberOfResults);
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ if(resultSet->Results == NULL) {
+ certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result set");
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ certReqDbg("getResultDotMac: polled server, SUCCESS");
+ CSSM_DATA_PTR result = (CSSM_DATA_PTR)resultSet->Results;
+ if(result->Data == NULL) {
+ certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result");
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+ mCertData.copy(*result);
+ certData = mCertData.get();
+ mCertState = CRS_HaveCert;
+ if(estimatedTime) {
+ *estimatedTime = 0;
+ }
+
+ /*
+ * Free the stuff allocated on our behalf by TP.
+ * FIXME - are we sure CssmClient is using alloc, free, etc.?
+ */
+ free(result->Data);
+ free(result);
+ free(resultSet);
+ break;
+ }
+ default:
+ /* what do we do with this? */
+ certReqDbg("CertificateRequest::getResultDotMac(): bad state");
+ MacOSError::throwMe(errSecInternalComponent);
+ }
+
+ /*
+ * One more thing: once we pass a cert back to caller, we erase
+ * the record of this transaction from prefs.
+ */
+ assert(mCertData.data() != NULL);
+ assert(mCertData.data() == certData.Data);
+ removeResults();
+}
+
+/*
+ * Obtain policy/error specific return data blob. We own the data, it's
+ * not copied.
+ */
+void CertificateRequest::getReturnData(
+ CssmData &rtnData)
+{
+ StLock<Mutex>_(mMutex);
+ rtnData = mRefId.get();
+}
+
+#pragma mark ----- preferences support -----
+
+/* Current user as CFString, for use as key in per-policy dictionary */
+CFStringRef CertificateRequest::createUserKey()
+{
+ StLock<Mutex>_(mMutex);
+ return CFStringCreateWithBytes(NULL, (UInt8 *)mUserName.data(), mUserName.length(),
+ kCFStringEncodingUTF8, false);
+}
+
+#define MAX_OID_LEN 2048 // way big... */
+
+/* current policy as CFString, for use as key in prefs dictionary */
+CFStringRef CertificateRequest::createPolicyKey()
+{
+ StLock<Mutex>_(mMutex);
+ char oidstr[MAX_OID_LEN];
+ unsigned char *inp = (unsigned char *)mPolicy.data();
+ char *outp = oidstr;
+ CFIndex len = mPolicy.length();
+ for(CFIndex dex=0; dex<len; dex++) {
+ sprintf(outp, "%02X ", *inp++);
+ outp += 3;
+ }
+ return CFStringCreateWithBytes(NULL, (UInt8 *)oidstr, len * 3,
+ kCFStringEncodingUTF8, false);
+}
+
+/*
+ * Store cert data or refId in prefs. If both are NULL, delete the
+ * user dictionary entry from the policy dictionary if there, and then
+ * delete the policy dictionary if it's empty.
+ */
+OSStatus CertificateRequest::storeResults(
+ const CSSM_DATA *refId, // optional, for queued requests
+ const CSSM_DATA *certData) // optional, for immediate completion
+{
+ StLock<Mutex>_(mMutex);
+ assert(mPolicy.data() != NULL);
+ assert(mUserName.data() != NULL);
+ assert(mDomain.data() != NULL);
+
+ bool deleteEntry = ((refId == NULL) && (certData == NULL));
+
+ /* get a mutable copy of the existing prefs, or a fresh empty one */
+ MutableDictionary *prefsDict = MutableDictionary::CreateMutableDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
+ if (prefsDict == NULL)
+ {
+ prefsDict = new MutableDictionary();
+ }
+
+ /* get a mutable copy of the dictionary for this policy, or a fresh empty one */
+ CFStringRef policyKey = createPolicyKey();
+ MutableDictionary *policyDict = prefsDict->copyMutableDictValue(policyKey);
+
+ CFStringRef userKey = createUserKey();
+ if(deleteEntry) {
+ /* remove user dictionary from this policy dictionary */
+ policyDict->removeValue(userKey);
+ }
+ else {
+ /* get a mutable copy of the dictionary for this user, or a fresh empty one */
+ MutableDictionary *userDict = policyDict->copyMutableDictValue(userKey);
+
+ CFStringRef domainKey = CFStringCreateWithBytes(NULL, (UInt8 *)mDomain.data(), mDomain.length(), kCFStringEncodingUTF8, false);
+ userDict->setValue(CFSTR(DOT_MAC_DOMAIN_KEY), domainKey);
+ CFRelease(domainKey);
+
+ /* write refId and/or cert --> user dictionary */
+ if(refId) {
+ userDict->setDataValue(CFSTR(DOT_MAC_REF_ID_KEY), refId->Data, refId->Length);
+ }
+ if(certData) {
+ userDict->setDataValue(CFSTR(DOT_MAC_CERT_KEY), certData->Data, certData->Length);
+ }
+
+ /* new user dictionary --> policy dictionary */
+ policyDict->setValue(userKey, userDict->dict());
+ delete userDict;
+ }
+ CFRelease(userKey);
+
+ /* new policy dictionary to prefs dictionary, or nuke it */
+ if(policyDict->count() == 0) {
+ prefsDict->removeValue(policyKey);
+ }
+ else {
+ prefsDict->setValue(policyKey, policyDict->dict());
+ }
+ CFRelease(policyKey);
+ delete policyDict;
+
+ /* prefs --> disk */
+ OSStatus ortn = errSecSuccess;
+ if(!prefsDict->writePlistToPrefs(DOT_MAC_REQ_PREFS, Dictionary::US_User)) {
+ certReqDbg("storeResults: error writing prefs to disk");
+ ortn = errSecIO;
+ }
+ delete prefsDict;
+ return ortn;
+}
+
+/*
+ * Attempt to fetch mCertData or mRefId from preferences.
+ */
+void CertificateRequest::retrieveResults()
+{
+ StLock<Mutex>_(mMutex);
+ assert(mPolicy.data() != NULL);
+ assert(mUserName.data() != NULL);
+
+ /* get the .mac cert prefs as a dictionary */
+ Dictionary *pd = Dictionary::CreateDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
+ if (pd == NULL)
+ {
+ certReqDbg("retrieveResults: no prefs found");
+ return;
+ }
+
+ auto_ptr<Dictionary> prefsDict(pd);
+
+ /* get dictionary for current policy */
+ CFStringRef policyKey = createPolicyKey();
+ Dictionary *policyDict = prefsDict->copyDictValue(policyKey);
+ CFRelease(policyKey);
+ if(policyDict != NULL) {
+ /* dictionary for user */
+ CFStringRef userKey = createUserKey();
+ Dictionary *userDict = policyDict->copyDictValue(userKey);
+ if(userDict != NULL) {
+ /* is there a cert in there? */
+ CFDataRef val = userDict->getDataValue(CFSTR(DOT_MAC_CERT_KEY));
+ if(val) {
+ mCertData.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
+ }
+
+ /* how about refId? */
+ val = userDict->getDataValue(CFSTR(DOT_MAC_REF_ID_KEY));
+ if(val) {
+ mRefId.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
+ }
+ delete userDict;
+ }
+ CFRelease(userKey);
+ delete policyDict;
+ }
+}
+
+/*
+ * Remove all trace of current policy/user. Called when we successfully transferred
+ * the cert back to caller.
+ */
+void CertificateRequest::removeResults()
+{
+ StLock<Mutex>_(mMutex);
+ assert(mPolicy.data() != NULL);
+ assert(mUserName.data() != NULL);
+ storeResults(NULL, NULL);
+}
+
+/*
+ * Have the TP ping the server to see of there's a request pending for the current
+ * user. Always throws: either
+ * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING -- request pending
+ * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING -- no request pending
+ * errSecParam -- no user, no password
+ * other gross errors, e.g. errSecIO for server connection failure
+ *
+ * The distinguishing features about this TP request are:
+ *
+ * policy OID = CSSMOID_DOTMAC_CERT_REQ_{IDENTITY,EMAIL_SIGN,EMAIL_ENCRYPT,SHARED_SERVICES}
+ * CSSM_TP_AUTHORITY_REQUEST_TYPE = CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP
+ * CSSM_APPLE_DOTMAC_TP_CERT_REQUEST.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING
+ * must have userName and password
+ * hostname optional as usual
+ */
+void CertificateRequest::postPendingRequest()
+{
+ StLock<Mutex>_(mMutex);
+ CSSM_RETURN crtn;
+ CSSM_TP_AUTHORITY_ID tpAuthority;
+ CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL;
+ CSSM_NET_ADDRESS tpNetAddrs;
+ CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq;
+ CSSM_TP_REQUEST_SET reqSet;
+ CSSM_TP_CALLERAUTH_CONTEXT callerAuth;
+ CSSM_FIELD policyField;
+ CSSM_DATA refId = {0, NULL};
+
+ assert(mCertState == CRS_Reconstructed);
+ if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
+ certReqDbg("postPendingRequest: user name and password required");
+ MacOSError::throwMe(errSecParam);
+ }
+
+ /* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */
+ memset(&certReq, 0, sizeof(certReq));
+ certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
+ certReq.userName = mUserName.get();
+ certReq.password = mPassword.get();
+ certReq.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING;
+
+ /* now the rest of the args for CSSM_TP_SubmitCredRequest() */
+ reqSet.Requests = &certReq;
+ reqSet.NumberOfRequests = 1;
+ /*
+ * This OID actually doesn't matter - right? This RPC doesn't know about
+ * which request we seek...
+ */
+ policyField.FieldOid = mPolicy;
+ policyField.FieldValue.Data = NULL;
+ policyField.FieldValue.Length = 0;
+ memset(&callerAuth, 0, sizeof(callerAuth));
+ callerAuth.Policy.NumberOfPolicyIds = 1;
+ callerAuth.Policy.PolicyIds = &policyField;
+ /* no other creds here */
+
+ if(mHostName.data() != NULL) {
+ tpAuthority.AuthorityCert = NULL;
+ tpAuthority.AuthorityLocation = &tpNetAddrs;
+ tpNetAddrs.AddressType = CSSM_ADDR_NAME;
+ tpNetAddrs.Address = mHostName.get();
+ tpAuthPtr = &tpAuthority;
+ }
+
+ /* go */
+ crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
+ tpAuthPtr,
+ CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP,
+ &reqSet,
+ &callerAuth,
+ &mEstTime,
+ &refId); // CSSM_DATA_PTR ReferenceIdentifier
+
+ if(refId.Data) {
+ /* shouldn't be any but just in case.... */
+ free(refId.Data);
+ }
+ switch(crtn) {
+ case CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING:
+ certReqDbg("postPendingRequest: REQ_IS_PENDING");
+ break;
+ case CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING:
+ certReqDbg("postPendingRequest: NO_REQ_PENDING");
+ break;
+ case CSSM_OK:
+ /* should never happen */
+ certReqDbg("postPendingRequest: unexpected success!");
+ crtn = errSecInternalComponent;
+ break;
+ default:
+ certReqDbg("postPendingRequest: unexpected rtn %lu", (unsigned long)crtn);
+ break;
+ }
+ CssmError::throwMe(crtn);
+}
+