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/*
26 * DotMacTpUtils.cpp
27 */
28
29#include "AppleDotMacTPSession.h"
30#include "dotMacTpDebug.h"
31#include "dotMacTpUtils.h"
32#include "dotMacTpMutils.h"
33#include "dotMacTp.h"
34#include "dotMacTpAsn1Templates.h"
35#include <security_asn1/SecNssCoder.h>
36#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
37#include <security_cdsa_utils/cuPem.h>
38#include <CoreFoundation/CoreFoundation.h>
39#include <CoreServices/CoreServices.h>
40#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
41
42#define CFRELEASE(cf)			if(cf != NULL) { CFRelease(cf); }
43
44/*
45 * Given an array of name/value pairs, cook up a CSSM_X509_NAME in specified
46 * SecNssCoder's address space.
47 */
48void dotMacTpbuildX509Name(
49	SecNssCoder						&coder,
50	uint32							numTypeValuePairs,  // size of typeValuePairs[]
51	CSSM_X509_TYPE_VALUE_PAIR_PTR	typeValuePairs,
52	CSSM_X509_NAME					&x509Name)
53{
54	memset(&x509Name, 0, sizeof(x509Name));
55
56	/*
57	 * One RDN per type/value pair per common usage out in the world
58	 * This actual CSSM_X509_TYPE_VALUE_PAIR is NOT re-mallocd; it's copied
59	 * directly into the outgoing CSSM_X509_NAME.
60	 */
61	x509Name.RelativeDistinguishedName =
62		coder.mallocn<CSSM_X509_RDN>(numTypeValuePairs);
63	for(unsigned nameDex=0; nameDex<numTypeValuePairs; nameDex++) {
64		CSSM_X509_RDN_PTR rdn = &x509Name.RelativeDistinguishedName[nameDex];
65		rdn->numberOfPairs = 1;
66		rdn->AttributeTypeAndValue = &typeValuePairs[nameDex];
67	}
68	x509Name.numberOfRDNs = numTypeValuePairs;
69}
70
71/* Convert a reference key to a raw key. */
72void dotMacRefKeyToRaw(
73	CSSM_CSP_HANDLE	cspHand,
74	const CSSM_KEY	*refKey,
75	CSSM_KEY_PTR	rawKey)			// RETURNED
76{
77	CSSM_CC_HANDLE		ccHand;
78	CSSM_RETURN			crtn;
79	CSSM_ACCESS_CREDENTIALS	creds;
80
81	memset(rawKey, 0, sizeof(CSSM_KEY));
82	memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
83	crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
84			CSSM_ALGID_NONE,
85			CSSM_ALGMODE_NONE,
86			&creds,				// passPhrase
87			NULL,				// wrapping key
88			NULL,				// init vector
89			CSSM_PADDING_NONE,	// Padding
90			0,					// Params
91			&ccHand);
92	if(crtn) {
93		dotMacErrorLog("dotMacRefKeyToRaw: context err");
94		CssmError::throwMe(crtn);
95	}
96
97	crtn = CSSM_WrapKey(ccHand,
98		&creds,
99		refKey,
100		NULL,			// DescriptiveData
101		rawKey);
102	if(crtn != CSSM_OK) {
103		dotMacErrorLog("dotMacRefKeyToRaw: wrapKey err");
104		CssmError::throwMe(crtn);
105	}
106	CSSM_DeleteContext(ccHand);
107}
108
109void dotMacTokenizeHostName(
110    const CSSM_DATA				&inName,    // UTF8, no NULL
111    CSSM_DATA					&outName,   // RETURNED
112    CSSM_DATA					&outDomain) // RETURNED
113{
114    int idx = 0;
115    int stopIdx = inName.Length;
116    uint8 *p = inName.Data;
117    outName = inName;
118    outDomain.Length = idx;
119    if (!p) return;
120    while (idx < stopIdx) {
121        if (*p++ == '.') {
122            outName.Length = idx;
123            outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1));
124            outDomain.Data = p;
125            break;
126        }
127        idx++;
128    }
129    if (outDomain.Length) {
130        /* continue scanning for a port specifier */
131        idx = 0;
132        stopIdx = outDomain.Length;
133        while (idx < stopIdx) {
134            if (*p++ == ':') {
135                outDomain.Length = idx;
136                break;
137            }
138            idx++;
139        }
140    }
141}
142
143void dotMacTokenizeUserName(
144    const CSSM_DATA				&inName,    // UTF8, no NULL
145    CSSM_DATA					&outName,   // RETURNED
146    CSSM_DATA					&outDomain) // RETURNED
147{
148    int idx = 0;
149    int stopIdx = inName.Length;
150    uint8 *p = inName.Data;
151    outName = inName;
152    outDomain.Length = idx;
153    if (!p) return;
154    while (idx < stopIdx) {
155        if (*p++ == '@') {
156            outName.Length = idx;
157            outDomain.Length = (CSSM_SIZE)(stopIdx - (idx + 1));
158            outDomain.Data = p;
159            break;
160        }
161        idx++;
162    }
163}
164
165/*
166 * Encode/decode ReferenceIdentitifiers for queued requests.
167 * We PEM encode/decode here to keep things orthogonal, since returned
168 * certs and URLs are also in PEM or at least UTF8 format.
169 */
170OSStatus dotMacEncodeRefId(
171	const CSSM_DATA				&userName,	// UTF8, no NULL
172	const CSSM_DATA				&domainName, // UTF8, no NULL
173	DotMacCertTypeTag			certTypeTag,
174	SecNssCoder					&coder,		// results mallocd in this address space
175	CSSM_DATA					&refId)		// RETURNED, PEM encoded
176{
177	DotMacTpPendingRequest req;
178
179	/* set up a DotMacTpPendingRequest */
180	req.userName = userName;
181    req.domainName = domainName;
182	uint8 certType = certTypeTag;
183	req.certTypeTag.Data = &certType;
184	req.certTypeTag.Length = 1;
185
186	/* DER encode */
187	CSSM_DATA tempData = {0, NULL};
188	PRErrorCode prtn = coder.encodeItem(&req, DotMacTpPendingRequestTemplate, tempData);
189	if(prtn) {
190		dotMacErrorLog("dotMacEncodeRefId: encodeItem error");
191		return internalComponentErr;
192	}
193
194	/* PEM encode */
195	unsigned char *pem;
196	unsigned pemLen;
197	if(pemEncode(tempData.Data, tempData.Length, &pem, &pemLen, "REFERENCE ID")) {
198		dotMacErrorLog("dotMacEncodeRefId: pemEncode error");
199		return internalComponentErr;
200	}
201	refId.Data = NULL;
202	refId.Length = 0;
203	coder.allocCopyItem(pem, pemLen, refId);
204	free(pem);
205
206	return noErr;
207}
208
209OSStatus dotMacDecodeRefId(
210	SecNssCoder					&coder,			// results mallocd in this address space
211	const CSSM_DATA				&refId,			// PEM encoded
212	CSSM_DATA					&userName,		// RETURNED, UTF8, no NULL
213	CSSM_DATA					&domainName,	// RETURNED, UTF8, no NULL
214	DotMacCertTypeTag			*certTypeTag)	// RETURNED
215{
216	/* PEM decode */
217	unsigned char *unPem;
218	unsigned unPemLen;
219	if(pemDecode(refId.Data, refId.Length, &unPem, &unPemLen)) {
220		dotMacErrorLog("dotMacDecodeRefId: pemDecode error");
221		return internalComponentErr;
222	}
223
224	/* DER decode */
225	CSSM_DATA tempData;
226	tempData.Data = unPem;
227	tempData.Length = unPemLen;
228
229	DotMacTpPendingRequest req;
230	memset(&req, 0, sizeof(req));
231
232	PRErrorCode prtn = coder.decodeItem(tempData, DotMacTpPendingRequestTemplate, &req);
233	free(unPem);
234	if(prtn) {
235		dotMacErrorLog("dotMacDecodeRefId: decodeItem error");
236		return paramErr;
237	}
238
239	/* decoded params back to caller */
240	userName = req.userName;
241    domainName = req.domainName;
242	if(req.certTypeTag.Length != 1) {
243		dotMacErrorLog("dotMacDecodeRefId: reqType length (%lu) error", req.certTypeTag.Length);
244		return paramErr;
245	}
246	*certTypeTag = req.certTypeTag.Data[0];
247	return noErr;
248}
249
250/* SPI to specify timeout on CFReadStream */
251#define _kCFStreamPropertyReadTimeout   CFSTR("_kCFStreamPropertyReadTimeout")
252
253/* the read timeout we set, in seconds */
254#define READ_STREAM_TIMEOUT		15
255
256/* amount of data per CFReadStreamRead() */
257#define READ_FRAGMENT_SIZE		512
258
259/* fetch cert via HTTP */
260CSSM_RETURN dotMacTpCertFetch(
261	const CSSM_DATA		&userName,  // UTF8, no NULL
262	const CSSM_DATA		&domainName, // UTF8, no NULL
263	DotMacCertTypeTag	certType,
264	Allocator			&alloc,		// results mallocd here
265	CSSM_DATA			&result)	// RETURNED
266{
267	unsigned rawUrlLen;
268    unsigned domainLen = (domainName.Length && domainName.Data) ? domainName.Length : strlen(DOT_MAC_DOMAIN);
269    uint8 *domain = (domainName.Length && domainName.Data) ? domainName.Data : (uint8 *) DOT_MAC_DOMAIN;
270	const char *typeArg;
271	CSSM_RETURN crtn = CSSM_OK;
272
273	switch(certType) {
274		case CSSM_DOT_MAC_TYPE_ICHAT:
275		case CSSM_DOT_MAC_TYPE_UNSPECIFIED:
276			typeArg = DOT_MAC_CERT_TYPE_ICHAT;
277			break;
278		case CSSM_DOT_MAC_TYPE_SHARED_SERVICES:
279			typeArg = DOT_MAC_CERT_TYPE_SHARED_SERVICES;
280			break;
281		case CSSM_DOT_MAC_TYPE_EMAIL_SIGNING:
282			typeArg = DOT_MAC_CERT_TYPE_EMAIL_SIGNING;
283			break;
284		case CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT:
285			typeArg = DOT_MAC_CERT_TYPE_EMAIL_ENCRYPT;
286			break;
287		default:
288			dotMacErrorLog("dotMacTpCertFetch: bad signType");
289			return paramErr;
290	}
291
292	/* URL :=  http://certinfo.mac.com/locate?accountName&type=certTypeTag */
293	rawUrlLen = strlen(DOT_MAC_LOOKUP_SCHEMA) +		/* http:// */
294		strlen(DOT_MAC_LOOKUP_HOST_NAME) +			/* certinfo */
295        domainLen +	1 +								/* .mac.com */
296		strlen(DOT_MAC_LOOKUP_PATH) +				/* /locate? */
297		userName.Length +							/* joe */
298		strlen(DOT_MAC_LOOKUP_TYPE) +				/* &type= */
299		strlen(typeArg) +							/* dmSharedServices */
300		1;											/* NULL */
301	unsigned char rawUrl[rawUrlLen];
302	unsigned char *cp = rawUrl;
303
304	unsigned len = strlen(DOT_MAC_LOOKUP_SCHEMA);
305	memmove(cp, DOT_MAC_LOOKUP_SCHEMA, len);
306	cp += len;
307
308	len = strlen(DOT_MAC_LOOKUP_HOST_NAME);
309	memmove(cp, DOT_MAC_LOOKUP_HOST_NAME, len);
310	cp += len;
311
312    memmove(cp, ".", 1);
313    cp += 1;
314    memmove(cp, domain, domainLen);
315    cp += domainLen;
316
317	len = strlen(DOT_MAC_LOOKUP_PATH);
318	memmove(cp, DOT_MAC_LOOKUP_PATH, len);
319	cp += len;
320
321	memmove(cp, userName.Data, userName.Length);
322	cp += userName.Length;
323
324	len = strlen(DOT_MAC_LOOKUP_TYPE);
325	memmove(cp, DOT_MAC_LOOKUP_TYPE, len);
326	cp += len;
327
328	len = strlen(typeArg);
329	memmove(cp, typeArg, len);
330	cp += len;
331
332	*cp = '\0';		// for debugging only, actually
333	dotMacDebug("dotMacTpCertFetch: URL %s", rawUrl);
334
335	CFURLRef cfUrl = CFURLCreateWithBytes(NULL,
336		rawUrl, rawUrlLen - 1,		// no NULL
337		kCFStringEncodingUTF8,
338		NULL);						// absolute path
339	if(cfUrl == NULL) {
340		dotMacErrorLog("dotMacTpCertFetch: CFURLCreateWithBytes returned NULL\n");
341		return paramErr;
342	}
343	/* subsequent errors to errOut: */
344
345	CFHTTPMessageRef httpRequestRef = NULL;
346	CFReadStreamRef httpStreamRef = NULL;
347	CFNumberRef cfnTo = NULL;
348	CFDictionaryRef proxyDict = NULL;
349	SInt32 ito = READ_STREAM_TIMEOUT;
350	CFMutableDataRef fetchedData = CFDataCreateMutable(NULL, 0);
351	UInt8 readFrag[READ_FRAGMENT_SIZE];
352	CFIndex bytesRead;
353	CFIndex resultLen;
354
355	httpRequestRef = CFHTTPMessageCreateRequest(NULL,
356		CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);
357	if(!httpRequestRef) {
358		dotMacErrorLog("***Error creating HTTPMessage from '%s'\n", rawUrl);
359		crtn = ioErr;
360		goto errOut;
361	}
362
363	// open the stream
364	httpStreamRef = CFReadStreamCreateForHTTPRequest(NULL, httpRequestRef);
365	if(!httpStreamRef) {
366		dotMacErrorLog("***Error creating stream for '%s'\n", rawUrl);
367		crtn = ioErr;
368		goto errOut;
369	}
370
371	// set a reasonable timeout
372	cfnTo = CFNumberCreate(NULL, kCFNumberSInt32Type, &ito);
373    if(!CFReadStreamSetProperty(httpStreamRef, _kCFStreamPropertyReadTimeout, cfnTo)) {
374		// oh well - keep going
375	}
376
377	// set up possible proxy info
378	proxyDict = SCDynamicStoreCopyProxies(NULL);
379	if(proxyDict) {
380		CFReadStreamSetProperty(httpStreamRef, kCFStreamPropertyHTTPProxy, proxyDict);
381	}
382
383	if(CFReadStreamOpen(httpStreamRef) == false) {
384		dotMacErrorLog("***Error opening stream for '%s'\n", rawUrl);
385		crtn = ioErr;
386		goto errOut;
387	}
388
389	// read data from the stream
390	bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag));
391	while (bytesRead > 0) {
392		CFDataAppendBytes(fetchedData, readFrag, bytesRead);
393		bytesRead = CFReadStreamRead(httpStreamRef, readFrag, sizeof(readFrag));
394	}
395
396	if (bytesRead < 0) {
397		dotMacErrorLog("***Error reading URL '%s'\n", rawUrl);
398		crtn = ioErr;
399		goto errOut;
400	}
401
402	resultLen = CFDataGetLength(fetchedData);
403	if(resultLen == 0) {
404		dotMacErrorLog("***No data available from URL '%s'\n", rawUrl);
405		/* but don't abort on this one - it means "no cert found" */
406		goto errOut;
407	}
408
409	/*
410	 * Only pass back good data.
411	 * FIXME this is a back to workaround nonconforming .Mac server behavior.
412	 * It currently sends HTML data that is *not* a cert when it wants to
413	 * indicate "no certs found". It should just return empty data, which
414	 * we'd detect above. For now we have to determine manually if the data
415	 * contains some PEM-formated stuff.
416	 */
417	{
418		/* Scan for PEM armour */
419		bool isPEM = false;
420		const char *srchStr = "-----BEGIN CERTIFICATE-----";
421		unsigned srchStrLen = strlen(srchStr);
422		const char *p = (const char *)CFDataGetBytePtr(fetchedData);
423		if(resultLen > (int)srchStrLen) {
424			/* no sense checking if result is smaller than that search string */
425			unsigned srchLen = resultLen - srchStrLen;
426			for(unsigned dex=0; dex< srchLen; dex++) {
427				if(!strncmp(p, srchStr, srchStrLen)) {
428					isPEM = true;
429					break;
430				}
431				p++;
432			}
433		}
434		if(isPEM) {
435			result.Data = (uint8 *)alloc.malloc(resultLen);
436			result.Length = resultLen;
437			memmove(result.Data, CFDataGetBytePtr(fetchedData), resultLen);
438		}
439		else {
440			result.Data = NULL;
441			result.Length = 0;
442		}
443	}
444errOut:
445	CFRELEASE(cfUrl);
446	CFRELEASE(httpRequestRef);
447	CFRELEASE(httpStreamRef);
448	CFRELEASE(cfnTo);
449	CFRELEASE(proxyDict);
450
451	return crtn;
452}
453
454