1/*
2 * Copyright (c) 2004 Apple Computer, 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 * dotMacTpRpcGlue.cpp - glue between CDSA and XMLRPC for .mac TP
26 */
27
28#include "dotMacTpRpcGlue.h"
29#include "dotMacTpUtils.h"
30#include "dotMacTpDebug.h"
31#include "dotMacTpMutils.h"
32#include <stdint.h>
33#include <CoreFoundation/CoreFoundation.h>
34#include "dotMacXmlRpc.h"
35#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
36#include <Security/cssmapple.h>
37
38/* Dump XMLRPC Result dictionary */
39#if		DICTIONARY_DEBUG
40#define RESULTS_DICTIONARY_DEBUG		1
41#define REQUEST_DICTIONARY_DEBUG		1
42#else
43#define RESULTS_DICTIONARY_DEBUG		0
44#define REQUEST_DICTIONARY_DEBUG		0
45#endif
46
47/*
48 * Force/simulate SuccessQueued errors to test this module before the server can
49 * actually generate those errors.
50 */
51#ifndef NDEBUG
52#define FORCE_SUCCESS_QUEUED			0
53#else
54#define FORCE_SUCCESS_QUEUED			0
55#endif
56#if		FORCE_SUCCESS_QUEUED
57/* actual behavior is tweakable by debugger */
58int forceQueued = 1;
59#endif  /* FORCE_SUCCESS_QUEUED */
60
61/*
62 * Force SuccessQueued even if server returns a really bad error
63 */
64#ifndef NDEBUG
65#define FORCE_SUCCESS_QUEUED_ALWAYS		0
66#else
67#define FORCE_SUCCESS_QUEUED			0
68#endif
69
70#define CFRELEASE(cf)		\
71	if(cf != NULL) {		\
72		CFRelease(cf);		\
73	}
74
75/*
76 * Constant strings for Treadstone XMLRPC API.
77 */
78
79/*
80 * Method names.
81 */
82static CFStringRef kMethodSignIChatEncrypt		= CFSTR("sign." DOT_MAC_CERT_TYPE_ICHAT);
83static CFStringRef kMethodSignSharedServices	= CFSTR("sign." DOT_MAC_CERT_TYPE_SHARED_SERVICES);
84static CFStringRef kMethodSignEmailSigning		= CFSTR("sign." DOT_MAC_CERT_TYPE_EMAIL_SIGNING);
85static CFStringRef kMethodSignEmailEncryption	= CFSTR("sign." DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT);
86static CFStringRef kMethodStatusIChatEncrypt	= CFSTR("status." DOT_MAC_CERT_TYPE_ICHAT);
87static CFStringRef kMethodStatusSharedServices	= CFSTR("status." DOT_MAC_CERT_TYPE_SHARED_SERVICES);
88static CFStringRef kMethodStatusEmailSigning	= CFSTR("status." DOT_MAC_CERT_TYPE_EMAIL_SIGNING);
89static CFStringRef kMethodStatusEmailEncryption	= CFSTR("status." DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT);
90static CFStringRef kMethodArchiveList			= CFSTR("archive.list");
91static CFStringRef kMethodArchiveSave			= CFSTR("archive.save");
92static CFStringRef kMethodArchiveFetch			= CFSTR("archive.fetch");
93static CFStringRef kMethodArchiveRemove			= CFSTR("archive.remove");
94
95/*
96 * Fixed parameter names.
97 */
98
99/* first param to sign */
100static CFStringRef kParamSignIssue					= CFSTR("issue");
101/*
102 * CertTypeTag as parameter to archive.save
103 * Also used as one of the out params from archive.list
104 */
105static CFStringRef kParamCertTypeIChat				= CFSTR(DOT_MAC_CERT_TYPE_ICHAT);
106static CFStringRef kParamCertTypeSharedServices		= CFSTR(DOT_MAC_CERT_TYPE_SHARED_SERVICES);
107static CFStringRef kParamCertTypeEmailEncryption	= CFSTR(DOT_MAC_CERT_TYPE_EMAIL_SIGNING);
108static CFStringRef kParamCertTypeEmailSigning		= CFSTR(DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT);
109
110/*
111 * names of values in an XMLRPC response
112 */
113static CFStringRef kResponseResultCode				= CFSTR("resultCode");
114/*
115 * FIXME: We don't use this: Should we?
116 */
117// static CFStringRef kResponseTimestamp			= CFSTR("timestamp");
118static CFStringRef kResponseResultBody				= CFSTR("resultBody");
119
120/*
121 * names of values in an archive.list dictionary
122 */
123static CFStringRef kArchiveListName					= CFSTR("name");
124static CFStringRef kArchiveListExpires				= CFSTR("expires");
125static CFStringRef kArchiveListType					= CFSTR("type");
126static CFStringRef kArchiveListSerialNumber			= CFSTR("serial");
127
128/*
129 * resultCode strings
130 */
131/* OK, resultBody contains the cert */
132static CFStringRef kResultSuccess				= CFSTR("Success");
133/* OK, resultBody contains seconds to availability of cert */
134static CFStringRef kResultQueued				= CFSTR("SuccessQueued");
135/* not really OK, caller must visit URL specified in resultBody */
136static CFStringRef kResultRedirected			= CFSTR("SuccessRedirected");
137static CFStringRef kResultFailed				= CFSTR("Failed");
138static CFStringRef kResultAlreadyExists			= CFSTR("FailedAlreadyExists");
139static CFStringRef kResultCertAlreadyExists		= CFSTR("FailedCertAlreadyExists");
140static CFStringRef kResultServiceError			= CFSTR("FailedServiceError");
141static CFStringRef kResultParameterError		= CFSTR("FailedParameterError");
142static CFStringRef kResultNotAllowed			= CFSTR("FailedNotAllowed");
143static CFStringRef kResultPendingCSR			= CFSTR("FailedPendingCSR");
144static CFStringRef kResultNoExistingCSR			= CFSTR("FailedNoExistingCSR");
145static CFStringRef kResultNotSupportForAccount  = CFSTR("FailedNotSupportedForAccount");
146static CFStringRef kResultCSRDidNotVerify		= CFSTR("FailedCSRDidNotVerify");
147static CFStringRef kResultNotImplemented		= CFSTR("NotImplemented");
148static CFStringRef kResultNotAuthorized			= CFSTR("NotAuthorized");
149static CFStringRef kResultNotAvailable			= CFSTR("NotAvailable");
150static CFStringRef kResultConsistencyCheck		= CFSTR("FailedConsistencyCheck");
151
152
153/* quickie parameter names which don't go over the wire, just for ordering */
154static CFStringRef kP1 = CFSTR("p1");
155static CFStringRef kP2 = CFSTR("p2");
156static CFStringRef kP3 = CFSTR("p3");
157static CFStringRef kP4 = CFSTR("p4");
158static CFStringRef kP5 = CFSTR("p5");
159
160/*
161 * Convert an XMLRPC resultCode string to an OSStatus/CSSM_RETURN.
162 */
163static OSStatus dotMacParseResult(
164	CFStringRef resultCode)
165{
166	if(CFEqual(resultCode, kResultSuccess)) {
167		#if		FORCE_SUCCESS_QUEUED
168		if(forceQueued) {
169			printf("...Forcing REQ_QUEUED status\n");
170			return CSSMERR_APPLE_DOTMAC_REQ_QUEUED;
171		}
172		#endif  /* FORCE_SUCCESS_QUEUED */
173		return noErr;
174	}
175	else if(CFEqual(resultCode, kResultQueued)) {
176		return CSSMERR_APPLE_DOTMAC_REQ_QUEUED;
177	}
178	else if(CFEqual(resultCode, kResultRedirected)) {
179		return CSSMERR_APPLE_DOTMAC_REQ_REDIRECT;
180	}
181	else if(CFEqual(resultCode, kResultFailed)) {
182		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_ERR;
183	}
184	else if(CFEqual(resultCode, kResultAlreadyExists) ||
185			CFEqual(resultCode, kResultCertAlreadyExists)) {
186		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_ALREADY_EXIST;
187	}
188	else if(CFEqual(resultCode, kResultParameterError)) {
189		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_PARAM;
190	}
191	else if(CFEqual(resultCode, kResultNotAllowed)) {
192		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_AUTH;
193	}
194	else if(CFEqual(resultCode, kResultPendingCSR)) {
195		return CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING;
196	}
197	else if(CFEqual(resultCode, kResultNoExistingCSR)) {
198		return CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING;
199	}
200	else if(CFEqual(resultCode, kResultNotSupportForAccount)) {
201		/* FIXME might want a .mac TP-specific error for this */
202		return CSSMERR_TP_REQUEST_REJECTED;
203	}
204	else if(CFEqual(resultCode, kResultCSRDidNotVerify)) {
205		return CSSMERR_APPLE_DOTMAC_CSR_VERIFY_FAIL;
206	}
207	else if(CFEqual(resultCode, kResultServiceError)) {
208		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_SERVICE_ERROR;
209	}
210	else if(CFEqual(resultCode, kResultNotImplemented)) {
211		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_UNIMPL;
212	}
213	else if(CFEqual(resultCode, kResultNotAuthorized)) {
214		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_AUTH;
215	}
216	else if(CFEqual(resultCode, kResultNotAvailable)) {
217		return CSSMERR_APPLE_DOTMAC_REQ_SERVER_NOT_AVAIL;
218	}
219	else if(CFEqual(resultCode, kResultConsistencyCheck)) {
220		return CSSMERR_APPLE_DOTMAC_FAILED_CONSISTENCY_CHECK;
221	}
222
223	else {
224		dotMacErrorLog("dotMacParseResult: unknown error\n");
225		return ioErr;
226	}
227}
228
229/* CSSM_DATA <--> CFStringRef */
230static inline CFStringRef cDataToCfstr(
231	const CSSM_DATA &cdata)
232{
233	return CFStringCreateWithBytes(NULL,
234		cdata.Data, (CFIndex)cdata.Length,
235		kCFStringEncodingASCII, false);
236}
237
238static void cfstrToCdata(
239	const CFStringRef	cfstr,
240	CSSM_DATA			&cdata,
241	Allocator			&alloc)
242{
243	CFDataRef cfData = CFStringCreateExternalRepresentation(NULL,
244		cfstr, kCFStringEncodingUTF8, 0);
245	if(cfData == NULL) {
246		dotMacErrorLog("dotMac cfstrToCdatafailure");
247		CssmError::throwMe(internalComponentErr);
248	}
249	cdata.Length = CFDataGetLength(cfData);
250	cdata.Data = (uint8 *)alloc.malloc(cdata.Length);
251	memmove(cdata.Data, CFDataGetBytePtr(cfData), cdata.Length);
252	CFRelease(cfData);
253}
254
255/*
256 * Convert between binary data - e.g. a cert serial number - and a CFStringRef.
257 * We do this with the same logic as SecurityInterface: if the data is 8 bytes
258 * or less, encode it as a decimal number, else stringify it as ASCII hex.
259 */
260static const char hexChars[] = "0123456789ABCDEF";
261
262static CFStringRef cDataToCfAsciiStr(
263	const CSSM_DATA &cdata)
264{
265	if(cdata.Length > sizeof(uint64)) {
266		char asciiData[(2 * cdata.Length) + 1];
267		const unsigned char *inp = (const unsigned char *)cdata.Data;
268		char *outp = asciiData;
269
270		for(unsigned dex=0; dex<cdata.Length; dex++) {
271			unsigned c = *inp++;
272			outp[1] = hexChars[c & 0xf];
273			c >>= 4;
274			outp[0] = hexChars[c];
275			outp += 2;
276		}
277		*outp = 0;
278		return CFStringCreateWithCString(NULL, asciiData, kCFStringEncodingASCII);
279	}
280	else {
281		uint64 value = 0;
282		for(unsigned i=0; i<cdata.Length; i++) {
283			value <<= 8;
284			value += cdata.Data[i];
285		}
286		char cStr[200];
287		snprintf(cStr, sizeof(cStr), "%llu", value);
288		return CFStringCreateWithCString(NULL, cStr, kCFStringEncodingASCII);
289	}
290}
291
292/*
293 * Convert a serial number string as created by cDataToCfAsciiStr() to
294 * a CSSM_DATA. We have to assume it's in decimal here.
295 */
296static void CfAsciiStrToCdata(
297	const CFStringRef	cfStr,
298	CSSM_DATA			&cdata,
299	Allocator			&alloc)
300{
301	/* the string MUST be in ASCII */
302	CFDataRef strData = CFStringCreateExternalRepresentation(NULL, cfStr,
303		kCFStringEncodingASCII, 0);
304	if(strData == NULL) {
305		dotMacDebug("dotMac CfAsciiStrToCdata: ASCII conversion FAILED!");
306		return;
307	}
308	unsigned len = (unsigned)CFDataGetLength(strData);
309	const char *inp = (const char *)CFDataGetBytePtr(strData);
310	char cStr[len + 1];
311	memmove(cStr, inp, len);
312	cStr[len] = '\0';
313
314	uint64 val = 0;
315	sscanf(cStr, "%llu", &val);
316
317	/* convert 64-bit val to byte array */
318	int byteNum = 7;			// byte within 64-bit val
319	unsigned shift = 7*8;		// bits to shift right to move that into one byte
320	uint64 mask = 0xff00000000000000ULL;
321
322	/* skip over zeroes in m.s. bytes */
323	while(val & mask) {
324		byteNum--;
325		shift -= 8;
326		mask >>= 8;
327	}
328
329	cdata.Data = (uint8 *)alloc.malloc(byteNum + 1);
330	cdata.Length = byteNum + 1;
331	uint8 *outp = cdata.Data;
332
333	do {
334		uint64 v = val >> shift;
335		*outp++ = v & 0xff;
336		shift -= 8;
337		byteNum--;
338	} while(byteNum >= 0);
339
340	CFRelease(strData);
341}
342
343static CFURLRef createUrl(
344	const char *schema,
345	const CSSM_DATA &hostName,
346	const char *path)
347{
348	int schemaLen = strlen(schema);
349	int urlLength = schemaLen + hostName.Length + strlen(path) + 1;
350	char *urlStr = (char *)malloc(urlLength);
351	memmove(urlStr, schema, schemaLen);
352	memmove(urlStr + schemaLen, hostName.Data, hostName.Length);
353	urlStr[schemaLen + hostName.Length] = '\0';
354	strcat(urlStr, path);
355	CFURLRef url = CFURLCreateWithBytes(NULL, (const UInt8 *)urlStr, urlLength-1,
356		kCFStringEncodingASCII, NULL);
357	dotMacDebug("dotMac createUrl: URL %s", urlStr);
358	free(urlStr);
359	return url;
360}
361
362OSStatus dotMacPostCertReq(
363	DotMacCertTypeTag	certType,
364	const CSSM_DATA		&userName,
365	const CSSM_DATA		&password,
366	const CSSM_DATA		&hostName,
367	bool				renew,				// this is obsolete; currently ignored
368	const CSSM_DATA		&csr,				// DER encoded
369	SecNssCoder			&coder,
370	sint32				&estTime,			// possibly returned
371	CSSM_DATA			&resultBodyData)	// possibly returned
372{
373	resultBodyData.Data = NULL;
374	resultBodyData.Length = 0;
375
376	/*
377	 * First gather arguments into CF form.
378	 */
379	CFURLRef url = createUrl(DOT_MAC_SIGN_SCHEMA, hostName, DOT_MAC_SIGN_PATH);
380	CFStringRef userStr = cDataToCfstr(userName);
381	CFStringRef pwdStr  = cDataToCfstr(password);
382	CFStringRef csrStr  = cDataToCfstr(csr);
383
384	/*
385	 * Now cook up arguments for an XMLRPC.
386	 */
387	CFMutableDictionaryRef argDict;
388	CFMutableArrayRef argOrder;
389	CFStringRef method;
390
391	switch(certType) {
392		case CSSM_DOT_MAC_TYPE_ICHAT:
393			method = kMethodSignIChatEncrypt;
394			break;
395		case CSSM_DOT_MAC_TYPE_SHARED_SERVICES:
396			method = kMethodSignSharedServices;
397			break;
398		case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT:
399			method = kMethodSignEmailEncryption;
400			break;
401		case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING:
402			method = kMethodSignEmailSigning;
403			break;
404		default:
405			return paramErr;
406	}
407
408	argDict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks,
409		&kCFTypeDictionaryValueCallBacks);
410	argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
411
412	/* this used to be "new" or "renew" */
413	CFDictionaryAddValue(argDict, kP1, kParamSignIssue);
414	CFArrayAppendValue(argOrder, kP1);
415
416	CFDictionaryAddValue(argDict, kP2, csrStr);
417	CFArrayAppendValue(argOrder, kP2);
418
419	OSStatus ortn;
420	CFDictionaryRef resultDict = NULL;
421	uint32_t httpStat;
422
423	ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder, userStr, pwdStr,
424		&resultDict, &httpStat);
425
426	if(ortn) {
427		#if FORCE_SUCCESS_QUEUED_ALWAYS
428		dotMacErrorLog("dotMacPostCertReq: FORCING queued success\n");
429		ortn = CSSMERR_APPLE_DOTMAC_REQ_QUEUED;
430		goto proceedQueued;
431		#endif
432		dotMacErrorLog("dotMacPostCertReq: XMLRPC returned %ld\n", ortn);
433		/* And we're dead - means RPC did not complete */
434		goto errOut;
435	}
436	if(resultDict == NULL) {
437		dotMacErrorLog("dotMacPostCertReq: XMLRPC failed to return a dictionary\n");
438		ortn = ioErr;
439		goto errOut;
440	}
441	#if RESULTS_DICTIONARY_DEBUG
442	dumpDictionary("Results dictionary", resultDict);
443	#endif
444
445	CFStringRef resultCode;
446	resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode);
447	if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) {
448		dotMacErrorLog("dotMacPostCertReq: resultCode is not a string\n");
449		ortn = ioErr;
450		goto errOut;
451	}
452	ortn = dotMacParseResult(resultCode);
453
454#if	FORCE_SUCCESS_QUEUED_ALWAYS
455proceedQueued:
456#endif
457
458	/* For some results, we have some data to give to caller */
459	switch(ortn) {
460		case noErr:								/* resultBody = PEM-encoded cert */
461		case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT: /* resultBody = URL */
462		{
463			CFStringRef resultBody;
464			resultBody = (CFStringRef)CFDictionaryGetValue(resultDict,
465					kResponseResultBody);
466			if((resultBody == NULL) ||(CFGetTypeID(resultBody) != CFStringGetTypeID())) {
467				dotMacErrorLog("dotMacPostCertReq: resultBody is not a string\n");
468				ortn = ioErr;
469				goto errOut;
470			}
471			CFIndex len = CFStringGetLength(resultBody);
472			coder.allocItem(resultBodyData, (size_t)len + 1);
473			if(!CFStringGetCString(resultBody, (char *)resultBodyData.Data, len+1,
474					kCFStringEncodingUTF8)) {
475				dotMacErrorLog("dotMacPostCertReq: resultBody is not convertible to "
476					"UTF8\n");
477				ortn = ioErr;
478			}
479			break;
480		}
481
482		case CSSMERR_APPLE_DOTMAC_REQ_QUEUED:
483		{
484			/*
485			 * Cook up an opaque reference ID which enables us to fetch the
486			 * result of this queued request at a later time.
487			 *
488			 * The estimated availability time is returned in the
489			 * resultsBody as a string value.
490			 */
491            CSSM_DATA host = { 0, NULL };
492            CSSM_DATA domain = { 0, NULL };
493            dotMacTokenizeHostName(hostName, host, domain);
494			ortn = dotMacEncodeRefId(userName, domain, certType, coder, resultBodyData);
495			if(ortn == noErr) {
496				ortn = CSSMERR_APPLE_DOTMAC_REQ_QUEUED;
497			}
498
499			/* Return the estimated time */
500			CFStringRef resultBody;
501			resultBody = (CFStringRef)CFDictionaryGetValue(resultDict,
502							kResponseResultBody);
503			if((resultBody == NULL) ||(CFGetTypeID(resultBody) != CFStringGetTypeID())) {
504				dotMacErrorLog("dotMacPostCertReq: resultBody is not a string\n");
505				ortn = ioErr;
506				goto errOut;
507			}
508			SInt32 timeValue = CFStringGetIntValue(resultBody);
509			estTime = (sint32) timeValue;
510			break;
511		}
512		default:
513			dotMacErrorLog("dotMacPostCertReq: unhandled result %d\n", (int)ortn);
514			break;
515	}
516
517errOut:
518	CFRelease(url);
519	CFRelease(userStr);
520	CFRelease(pwdStr);
521	CFRelease(csrStr);
522	CFRelease(argDict);
523	CFRelease(argOrder);
524	if(resultDict) {
525		CFRelease(resultDict);
526	}
527	return ortn;
528
529}
530
531/*
532 * Return archive list in one of two formats.
533 * Returned memory is allocated in provided allocator's space.
534 *
535 * This version is used when the app provided a v1 CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST,
536 * and it therefore expecting an array of DotMacArchive in return. This can be
537 * deleted when we've ensured that nobody is using the v1 request.
538 */
539static OSStatus dotMacReturnArchiveList_v1(
540	CFDictionaryRef		resultDict,
541	unsigned			*numArchives,	// RETURNED
542	DotMacArchive		**archives,		// RETURNED
543	Allocator			&alloc)
544{
545	CFArrayRef archList;
546	archList = (CFArrayRef)CFDictionaryGetValue(resultDict,
547			kResponseResultBody);
548	if((archList == NULL) || (CFGetTypeID(archList) != CFArrayGetTypeID())) {
549		dotMacErrorLog("archive(list): resultBody is not an array\n");
550		return ioErr;
551	}
552	assert(numArchives != NULL);
553	assert(archives != NULL);
554	CFIndex numEntries = CFArrayGetCount(archList);
555	*archives = (DotMacArchive *)alloc.malloc(sizeof(DotMacArchive) * numEntries);
556	memset(*archives, 0, sizeof(DotMacArchive) * numEntries);
557	for(CFIndex dex=0; dex<numEntries; dex++) {
558		DotMacArchive *dmarc = &((*archives)[dex]);
559		CFDictionaryRef dict =
560			(CFDictionaryRef)CFArrayGetValueAtIndex(archList, dex);
561		if((dict == NULL) || (CFGetTypeID(dict) != CFDictionaryGetTypeID())) {
562			dotMacErrorLog("archive(list): result element is not a dict\n");
563			return ioErr;
564		}
565
566		/* extract two fields from this dictionary: name and expiration date */
567		CFStringRef cfstr = (CFStringRef)CFDictionaryGetValue(dict,
568				kArchiveListName);
569		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
570			dotMacErrorLog("archive(list): archiveName is not a string\n");
571			return ioErr;
572		}
573		cfstrToCdata(cfstr, dmarc->archiveName, alloc);
574
575		cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListExpires);
576		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
577			dotMacErrorLog("archive(list): expires is not a string\n");
578			return ioErr;
579		}
580		cfstrToCdata(cfstr, dmarc->timeString, alloc);
581	}
582	*numArchives = numEntries;
583	return noErr;
584}
585
586/* V. 2, Treadstone style Archive list */
587static OSStatus dotMacReturnArchiveList_v2(
588	CFDictionaryRef		resultDict,
589	unsigned			*numArchives,	// RETURNED
590	DotMacArchive_v2	**archives,		// RETURNED
591	Allocator			&alloc)
592{
593	CFArrayRef archList;
594	archList = (CFArrayRef)CFDictionaryGetValue(resultDict,
595			kResponseResultBody);
596	if((archList == NULL) || (CFGetTypeID(archList) != CFArrayGetTypeID())) {
597		dotMacErrorLog("archive(list): resultBody is not an array\n");
598		return ioErr;
599	}
600	assert(numArchives != NULL);
601	assert(archives != NULL);
602	CFIndex numEntries = CFArrayGetCount(archList);
603	*archives = (DotMacArchive_v2 *)alloc.malloc(sizeof(DotMacArchive_v2) * numEntries);
604	memset(*archives, 0, sizeof(DotMacArchive_v2) * numEntries);
605	for(CFIndex dex=0; dex<numEntries; dex++) {
606		DotMacArchive_v2 *dmarc = &((*archives)[dex]);
607		CFDictionaryRef dict =
608			(CFDictionaryRef)CFArrayGetValueAtIndex(archList, dex);
609		if((dict == NULL) || (CFGetTypeID(dict) != CFDictionaryGetTypeID())) {
610			dotMacErrorLog("archive(list): result element is not a dict\n");
611			return ioErr;
612		}
613
614		/*
615		 * Extract 4 fields from this dictionary:
616		 * name
617		 * expiration date
618		 * certTypeTag
619		 * serial number
620		 */
621		CFStringRef cfstr = (CFStringRef)CFDictionaryGetValue(dict,
622				kArchiveListName);
623		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
624			dotMacErrorLog("archive(list): archiveName is not a string\n");
625			return ioErr;
626		}
627		cfstrToCdata(cfstr, dmarc->archiveName, alloc);
628
629		/* expiration date */
630		cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListExpires);
631		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
632			dotMacErrorLog("archive(list): expires is not a string\n");
633			return ioErr;
634		}
635		cfstrToCdata(cfstr, dmarc->timeString, alloc);
636
637		/* certTypeTag */
638		cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListType);
639		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
640			dotMacErrorLog("archive(list): CertType is not a string\n");
641			return ioErr;
642		}
643		if(CFEqual(cfstr, kParamCertTypeIChat)) {
644			dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_ICHAT;
645		}
646		else if(CFEqual(cfstr, kParamCertTypeSharedServices)) {
647			dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_SHARED_SERVICES;
648		}
649		else if(CFEqual(cfstr, kParamCertTypeEmailEncryption)) {
650			dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT;
651		}
652		else if(CFEqual(cfstr, kParamCertTypeEmailSigning)) {
653			dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_EMAIL_SIGNING;
654		}
655		else {
656			dmarc->certTypeTag = CSSM_DOT_MAC_TYPE_UNSPECIFIED;
657		}
658
659		/* serial number */
660		cfstr = (CFStringRef)CFDictionaryGetValue(dict, kArchiveListSerialNumber);
661		if((cfstr == NULL) || (CFGetTypeID(cfstr) != CFStringGetTypeID())) {
662			dotMacErrorLog("archive(list): Serial Number is not a string\n");
663			return ioErr;
664		}
665		CfAsciiStrToCdata(cfstr, dmarc->serialNumber, alloc);
666	}
667	*numArchives = numEntries;
668	return noErr;
669}
670
671/* post archive request */
672OSStatus dotMacPostArchiveReq(
673	uint32				version,
674	DotMacCertTypeTag	certTypeTag,
675	DotMacArchiveType	archiveType,
676	const CSSM_DATA		&userName,
677	const CSSM_DATA		&password,
678	const CSSM_DATA		&hostName,
679	const CSSM_DATA		*archiveName,
680	const CSSM_DATA		*pfxIn,			// for store only
681	const CSSM_DATA		*timeString,	// for store only
682	const CSSM_DATA		*serialNumber,	// for store only
683	CSSM_DATA			*pfxOut,		// RETURNED for fetch, allocated via alloc
684	unsigned			*numArchives,	// RETURNED for list
685	// at most one of the following is returned, and for list only
686	DotMacArchive		**archives_v1,	// RETURNED for list, allocated via alloc
687	DotMacArchive_v2	**archives_v2,
688	Allocator			&alloc)
689{
690	/* init return values */
691	if(pfxOut) {
692		pfxOut->Data = NULL;
693		pfxOut->Length = 0;
694	}
695	if(numArchives) {
696		*numArchives = 0;
697	}
698	if(archives_v1) {
699		*archives_v1 = NULL;
700	}
701	if(archives_v2) {
702		*archives_v2 = NULL;
703	}
704	/*
705	 * gather arguments into CF form.
706	 * NOTE: This implementation always does a Treadstone-style XMLRPC, regardless
707	 *       of what the caller (the app) is trying to do. If the app is doing
708	 *       a v1 (pre-Treadstone op), we use default/NULL values for params
709	 *       like serial number that we have to send to the server; in the case
710	 *		 of a list op, we return one of two forms of archive (DotMacArchive
711	 *		 or DotMacArchive_v2).
712	 * NOTE WELL: we rely on caller to validate required inputs, it just works
713	 *		 out a lot cleaner to verify there than here.
714	 */
715	CFURLRef url = createUrl(DOT_MAC_ARCHIVE_SCHEMA, hostName, DOT_MAC_ARCHIVE_PATH);
716	CFStringRef userStr = cDataToCfstr(userName);
717	CFStringRef pwdStr  = cDataToCfstr(password);
718	CFStringRef pfxInStr = NULL;
719	if(pfxIn) {
720		pfxInStr = cDataToCfstr(*pfxIn);
721	}
722	CFStringRef archiveNameStr = NULL;
723	if(archiveName) {
724		archiveNameStr = cDataToCfstr(*archiveName);
725	}
726	CFStringRef timeStringStr = NULL;
727	if(timeString) {
728		timeStringStr = cDataToCfstr(*timeString);
729	}
730
731	uint8 zero = 0;
732	CSSM_DATA zeroData = {1, &zero};
733	CFStringRef serialNumberStr = NULL;
734	CFStringRef certTypeStr = NULL;
735	if(archiveType == DMAT_Store) {
736		/*
737		 * Need a serial number for this. If caller didn't provide one,
738		 * make an empty one.
739		 * WARNING this actually will result in a failure on the server side
740		 * since the server requires the correct serial number.
741		 */
742		if(serialNumber == NULL) {
743			serialNumber = &zeroData;
744		}
745		serialNumberStr = cDataToCfAsciiStr(*serialNumber);
746
747		/* certTypeTag --> string */
748		switch(certTypeTag) {
749			case CSSM_DOT_MAC_TYPE_ICHAT:
750				certTypeStr = kParamCertTypeIChat;
751				break;
752			case CSSM_DOT_MAC_TYPE_SHARED_SERVICES:
753				certTypeStr = kParamCertTypeSharedServices;
754				break;
755			case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT:
756				certTypeStr = kParamCertTypeEmailEncryption;
757				break;
758			case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING:
759				certTypeStr = kParamCertTypeEmailSigning;
760				break;
761			default:
762				dotMacErrorLog("dotMacPostArchiveReq: Bad cert type\n");
763				return paramErr;
764		}
765	}
766
767	/*
768	 * Now cook up arguments for an XMLRPC.
769	 */
770	CFMutableDictionaryRef argDict;
771	CFMutableArrayRef argOrder;
772	CFStringRef method;
773	argDict = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks,
774		&kCFTypeDictionaryValueCallBacks);
775	argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
776
777	switch(archiveType) {
778		case DMAT_List:
779			method = kMethodArchiveList;
780			/* no parameters */
781			break;
782		case DMAT_Store:
783			method = kMethodArchiveSave;
784			/* parameters: name, pfx, certType, date, serialNumber */
785			assert(archiveNameStr != NULL);
786			assert(timeStringStr != NULL);
787			assert(pfxInStr != NULL);
788			assert(serialNumberStr != NULL);
789			assert(certTypeStr != NULL);
790			CFDictionaryAddValue(argDict, kP1, archiveNameStr);
791			CFArrayAppendValue(argOrder, kP1);
792			CFDictionaryAddValue(argDict, kP2, pfxInStr);
793			CFArrayAppendValue(argOrder, kP2);
794			CFDictionaryAddValue(argDict, kP3, certTypeStr);
795			CFArrayAppendValue(argOrder, kP3);
796			CFDictionaryAddValue(argDict, kP4, timeStringStr);
797			CFArrayAppendValue(argOrder, kP4);
798			CFDictionaryAddValue(argDict, kP5, serialNumberStr);
799			CFArrayAppendValue(argOrder, kP5);
800			break;
801		case DMAT_Fetch:
802			method = kMethodArchiveFetch;
803			/* parameters: name */
804			assert(archiveNameStr != NULL);
805			CFDictionaryAddValue(argDict, kP1, archiveNameStr);
806			CFArrayAppendValue(argOrder, kP1);
807			break;
808		case DMAT_Remove:
809			method = kMethodArchiveRemove;
810			/* parameters: name */
811			assert(archiveNameStr != NULL);
812			CFDictionaryAddValue(argDict, kP1, archiveNameStr);
813			CFArrayAppendValue(argOrder, kP1);
814			break;
815		default:
816			return paramErr;
817	}
818
819	OSStatus ortn;
820	CFDictionaryRef resultDict = NULL;
821	uint32_t httpStat;
822
823	#if REQUEST_DICTIONARY_DEBUG
824	dumpDictionary("Arguments dictionary", argDict);
825	#endif
826	ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder, userStr, pwdStr,
827		&resultDict, &httpStat);
828
829	if(ortn) {
830		dotMacErrorLog("dotMacPostArchiveReq: XMLRPC returned %ld\n", ortn);
831		/* And we're dead - means RPC did not complete */
832		goto errOut;
833	}
834	if(resultDict == NULL) {
835		dotMacErrorLog("dotMacPostArchiveReq: XMLRPC failed to return a dictionary\n");
836		ortn = ioErr;
837		goto errOut;
838	}
839	#if RESULTS_DICTIONARY_DEBUG
840	dumpDictionary("Results dictionary", resultDict);
841	#endif
842
843	CFStringRef resultCode;
844	resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode);
845	if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) {
846		dotMacErrorLog("dotMacPostArchiveReq: resultCode is not a string\n");
847		ortn = ioErr;
848		goto errOut;
849	}
850
851	/* no partial success on this one - it worked or it didn't */
852	ortn = dotMacParseResult(resultCode);
853	if(ortn) {
854		goto errOut;
855	}
856
857	/* For some ops, we have some data to give to caller */
858	switch(archiveType) {
859		case DMAT_List:
860		{
861			if(version == CSSM_DOT_MAC_TP_ARCHIVE_REQ_VERSION_v1) {
862				assert(archives_v1 != NULL);
863				ortn = dotMacReturnArchiveList_v1(resultDict, numArchives, archives_v1, alloc);
864			}
865			else {
866				assert(archives_v2 != NULL);
867				ortn = dotMacReturnArchiveList_v2(resultDict, numArchives, archives_v2, alloc);
868			}
869			break;
870		}
871		case DMAT_Store:
872			/* no returned data */
873			break;
874
875		case DMAT_Fetch:
876		{
877			/* resultBody is a PKCS12 PFX */
878			CFStringRef pfxStr;
879			pfxStr = (CFStringRef)CFDictionaryGetValue(resultDict,
880					kResponseResultBody);
881			if((pfxStr == NULL) ||(CFGetTypeID(pfxStr) != CFStringGetTypeID())) {
882				dotMacErrorLog("archive(fetch): resultBody is not a string\n");
883				ortn = ioErr;
884				goto errOut;
885			}
886			assert(pfxOut != NULL);
887			cfstrToCdata(pfxStr, *pfxOut, alloc);
888			break;
889		}
890		case DMAT_Remove:
891			/* no returned data */
892			break;
893
894		default:
895			return paramErr;
896	}
897
898errOut:
899	CFRELEASE(url);
900	CFRELEASE(userStr);
901	CFRELEASE(pwdStr);
902	CFRELEASE(pfxInStr);
903	CFRELEASE(archiveNameStr);
904	CFRELEASE(timeStringStr);
905	CFRELEASE(argDict);
906	CFRELEASE(argOrder);
907	CFRELEASE(resultDict);
908
909	return ortn;
910
911}
912
913/*
914 * Post "Is request pending?" request.
915 * Aside from gross network failures and so forth this returns one of two values:
916 *
917 * CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING -- a request is pending
918 * CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING -- *no* request pending
919 */
920OSStatus dotMacPostReqPendingPing(
921	DotMacCertTypeTag	certType,
922	const CSSM_DATA		&userName,
923	const CSSM_DATA		&password,
924	const CSSM_DATA		&hostName)
925{
926	/*
927	 * First gather arguments into CF form.
928	 */
929	CFURLRef url = createUrl(DOT_MAC_SIGN_SCHEMA, hostName, DOT_MAC_SIGN_PATH);
930	CFStringRef userStr = cDataToCfstr(userName);
931	CFStringRef pwdStr  = cDataToCfstr(password);
932
933	CFStringRef method;
934
935	switch(certType) {
936		case CSSM_DOT_MAC_TYPE_ICHAT:
937			method = kMethodStatusIChatEncrypt;
938			break;
939		case CSSM_DOT_MAC_TYPE_SHARED_SERVICES:
940			method = kMethodStatusSharedServices;
941			break;
942		case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT:
943			method = kMethodStatusEmailEncryption;
944			break;
945		case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING:
946			method = kMethodStatusEmailSigning;
947			break;
948		default:
949			return paramErr;
950	}
951	/*
952	 * Cook up empty arguments dict for an XMLRPC.
953	 */
954	CFMutableDictionaryRef argDict =
955		CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks,
956		&kCFTypeDictionaryValueCallBacks);
957	CFMutableArrayRef argOrder = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
958
959	OSStatus ortn;
960	CFDictionaryRef resultDict = NULL;
961	uint32_t httpStat;
962
963	ortn = performAuthenticatedXMLRPC(url, method, argDict, argOrder,
964		userStr, pwdStr, &resultDict, &httpStat);
965
966	if(ortn) {
967		dotMacErrorLog("dotMacPostReqPendingPing: XMLRPC returned %ld\n", ortn);
968		/* And we're dead - means RPC did not complete */
969		goto errOut;
970	}
971	if(resultDict == NULL) {
972		dotMacErrorLog("dotMacPostReqPendingPing: XMLRPC failed to return a dictionary\n");
973		ortn = ioErr;
974		goto errOut;
975	}
976	#if RESULTS_DICTIONARY_DEBUG
977	dumpDictionary("Results dictionary", resultDict);
978	#endif
979
980	CFStringRef resultCode;
981	resultCode = (CFStringRef)CFDictionaryGetValue(resultDict, kResponseResultCode);
982	if((resultCode == NULL) || (CFGetTypeID(resultCode) != CFStringGetTypeID())) {
983		dotMacErrorLog("dotMacPostCertReq: resultCode is not a string\n");
984		ortn = ioErr;
985		goto errOut;
986	}
987	ortn = dotMacParseResult(resultCode);
988	/* should not return success */
989	dotMacDebug("dotMacPostReqPendingPing: ortn %lu", ortn);
990
991errOut:
992	CFRELEASE(url);
993	CFRELEASE(userStr);
994	CFRELEASE(pwdStr);
995	CFRELEASE(argDict);
996	CFRELEASE(argOrder);
997	CFRELEASE(resultDict);
998	return ortn;
999}
1000