1/*
2 * Copyright (c) 2002-2004,2011-2014 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24//
25// CertificateRequest.cpp
26//
27#include <security_keychain/CertificateRequest.h>
28#include <Security/oidsalg.h>
29#include <Security/SecKey.h>
30#include <Security/SecKeyPriv.h>
31#include <Security/cssmapi.h>
32#include <string.h>
33#include <dotMacTp.h>
34#include <Security/oidsattr.h>
35#include <security_utilities/simpleprefs.h>
36#include <SecBase.h>
37
38/* one top-level prefs file for all of .mac cert requests */
39#define DOT_MAC_REQ_PREFS	"com.apple.security.certreq"
40
41/*
42 * Within that dictionary is a set of per-policy dictionaries; the key in the
43 * top-level prefs for these dictionaries is the raw policy OID data encoded
44 * as an ASCII string.
45 *
46 * Within one per-policy dictionary exists a number of per-user dictionaries,
47 * with the username key as a string. Note that this user name, the one passed to the
48 * .mac server, does NOT have to have any relation to the current Unix user name; one
49 * Unix user can have multiple .mac accounts.
50 *
51 *
52 * Within the per-policy, per user dictionary are these two values, both stored
53 * as raw data (CFData) blobs.
54 */
55#define DOT_MAC_REF_ID_KEY	"refId"
56#define DOT_MAC_CERT_KEY	"certificate"
57
58/* Domain for .mac cert requests */
59#define DOT_MAC_DOMAIN_KEY  "domain"
60#define DOT_MAC_DOMAIN      "mac.com"
61
62/* Hosts for .mac cert requests */
63#define DOT_MAC_MGMT_HOST   "certmgmt"
64#define DOT_MAC_INFO_HOST   "certinfo"
65
66/*
67 * Compare two CSSM_DATAs (or two CSSM_OIDs), return true if identical.
68 */
69static
70bool nssCompareCssmData(
71	const CSSM_DATA *data1,
72	const CSSM_DATA *data2)
73{
74	if((data1 == NULL) || (data1->Data == NULL) ||
75	   (data2 == NULL) || (data2->Data == NULL) ||
76	   (data1->Length != data2->Length)) {
77		return false;
78	}
79	if(data1->Length != data2->Length) {
80		return false;
81	}
82	if(memcmp(data1->Data, data2->Data, data1->Length) == 0) {
83		return true;
84	}
85	else {
86		return false;
87	}
88}
89
90/* any nonzero value means true */
91static bool attrBoolValue(
92	const SecCertificateRequestAttribute *attr)
93{
94	if((attr->value.Data != NULL) &&
95	   (attr->value.Length != 0) &&
96	   (attr->value.Data[0] != 0)) {
97		return true;
98	}
99	else {
100		return false;
101	}
102}
103
104static void tokenizeName(
105		const CSSM_DATA		*inName,    /* required */
106		CSSM_DATA			*outName,   /* required */
107		CSSM_DATA			*outDomain) /* optional */
108{
109    if (!inName || !outName) return;
110    CSSM_SIZE idx = 0;
111    CSSM_SIZE stopIdx = inName->Length;
112    uint8 *p = inName->Data;
113    *outName = *inName;
114    if (outDomain) {
115        outDomain->Length = idx;
116        outDomain->Data = p;
117    }
118    if (!p) return;
119    while (idx < stopIdx) {
120        if (*p++ == '@') {
121            outName->Length = idx;
122            if (outDomain) {
123                outDomain->Length = inName->Length - (idx + 1);
124                outDomain->Data = p;
125            }
126            break;
127        }
128        idx++;
129    }
130}
131
132using namespace KeychainCore;
133
134CertificateRequest::CertificateRequest(const CSSM_OID &policy,
135		CSSM_CERT_TYPE certificateType,
136		CSSM_TP_AUTHORITY_REQUEST_TYPE requestType,
137		SecKeyRef privateKeyItemRef,
138		SecKeyRef publicKeyItemRef,
139		const SecCertificateRequestAttributeList *attributeList,
140		bool isNew /* = true */)
141		:	mAlloc(Allocator::standard()),
142			mTP(gGuidAppleDotMacTP),
143			mCL(gGuidAppleX509CL),
144			mPolicy(mAlloc, policy.Data, policy.Length),
145			mCertType(certificateType),
146			mReqType(requestType),
147			mPrivKey(NULL),
148			mPubKey(NULL),
149			mEstTime(0),
150			mRefId(mAlloc),
151			mCertState(isNew ? CRS_New : CRS_Reconstructed),
152			mCertData(mAlloc),
153			mUserName(mAlloc),
154			mPassword(mAlloc),
155			mHostName(mAlloc),
156			mDomain(mAlloc),
157			mDoRenew(false),
158			mIsAsync(false),
159			mMutex(Mutex::recursive)
160{
161	StLock<Mutex>_(mMutex);
162	certReqDbg("CertificateRequest construct");
163
164	/* Validate policy OID. */
165	if(!(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
166	     nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
167	     nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
168	     nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy))) {
169		certReqDbg("CertificateRequest(): unknown policy oid");
170		MacOSError::throwMe(errSecParam);
171	}
172	if(privateKeyItemRef) {
173		mPrivKey = privateKeyItemRef;
174		CFRetain(mPrivKey);
175	}
176	if(publicKeyItemRef) {
177		mPubKey = publicKeyItemRef;
178		CFRetain(mPubKey);
179	}
180
181	/* parse attr array */
182	if(attributeList == NULL) {
183		return;
184	}
185
186	bool doPendingRequest = false;
187	for(unsigned dex=0; dex<attributeList->count; dex++) {
188		const SecCertificateRequestAttribute *attr = &attributeList->attr[dex];
189
190		if((attr->oid.Data == NULL) || (attr->value.Data == NULL)) {
191			MacOSError::throwMe(errSecParam);
192		}
193		if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME, &attr->oid)) {
194            CSSM_DATA userName = { 0, NULL };
195            CSSM_DATA domainName = { 0, NULL };
196            tokenizeName(&attr->value, &userName, &domainName);
197            if (!domainName.Length || !domainName.Data) {
198                domainName.Length = strlen(DOT_MAC_DOMAIN);
199                domainName.Data = (uint8*) DOT_MAC_DOMAIN;
200            }
201			mUserName.copy(userName);
202            mDomain.copy(domainName);
203		}
204		else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD, &attr->oid)) {
205			mPassword.copy(attr->value);
206		}
207		else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME, &attr->oid)) {
208			mHostName.copy(attr->value);
209		}
210		else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_RENEW, &attr->oid)) {
211			/*
212			 * any nonzero value means true
213			 * FIXME: this is deprecated, Treadstone doesn't allow this. Reject this
214			 * request? Ignore?
215			 */
216			mDoRenew = attrBoolValue(attr);
217		}
218		else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_ASYNC, &attr->oid)) {
219			/* any nonzero value means true */
220			mIsAsync = attrBoolValue(attr);
221		}
222		else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_IS_PENDING, &attr->oid)) {
223			/* any nonzero value means true */
224			doPendingRequest = attrBoolValue(attr);
225		}
226
227		else {
228			certReqDbg("CertificateRequest(): unknown name/value oid");
229			MacOSError::throwMe(errSecParam);
230		}
231	}
232	if(mCertState == CRS_Reconstructed) {
233		/* see if we have a refId or maybe even a cert in prefs */
234		retrieveResults();
235		if(mCertData.data() != NULL) {
236			mCertState = CRS_HaveCert;
237		}
238		else if(mRefId.data() != NULL) {
239			mCertState = CRS_HaveRefId;
240		}
241		else if(doPendingRequest) {
242			/* ask the server if there's a request pending */
243			postPendingRequest();
244			/* NOT REACHED - that always throws */
245		}
246		else {
247			certReqDbg("CertificateRequest(): nothing in prefs");
248			/* Nothing found in prefs; nothing to go by */
249			MacOSError::throwMe(errSecItemNotFound);
250		}
251	}
252}
253
254CertificateRequest::~CertificateRequest() throw()
255{
256	StLock<Mutex>_(mMutex);
257	certReqDbg("CertificateRequest destruct");
258
259	if(mPrivKey) {
260		CFRelease(mPrivKey);
261	}
262	if(mPubKey) {
263		CFRelease(mPubKey);
264	}
265}
266
267#pragma mark ----- cert request submit -----
268
269void CertificateRequest::submit(
270	sint32 *estimatedTime)
271{
272	StLock<Mutex>_(mMutex);
273	CSSM_DATA &policy = mPolicy.get();
274	if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
275	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
276	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
277	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
278		return submitDotMac(estimatedTime);
279	}
280	else {
281		/* shouldn't be here, we already validated policy in constructor */
282		assert(0);
283		certReqDbg("CertificateRequest::submit(): bad policy");
284		MacOSError::throwMe(errSecParam);
285	}
286}
287
288void CertificateRequest::submitDotMac(
289	sint32 *estimatedTime)
290{
291	StLock<Mutex>_(mMutex);
292	CSSM_RETURN							crtn;
293	CSSM_TP_AUTHORITY_ID				tpAuthority;
294	CSSM_TP_AUTHORITY_ID				*tpAuthPtr = NULL;
295	CSSM_NET_ADDRESS					tpNetAddrs;
296	CSSM_APPLE_DOTMAC_TP_CERT_REQUEST	certReq;
297	CSSM_TP_REQUEST_SET					reqSet;
298	CSSM_CSP_HANDLE						cspHand = 0;
299	CSSM_X509_TYPE_VALUE_PAIR			tvp;
300	CSSM_TP_CALLERAUTH_CONTEXT			callerAuth;
301	CSSM_FIELD							policyField;
302	CSSM_DATA							refId = {0, NULL};
303	const CSSM_KEY						*privKey;
304	const CSSM_KEY						*pubKey;
305	OSStatus							ortn;
306
307	if(mCertState != CRS_New) {
308		certReqDbg("CertificateRequest: can only submit a new request");
309		MacOSError::throwMe(errSecParam);
310	}
311	if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
312		certReqDbg("CertificateRequest: user name and password required");
313		MacOSError::throwMe(errSecParam);
314	}
315
316	/* get keys and CSP handle in CSSM terms */
317	if((mPrivKey == NULL) || (mPubKey == NULL)) {
318		certReqDbg("CertificateRequest: pub and priv keys required");
319		MacOSError::throwMe(errSecParam);
320	}
321	ortn = SecKeyGetCSSMKey(mPrivKey, &privKey);
322	if(ortn) {
323		MacOSError::throwMe(ortn);
324	}
325	ortn = SecKeyGetCSSMKey(mPubKey, &pubKey);
326	if(ortn) {
327		MacOSError::throwMe(ortn);
328	}
329	ortn = SecKeyGetCSPHandle(mPrivKey, &cspHand);
330	if(ortn) {
331		MacOSError::throwMe(ortn);
332	}
333
334	/*
335	 * CSSM_X509_TYPE_VALUE_PAIR_PTR - one pair for now.
336	 * Caller passes in user name like "johnsmith"; in the CSR,
337	 * we write "johnsmith@mac.com".
338	 */
339	tvp.type = CSSMOID_CommonName;
340	tvp.valueType = BER_TAG_PKIX_UTF8_STRING;
341	CssmAutoData fullUserName(mAlloc);
342	size_t nameLen = mUserName.length();
343	size_t domainLen = mDomain.length();
344	fullUserName.malloc(nameLen + 1 + domainLen);
345	tvp.value = fullUserName.get();
346	memmove(tvp.value.Data, mUserName.data(), nameLen);
347	memmove(tvp.value.Data + nameLen, "@", 1);
348	memmove(tvp.value.Data + nameLen + 1, mDomain.data(), domainLen);
349
350	/* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */
351	memset(&certReq, 0, sizeof(certReq));
352	certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
353	certReq.cspHand = cspHand;
354	certReq.clHand = mCL->handle();
355	certReq.numTypeValuePairs = 1;
356	certReq.typeValuePairs = &tvp;
357	certReq.publicKey = const_cast<CSSM_KEY_PTR>(pubKey);
358	certReq.privateKey = const_cast<CSSM_KEY_PTR>(privKey);
359	certReq.userName = mUserName.get();
360	certReq.password = mPassword.get();
361	if(mDoRenew) {
362		certReq.flags |= CSSM_DOTMAC_TP_SIGN_RENEW;
363	}
364	/* we don't deal with CSR here, input or output */
365
366	/* now the rest of the args for CSSM_TP_SubmitCredRequest() */
367	reqSet.Requests = &certReq;
368	reqSet.NumberOfRequests = 1;
369	policyField.FieldOid = mPolicy;
370	policyField.FieldValue.Data = NULL;
371	policyField.FieldValue.Length = 0;
372	memset(&callerAuth, 0, sizeof(callerAuth));
373	callerAuth.Policy.NumberOfPolicyIds = 1;
374	callerAuth.Policy.PolicyIds = &policyField;
375	ortn = SecKeyGetCredentials(mPrivKey,
376		CSSM_ACL_AUTHORIZATION_SIGN,
377		kSecCredentialTypeDefault,
378		const_cast<const CSSM_ACCESS_CREDENTIALS **>(&callerAuth.CallerCredentials));
379	if(ortn) {
380		certReqDbg("CertificateRequest: SecKeyGetCredentials error");
381		MacOSError::throwMe(ortn);
382	}
383
384	CssmAutoData hostName(mAlloc);
385    tpAuthority.AuthorityCert = NULL;
386    tpAuthority.AuthorityLocation = &tpNetAddrs;
387    tpNetAddrs.AddressType = CSSM_ADDR_NAME;
388	if(mHostName.data() != NULL) {
389		tpNetAddrs.Address = mHostName.get();
390	} else {
391        unsigned hostLen = strlen(DOT_MAC_MGMT_HOST);
392        hostName.malloc(hostLen + 1 + domainLen);
393        tpNetAddrs.Address = hostName.get();
394        memmove(tpNetAddrs.Address.Data, DOT_MAC_MGMT_HOST, hostLen);
395        memmove(tpNetAddrs.Address.Data + hostLen, ".", 1);
396        memmove(tpNetAddrs.Address.Data + hostLen + 1, mDomain.data(), domainLen);
397    }
398    tpAuthPtr = &tpAuthority;
399
400	/* go */
401	crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
402		tpAuthPtr,
403		CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
404		&reqSet,
405		&callerAuth,
406		&mEstTime,
407		&refId);	// CSSM_DATA_PTR ReferenceIdentifier
408
409	/* handle return, store results */
410	switch(crtn) {
411		case CSSM_OK:
412			/* refID is a cert, we have to store it in prefs for later retrieval. */
413			certReqDbg("submitDotMac: full success, storing cert");
414			if(!mIsAsync) {
415				/* store in prefs if not running in async mode */
416				ortn = storeResults(NULL, &refId);
417				if(ortn) {
418					crtn = ortn;
419				}
420			}
421			/* but keep a local copy too */
422			mCertData.copy(refId);
423			mCertState = CRS_HaveCert;
424			if(estimatedTime) {
425				/* it's ready right now */
426				*estimatedTime = 0;
427			}
428			break;
429
430		case CSSMERR_APPLE_DOTMAC_REQ_QUEUED:
431			/* refID is the blob we use in CSSM_TP_RetrieveCredResult() */
432			certReqDbg("submitDotMac: queued, storing refId");
433			mRefId.copy(refId);
434			/* return success - this crtn is not visible at API */
435			crtn = CSSM_OK;
436			if(!mIsAsync) {
437				/* store in prefs if not running in async mode */
438				ortn = storeResults(&refId, NULL);
439				if(ortn) {
440					crtn = ortn;
441				}
442			}
443			mCertState = CRS_HaveRefId;
444			if(estimatedTime) {
445				*estimatedTime = mEstTime;
446			}
447			break;
448
449		case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT:
450			/* refID is a URL, caller obtains via getReturnData() */
451			certReqDbg("submitDotMac: redirect");
452			mRefId.copy(refId);
453			mCertState = CRS_HaveOtherData;
454			break;
455
456		default:
457			/* all others are fatal errors, thrown below */
458			break;
459	}
460	if(refId.Data) {
461		/* mallocd on our behalf by TP */
462		free(refId.Data);
463	}
464	if(crtn) {
465		CssmError::throwMe(crtn);
466	}
467}
468
469#pragma mark ----- cert request get result -----
470
471void CertificateRequest::getResult(
472	sint32			*estimatedTime,		// optional
473	CssmData		&certData)
474{
475	StLock<Mutex>_(mMutex);
476	CSSM_DATA &policy = mPolicy.get();
477	if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
478	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
479	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
480	   nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
481		return getResultDotMac(estimatedTime, certData);
482	}
483	else {
484		/* shouldn't be here, we already validated policy in constructor */
485		assert(0);
486		certReqDbg("CertificateRequest::getResult(): bad policy");
487		MacOSError::throwMe(errSecParam);
488	}
489}
490
491void CertificateRequest::getResultDotMac(
492	sint32			*estimatedTime,		// optional
493	CssmData		&certData)
494{
495	StLock<Mutex>_(mMutex);
496	switch(mCertState) {
497		case CRS_HaveCert:
498			/* trivial case, we already have what caller is looking for */
499			certReqDbg("getResultDotMac: have the cert right now");
500			assert(mCertData.data() != NULL);
501			certData = mCertData.get();
502			if(estimatedTime) {
503				*estimatedTime = 0;
504			}
505			break;
506		case CRS_HaveRefId:
507		{
508			/* ping the server */
509			certReqDbg("getResultDotMac: CRS_HaveRefId; polling server");
510			assert(mRefId.data() != NULL);
511			CSSM_BOOL ConfirmationRequired;
512			CSSM_TP_RESULT_SET_PTR resultSet = NULL;
513			CSSM_RETURN crtn;
514
515			crtn = CSSM_TP_RetrieveCredResult(mTP->handle(),
516				&mRefId.get(),
517				NULL,				// CallerAuthCredentials
518				&mEstTime,
519				&ConfirmationRequired,
520				&resultSet);
521			switch(crtn) {
522				case CSSM_OK:
523					break;
524				case CSSMERR_TP_CERT_NOT_VALID_YET:
525					/*
526					 * By convention, this means "not ready yet".
527					 * The dot mac server does not have a way of telling us the
528					 * estimated time on a straight lookup like this (we only get
529					 * an estimated completion time on the initial request), so we
530					 * fake it.
531					 */
532					certReqDbg("getResultDotMac: polled server, not ready yet");
533					if(estimatedTime) {
534						*estimatedTime = (mEstTime) ? mEstTime : 1;
535					}
536					MacOSError::throwMe(CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING);
537				default:
538					certReqDbg("CSSM_TP_RetrieveCredResult error");
539					CssmError::throwMe(crtn);
540			}
541			if(resultSet == NULL) {
542				certReqDbg("***CSSM_TP_RetrieveCredResult OK, but no result set");
543				MacOSError::throwMe(errSecInternalComponent);
544			}
545			if(resultSet->NumberOfResults != 1) {
546				certReqDbg("***CSSM_TP_RetrieveCredResult OK, NumberOfResults (%lu)",
547					(unsigned long)resultSet->NumberOfResults);
548				MacOSError::throwMe(errSecInternalComponent);
549			}
550			if(resultSet->Results == NULL) {
551				certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result set");
552				MacOSError::throwMe(errSecInternalComponent);
553			}
554			certReqDbg("getResultDotMac: polled server, SUCCESS");
555			CSSM_DATA_PTR result = (CSSM_DATA_PTR)resultSet->Results;
556			if(result->Data == NULL) {
557				certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result");
558				MacOSError::throwMe(errSecInternalComponent);
559			}
560			mCertData.copy(*result);
561			certData = mCertData.get();
562			mCertState = CRS_HaveCert;
563			if(estimatedTime) {
564				*estimatedTime = 0;
565			}
566
567			/*
568			 * Free the stuff allocated on our behalf by TP.
569			 * FIXME - are we sure CssmClient is using alloc, free, etc.?
570			 */
571			free(result->Data);
572			free(result);
573			free(resultSet);
574			break;
575		}
576		default:
577			/* what do we do with this? */
578			certReqDbg("CertificateRequest::getResultDotMac(): bad state");
579			MacOSError::throwMe(errSecInternalComponent);
580	}
581
582	/*
583	 * One more thing: once we pass a cert back to caller, we erase
584	 * the record of this transaction from prefs.
585	 */
586	assert(mCertData.data() != NULL);
587	assert(mCertData.data() == certData.Data);
588	removeResults();
589}
590
591/*
592 * Obtain policy/error specific return data blob. We own the data, it's
593 * not copied.
594 */
595void CertificateRequest::getReturnData(
596	CssmData	&rtnData)
597{
598	StLock<Mutex>_(mMutex);
599	rtnData = mRefId.get();
600}
601
602#pragma mark ----- preferences support -----
603
604/* Current user as CFString, for use as key in per-policy dictionary */
605CFStringRef CertificateRequest::createUserKey()
606{
607	StLock<Mutex>_(mMutex);
608	return CFStringCreateWithBytes(NULL, (UInt8 *)mUserName.data(), mUserName.length(),
609		kCFStringEncodingUTF8, false);
610}
611
612#define MAX_OID_LEN	2048		// way big... */
613
614/* current policy as CFString, for use as key in prefs dictionary */
615CFStringRef CertificateRequest::createPolicyKey()
616{
617	StLock<Mutex>_(mMutex);
618	char oidstr[MAX_OID_LEN];
619	unsigned char *inp = (unsigned char *)mPolicy.data();
620	char *outp = oidstr;
621	CFIndex len = mPolicy.length();
622	for(CFIndex dex=0; dex<len; dex++) {
623		sprintf(outp, "%02X ", *inp++);
624		outp += 3;
625	}
626	return CFStringCreateWithBytes(NULL, (UInt8 *)oidstr, len * 3,
627		kCFStringEncodingUTF8, false);
628}
629
630/*
631 * Store cert data or refId in prefs. If both are NULL, delete the
632 * user dictionary entry from the policy dictionary if there, and then
633 * delete the policy dictionary if it's empty.
634 */
635OSStatus CertificateRequest::storeResults(
636	const CSSM_DATA		*refId,			// optional, for queued requests
637	const CSSM_DATA		*certData)		// optional, for immediate completion
638{
639	StLock<Mutex>_(mMutex);
640	assert(mPolicy.data() != NULL);
641	assert(mUserName.data() != NULL);
642	assert(mDomain.data() != NULL);
643
644	bool deleteEntry = ((refId == NULL) && (certData == NULL));
645
646	/* get a mutable copy of the existing prefs, or a fresh empty one */
647	MutableDictionary *prefsDict = MutableDictionary::CreateMutableDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
648	if (prefsDict == NULL)
649	{
650		prefsDict = new MutableDictionary();
651	}
652
653	/* get a mutable copy of the dictionary for this policy, or a fresh empty one */
654	CFStringRef policyKey = createPolicyKey();
655	MutableDictionary *policyDict = prefsDict->copyMutableDictValue(policyKey);
656
657	CFStringRef userKey = createUserKey();
658	if(deleteEntry) {
659		/* remove user dictionary from this policy dictionary */
660		policyDict->removeValue(userKey);
661	}
662	else {
663		/* get a mutable copy of the dictionary for this user, or a fresh empty one */
664		MutableDictionary *userDict = policyDict->copyMutableDictValue(userKey);
665
666        CFStringRef domainKey = CFStringCreateWithBytes(NULL, (UInt8 *)mDomain.data(), mDomain.length(), kCFStringEncodingUTF8, false);
667        userDict->setValue(CFSTR(DOT_MAC_DOMAIN_KEY), domainKey);
668        CFRelease(domainKey);
669
670		/* write refId and/or cert --> user dictionary */
671		if(refId) {
672			userDict->setDataValue(CFSTR(DOT_MAC_REF_ID_KEY), refId->Data, refId->Length);
673		}
674		if(certData) {
675			userDict->setDataValue(CFSTR(DOT_MAC_CERT_KEY), certData->Data, certData->Length);
676		}
677
678		/* new user dictionary --> policy dictionary */
679		policyDict->setValue(userKey, userDict->dict());
680		delete userDict;
681	}
682	CFRelease(userKey);
683
684	/* new policy dictionary to prefs dictionary, or nuke it */
685	if(policyDict->count() == 0) {
686		prefsDict->removeValue(policyKey);
687	}
688	else {
689		prefsDict->setValue(policyKey, policyDict->dict());
690	}
691	CFRelease(policyKey);
692	delete policyDict;
693
694	/* prefs --> disk */
695	OSStatus ortn = errSecSuccess;
696	if(!prefsDict->writePlistToPrefs(DOT_MAC_REQ_PREFS, Dictionary::US_User)) {
697		certReqDbg("storeResults: error writing prefs to disk");
698		ortn = errSecIO;
699	}
700	delete prefsDict;
701	return ortn;
702}
703
704/*
705 * Attempt to fetch mCertData or mRefId from preferences.
706 */
707void CertificateRequest::retrieveResults()
708{
709	StLock<Mutex>_(mMutex);
710	assert(mPolicy.data() != NULL);
711	assert(mUserName.data() != NULL);
712
713	/* get the .mac cert prefs as a dictionary */
714	Dictionary *pd = Dictionary::CreateDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
715	if (pd == NULL)
716	{
717		certReqDbg("retrieveResults: no prefs found");
718		return;
719	}
720
721	auto_ptr<Dictionary> prefsDict(pd);
722
723	/* get dictionary for current policy */
724	CFStringRef policyKey = createPolicyKey();
725	Dictionary *policyDict = prefsDict->copyDictValue(policyKey);
726	CFRelease(policyKey);
727	if(policyDict != NULL) {
728		/* dictionary for user */
729		CFStringRef userKey = createUserKey();
730		Dictionary *userDict = policyDict->copyDictValue(userKey);
731		if(userDict != NULL) {
732			/* is there a cert in there? */
733			CFDataRef val = userDict->getDataValue(CFSTR(DOT_MAC_CERT_KEY));
734			if(val) {
735				mCertData.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
736			}
737
738			/* how about refId? */
739			val = userDict->getDataValue(CFSTR(DOT_MAC_REF_ID_KEY));
740			if(val) {
741				mRefId.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
742			}
743			delete userDict;
744		}
745		CFRelease(userKey);
746		delete policyDict;
747	}
748}
749
750/*
751 * Remove all trace of current policy/user. Called when we successfully transferred
752 * the cert back to caller.
753 */
754void CertificateRequest::removeResults()
755{
756	StLock<Mutex>_(mMutex);
757	assert(mPolicy.data() != NULL);
758	assert(mUserName.data() != NULL);
759	storeResults(NULL, NULL);
760}
761
762/*
763 * Have the TP ping the server to see of there's a request pending for the current
764 * user. Always throws: either
765 * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING  -- request pending
766 * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING  -- no request pending
767 * errSecParam -- no user, no password
768 * other gross errors, e.g. errSecIO for server connection failure
769 *
770 * The distinguishing features about this TP request are:
771 *
772 * policy OID = CSSMOID_DOTMAC_CERT_REQ_{IDENTITY,EMAIL_SIGN,EMAIL_ENCRYPT,SHARED_SERVICES}
773 * CSSM_TP_AUTHORITY_REQUEST_TYPE = CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP
774 * CSSM_APPLE_DOTMAC_TP_CERT_REQUEST.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING
775 * must have userName and password
776 * hostname optional as usual
777 */
778void CertificateRequest::postPendingRequest()
779{
780	StLock<Mutex>_(mMutex);
781	CSSM_RETURN							crtn;
782	CSSM_TP_AUTHORITY_ID				tpAuthority;
783	CSSM_TP_AUTHORITY_ID				*tpAuthPtr = NULL;
784	CSSM_NET_ADDRESS					tpNetAddrs;
785	CSSM_APPLE_DOTMAC_TP_CERT_REQUEST	certReq;
786	CSSM_TP_REQUEST_SET					reqSet;
787	CSSM_TP_CALLERAUTH_CONTEXT			callerAuth;
788	CSSM_FIELD							policyField;
789	CSSM_DATA							refId = {0, NULL};
790
791	assert(mCertState == CRS_Reconstructed);
792	if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
793		certReqDbg("postPendingRequest: user name and password required");
794		MacOSError::throwMe(errSecParam);
795	}
796
797	/* Fill in the CSSM_APPLE_DOTMAC_TP_CERT_REQUEST */
798	memset(&certReq, 0, sizeof(certReq));
799	certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
800	certReq.userName = mUserName.get();
801	certReq.password = mPassword.get();
802	certReq.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING;
803
804	/* now the rest of the args for CSSM_TP_SubmitCredRequest() */
805	reqSet.Requests = &certReq;
806	reqSet.NumberOfRequests = 1;
807	/*
808	 * This OID actually doesn't matter - right? This RPC doesn't know about
809	 * which request we seek...
810	 */
811	policyField.FieldOid = mPolicy;
812	policyField.FieldValue.Data = NULL;
813	policyField.FieldValue.Length = 0;
814	memset(&callerAuth, 0, sizeof(callerAuth));
815	callerAuth.Policy.NumberOfPolicyIds = 1;
816	callerAuth.Policy.PolicyIds = &policyField;
817	/* no other creds here */
818
819	if(mHostName.data() != NULL) {
820		tpAuthority.AuthorityCert = NULL;
821		tpAuthority.AuthorityLocation = &tpNetAddrs;
822		tpNetAddrs.AddressType = CSSM_ADDR_NAME;
823		tpNetAddrs.Address = mHostName.get();
824		tpAuthPtr = &tpAuthority;
825	}
826
827	/* go */
828	crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
829		tpAuthPtr,
830		CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP,
831		&reqSet,
832		&callerAuth,
833		&mEstTime,
834		&refId);	// CSSM_DATA_PTR ReferenceIdentifier
835
836	if(refId.Data) {
837		/* shouldn't be any but just in case.... */
838		free(refId.Data);
839	}
840	switch(crtn) {
841		case CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING:
842			certReqDbg("postPendingRequest: REQ_IS_PENDING");
843			break;
844		case CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING:
845			certReqDbg("postPendingRequest: NO_REQ_PENDING");
846			break;
847		case CSSM_OK:
848			/* should never happen */
849			certReqDbg("postPendingRequest: unexpected success!");
850			crtn = errSecInternalComponent;
851			break;
852		default:
853			certReqDbg("postPendingRequest: unexpected rtn %lu", (unsigned long)crtn);
854			break;
855	}
856	CssmError::throwMe(crtn);
857}
858
859