1/*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19/*
20	certGroupUtils.cpp
21
22	Created 10/9/2000 by Doug Mitchell.
23*/
24
25#include <Security/cssmtype.h>
26#include <Security/cssmapi.h>
27#include <Security/x509defs.h>
28#include <Security/oidscert.h>
29#include <Security/oidsalg.h>
30#include <Security/cssmapple.h>
31#include <Security/SecAsn1Coder.h>
32#include <Security/keyTemplates.h>
33
34#include "certGroupUtils.h"
35#include "tpdebugging.h"
36#include "tpTime.h"
37
38#include <string.h>				/* for memcmp */
39
40
41/*
42 * Copy one CSSM_DATA to another, mallocing destination.
43 */
44void tpCopyCssmData(
45	Allocator		&alloc,
46	const CSSM_DATA	*src,
47	CSSM_DATA_PTR	dst)
48{
49	dst->Data = (uint8 *)alloc.malloc(src->Length);
50	dst->Length = src->Length;
51	memmove(dst->Data, src->Data, src->Length);
52}
53
54/*
55 * Malloc a CSSM_DATA, copy another one to it.
56 */
57CSSM_DATA_PTR tpMallocCopyCssmData(
58	Allocator		&alloc,
59	const CSSM_DATA	*src)
60{
61	CSSM_DATA_PTR dst = (CSSM_DATA_PTR)alloc.malloc(sizeof(CSSM_DATA));
62	tpCopyCssmData(alloc, src, dst);
63	return dst;
64}
65
66/*
67 * Free the data referenced by a CSSM data, and optionally, the struct itself.
68 */
69void tpFreeCssmData(
70	Allocator		&alloc,
71	CSSM_DATA_PTR 	data,
72	CSSM_BOOL 		freeStruct)
73{
74	if(data == NULL) {
75		return;
76	}
77	if(data->Length != 0) {
78		tpFree(alloc, data->Data);
79	}
80	if(freeStruct) {
81		tpFree(alloc, data);
82	}
83	else {
84		data->Length = 0;
85		data->Data = NULL;
86	}
87}
88
89/*
90 * Compare two CSSM_DATAs, return CSSM_TRUE if identical.
91 */
92CSSM_BOOL tpCompareCssmData(
93	const CSSM_DATA *data1,
94	const CSSM_DATA *data2)
95{
96	if((data1 == NULL) || (data1->Data == NULL) ||
97	   (data2 == NULL) || (data2->Data == NULL) ||
98	   (data1->Length != data2->Length)) {
99		return CSSM_FALSE;
100	}
101	if(data1->Length != data2->Length) {
102		return CSSM_FALSE;
103	}
104	if(memcmp(data1->Data, data2->Data, data1->Length) == 0) {
105		return CSSM_TRUE;
106	}
107	else {
108		return CSSM_FALSE;
109	}
110}
111
112/*
113 * Free memory via specified plugin's app-level allocator
114 */
115void tpFreePluginMemory(
116	CSSM_HANDLE	hand,
117	void 		*p)
118{
119	CSSM_API_MEMORY_FUNCS memFuncs;
120	CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
121	if(crtn) {
122		tpErrorLog("CSSM_GetAPIMemoryFunctions failure\n");
123		/* oh well, leak and continue */
124		return;
125	}
126	memFuncs.free_func(p, memFuncs.AllocRef);
127}
128
129/*
130 * Obtain the public key blob from a cert.
131 */
132CSSM_DATA_PTR tp_CertGetPublicKey(
133    TPCertInfo *cert,
134	CSSM_DATA_PTR *valueToFree)			// used in tp_CertFreePublicKey
135{
136	CSSM_RETURN crtn;
137	CSSM_DATA_PTR val;
138	CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *keyInfo;
139
140	*valueToFree = NULL;
141	crtn = cert->fetchField(&CSSMOID_X509V1SubjectPublicKeyCStruct, &val);
142	if(crtn) {
143		tpErrorLog("Error on CSSM_CL_CertGetFirstFieldValue(PublicKeyCStruct)\n");
144		return NULL;
145	}
146	*valueToFree = val;
147	keyInfo = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *)val->Data;
148	return &keyInfo->subjectPublicKey;
149}
150
151void tp_CertFreePublicKey(
152	CSSM_CL_HANDLE	clHand,
153	CSSM_DATA_PTR	value)
154{
155  	CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SubjectPublicKeyCStruct, value);
156}
157
158/*
159 * Obtain signature algorithm info from a cert.
160 */
161CSSM_X509_ALGORITHM_IDENTIFIER_PTR tp_CertGetAlgId(
162    TPCertInfo	 	*cert,
163	CSSM_DATA_PTR 	*valueToFree)			// used in tp_CertFreeAlgId
164{
165	CSSM_RETURN crtn;
166	CSSM_DATA_PTR val;
167
168	*valueToFree = NULL;
169	crtn = cert->fetchField(&CSSMOID_X509V1SignatureAlgorithm, &val);
170	if(crtn) {
171		tpErrorLog("Error on fetchField(CSSMOID_X509V1SignatureAlgorithm)\n");
172		return NULL;
173	}
174	*valueToFree = val;
175	return (CSSM_X509_ALGORITHM_IDENTIFIER_PTR)val->Data;
176}
177
178void tp_CertFreeAlgId(
179	CSSM_CL_HANDLE	clHand,
180	CSSM_DATA_PTR	value)
181{
182  	CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SignatureAlgorithm, value);
183}
184
185/*
186 * Determine if two certs - passed in encoded form - are equivalent.
187 */
188CSSM_BOOL tp_CompareCerts(
189	const CSSM_DATA			*cert1,
190	const CSSM_DATA			*cert2)
191{
192	return tpCompareCssmData(cert1, cert2);
193}
194
195/*
196 * Convert a C string to lower case in place. NULL terminator not needed.
197 */
198void tpToLower(
199	char *str,
200	unsigned strLen)
201{
202	for(unsigned i=0; i<strLen; i++) {
203		*str = tolower(*str);
204		str++;
205	}
206}
207
208/*
209 * Normalize an RFC822 addr-spec. This consists of converting
210 * all characters following the '@' character to lower case.
211 * A true normalizeAll results in lower-casing all characters
212 * (e.g. for iChat).
213 */
214void tpNormalizeAddrSpec(
215	char		*addr,
216	unsigned	addrLen,
217	bool		normalizeAll)
218{
219	if (addr == NULL) {
220		tpPolicyError("tpNormalizeAddrSpec: bad addr");
221		return;
222	}
223	if(!normalizeAll) {
224		while((addrLen != 0) && (*addr != '@')) {
225			addr++;
226			addrLen--;
227		}
228		if(addrLen == 0) {
229			tpPolicyError("tpNormalizeAddrSpec: bad addr-spec");
230			return;
231		}
232	}
233	tpToLower(addr, addrLen);
234}
235
236/***
237 *** dnsName compare support.
238 *** Please do not make any changes to this code without talking to
239 *** dmitch about updating (if necessary) and running (always)
240 *** regression tests which specifically test this logic.
241 ***/
242
243/*
244 * Max length of a distinguished name component (label) we handle.
245 * Various RFCs spec this out at 63 bytes; we're just allocating space
246 * for these on the stack, so why not cut some slack.
247 */
248#define MAX_DNS_COMP_LEN	128
249
250/*
251 * Obtain the next component from a DNS Name.
252 * Caller mallocs outBuf, size >= MAX_DNS_COMP_LEN.
253 * Returns true if a component was found.
254 */
255static bool tpNextDnsComp(
256	const char 	*inBuf,
257	uint32		&inBufLen,		// IN/OUT
258	char		*outBuf,		// component RETURNED here
259	uint32		&outBufLen)		// RETURNED length of component
260{
261	outBufLen = 0;
262	if(inBufLen == 0) {
263		return false;
264	}
265
266	/* skip over leading '.' */
267	if(*inBuf == '.') {
268		inBuf++;
269		if(--inBufLen == 0) {
270			return false;
271		}
272	}
273
274	/* copy chars until out of data or next '.' found */
275	do {
276		if(*inBuf == '.') {
277			break;
278		}
279		*outBuf++ = *inBuf++;
280		inBufLen--;
281		outBufLen++;
282		if(outBufLen >= MAX_DNS_COMP_LEN) {
283			/* abort */
284			break;
285		}
286	} while(inBufLen != 0);
287	if(outBufLen) {
288		return true;
289	}
290	else {
291		return false;
292	}
293}
294
295/*
296 * Find location of specified substring in given bigstring. Returns
297 * pointer to start of substring in bigstring, else returns NULL.
298 */
299static const char *tpSubStr(
300	const char 	*bigstr,
301	uint32 		bigstrLen,
302	const char 	*substr,
303	uint32		substrLen)
304{
305	/* stop searching substrLen chars before end of bigstr */
306	const char *endBigStr = bigstr + bigstrLen - substrLen;
307	for( ; bigstr <= endBigStr; ) {
308		if(*bigstr == *substr) {
309			/* first char match - remainder? */
310			if(substrLen == 1) {
311				/* don't count on memcmp(a,b,0) */
312				return bigstr;
313			}
314			if(!memcmp(bigstr+1, substr+1, substrLen - 1)) {
315				return bigstr;
316			}
317		}
318		bigstr++;
319	}
320	return NULL;
321}
322
323/*
324 * Compare two DNS components, with full wildcard check. We assume
325 * that no '.' chars exist (per the processing performed in
326 * tpNextDnsComp()). Returns CSSM_TRUE on match, else CSSM_FALSE.
327 */
328static CSSM_BOOL tpCompareComps(
329	const char 	*hostComp, 			// no wildcards
330	uint32 		hostCompLen,
331	const char 	*certComp, 			// wildcards OK here
332	uint32		certCompLen)
333{
334	const char *endCertComp = certComp + certCompLen;
335	const char *endHostComp = hostComp + hostCompLen;
336	do {
337		/* wild card in cert name? */
338		const char *wildCard = tpSubStr(certComp, certCompLen,
339			"*", 1);
340		if(wildCard == NULL) {
341			/* no, require perfect literal match right now */
342			if((hostCompLen == certCompLen) &&
343					!memcmp(hostComp, certComp, certCompLen)) {
344				return CSSM_TRUE;
345			}
346			else {
347				return CSSM_FALSE;
348			}
349		}
350
351		if(wildCard != certComp) {
352			/*
353			 * Require literal match of hostComp with certComp
354			 * up until (but not including) the wildcard
355			 */
356			ptrdiff_t subStrLen = wildCard - certComp;
357			if(subStrLen > hostCompLen) {
358				/* out of host name chars */
359				return CSSM_FALSE;
360			}
361			if(memcmp(certComp, hostComp, subStrLen)) {
362				return CSSM_FALSE;
363			}
364			/* OK, skip over substring */
365			hostComp    += subStrLen;
366			hostCompLen -= subStrLen;
367			/* start parsing at the wildcard itself */
368			certComp     = wildCard;
369			certCompLen -= subStrLen;
370			continue;
371		}
372
373		/*
374		 * Currently looking at a wildcard.
375		 *
376		 * Find substring in hostComp which matches from the char after
377		 * the wildcard up to whichever of these comes next:
378		 *
379		 *  -- end of certComp
380		 *  -- another wildcard
381		 */
382		wildCard++;
383		if(wildCard == endCertComp) {
384			/*
385			 * -- Wild card at end of cert's DNS
386			 * -- nothing else to match - rest of hostComp is the wildcard
387			 *    match
388			 * -- done, success
389			 */
390			return CSSM_TRUE;
391		}
392
393		const char *afterSubStr;		// in certComp
394		afterSubStr = tpSubStr(wildCard, (uint32)(endCertComp - wildCard),
395			"*", 1);
396		if(afterSubStr == NULL) {
397			/* no more wildcards - use end of certComp */
398			afterSubStr = endCertComp;
399		}
400		uint32 subStrLen = (uint32)(afterSubStr - wildCard);
401		const char *foundSub = tpSubStr(hostComp, hostCompLen,
402			wildCard, subStrLen);
403		if(foundSub == NULL) {
404			/* No match of explicit chars */
405			return CSSM_FALSE;
406		}
407
408		/* found it - skip past this substring */
409		hostComp    = foundSub + subStrLen;
410		hostCompLen = (uint32)(endHostComp - hostComp);
411		certComp    = afterSubStr;
412		certCompLen = (uint32)(endCertComp - afterSubStr);
413
414	} while((hostCompLen != 0) || (certCompLen != 0));
415	if((hostCompLen == 0) && (certCompLen == 0)) {
416		return CSSM_TRUE;
417	}
418	else {
419		/* end of one but not the other */
420		return CSSM_FALSE;
421	}
422}
423
424/*
425 * Compare hostname, is presented to the TP in
426 * CSSM_APPLE_TP_SSL_OPTIONS.ServerName, to a server name obtained
427 * from the server's cert (i.e., from subjectAltName or commonName).
428 * Limited wildcard checking is performed here.
429 *
430 * The incoming hostname is assumed to have been processed by tpToLower();
431 * we'll perform that processing on certName here.
432 *
433 * Trailing '.' characters in both host names will be ignored per Radar 3996792.
434 *
435 * Returns CSSM_TRUE on match, else CSSM_FALSE.
436 */
437CSSM_BOOL tpCompareHostNames(
438	const char	 	*hostName,		// spec'd by app, tpToLower'd
439	uint32			hostNameLen,
440	char			*certName,		// from cert, we tpToLower
441	uint32			certNameLen)
442{
443	tpToLower(certName, certNameLen);
444
445	/* tolerate optional NULL terminators for both */
446	if(hostNameLen && (hostName[hostNameLen - 1] == '\0')) {
447		hostNameLen--;
448	}
449	if(certNameLen && (certName[certNameLen - 1] == '\0')) {
450		certNameLen--;
451	}
452
453	if((hostNameLen == 0) || (certNameLen == 0)) {
454		/* trivial case with at least one empty name */
455		if(hostNameLen == certNameLen) {
456			return CSSM_TRUE;
457		}
458		else {
459			return CSSM_FALSE;
460		}
461	}
462
463	/* trim off trailing dots */
464	if(hostName[hostNameLen - 1] == '.') {
465		hostNameLen--;
466	}
467	if(certName[certNameLen - 1] == '.') {
468		certNameLen--;
469	}
470
471	/* Case 1: exact match */
472	if((certNameLen == hostNameLen) &&
473	    !memcmp(certName, hostName, certNameLen)) {
474		return CSSM_TRUE;
475	}
476
477	/*
478	 * Case 2: Compare one component at a time, handling wildcards in
479	 * cert's server name. The characters implicitly matched by a
480	 * wildcard span only one component of a dnsName.
481	 */
482	do {
483		/* get next component from each dnsName */
484		char hostComp[MAX_DNS_COMP_LEN];
485		char certComp[MAX_DNS_COMP_LEN];
486		uint32 hostCompLen;
487		uint32 certCompLen;
488
489		bool foundHost = tpNextDnsComp(hostName, hostNameLen,
490				hostComp, hostCompLen);
491		bool foundCert = tpNextDnsComp(certName, certNameLen,
492				certComp, certCompLen);
493		if(foundHost != foundCert) {
494			/* unequal number of components */
495			tpPolicyError("tpCompareHostNames: wildcard mismatch (1)");
496			return CSSM_FALSE;
497		}
498		if(!foundHost) {
499			/* normal successful termination */
500			return CSSM_TRUE;
501		}
502
503		/* compare individual components */
504		if(!tpCompareComps(hostComp, hostCompLen,
505				certComp, certCompLen)) {
506			tpPolicyError("tpCompareHostNames: wildcard mismatch (2)");
507			return CSSM_FALSE;
508		}
509
510		/* skip over this component
511		 * (note: since tpNextDnsComp will first skip over a leading '.',
512		 * we must make sure to skip over it here as well.)
513		 */
514		if(*hostName == '.') hostName++;
515		hostName += hostCompLen;
516		if(*certName == '.') certName++;
517		certName += certCompLen;
518	} while(1);
519	/* NOT REACHED */
520	//assert(0):
521	return CSSM_FALSE;
522}
523
524/*
525 * Compare email address, is presented to the TP in
526 * CSSM_APPLE_TP_SMIME_OPTIONS.SenderEmail, to a string obtained
527 * from the sender's cert (i.e., from subjectAltName or Subject DN).
528 *
529 * Returns CSSM_TRUE on match, else CSSM_FALSE.
530 *
531 * Incoming appEmail string has already been tpNormalizeAddrSpec'd.
532 * We do that for certEmail string here.
533 */
534CSSM_BOOL tpCompareEmailAddr(
535	const char	 	*appEmail,		// spec'd by app, normalized
536	uint32			appEmailLen,
537	char			*certEmail,		// from cert, we normalize
538	uint32			certEmailLen,
539	bool			normalizeAll)	// true : lower-case all certEmail characters
540
541{
542	tpNormalizeAddrSpec(certEmail, certEmailLen, normalizeAll);
543
544	/* tolerate optional NULL terminators for both */
545	if(appEmailLen > 0 && appEmail[appEmailLen - 1] == '\0') {
546		appEmailLen--;
547	}
548	if(certEmailLen > 0 && certEmail[certEmailLen - 1] == '\0') {
549		certEmailLen--;
550	}
551	if((certEmailLen == appEmailLen) &&
552	    !memcmp(certEmail, appEmail, certEmailLen)) {
553		return CSSM_TRUE;
554	}
555	else {
556		/* mismatch */
557		tpPolicyError("tpCompareEmailAddr: app/cert email addrs mismatch");
558		return CSSM_FALSE;
559	}
560}
561
562/*
563 * Following a CSSMOID_ECDSA_WithSpecified algorithm is an encoded
564 * ECDSA_SigAlgParams containing the digest agorithm OID. Decode and return
565 * a unified ECDSA/digest alg (e.g. CSSM_ALGID_SHA512WithECDSA).
566 * Returns nonzero on error.
567 */
568int decodeECDSA_SigAlgParams(
569	const CSSM_DATA *params,
570	CSSM_ALGORITHMS *cssmAlg)		/* RETURNED */
571{
572	SecAsn1CoderRef coder = NULL;
573	if(SecAsn1CoderCreate(&coder)) {
574		tpErrorLog("***Error in SecAsn1CoderCreate()\n");
575		return -1;
576	}
577	CSSM_X509_ALGORITHM_IDENTIFIER algParams;
578	memset(&algParams, 0, sizeof(algParams));
579	int ourRtn = 0;
580	bool algFound = false;
581	if(SecAsn1DecodeData(coder, params, kSecAsn1AlgorithmIDTemplate,
582			&algParams)) {
583		tpErrorLog("***Error decoding CSSM_X509_ALGORITHM_IDENTIFIER\n");
584		ourRtn = -1;
585		goto errOut;
586	}
587	CSSM_ALGORITHMS digestAlg;
588	algFound = cssmOidToAlg(&algParams.algorithm, &digestAlg);
589	if(!algFound) {
590		tpErrorLog("***Unknown algorithm in CSSM_X509_ALGORITHM_IDENTIFIER\n");
591		ourRtn = -1;
592		goto errOut;
593	}
594	switch(digestAlg) {
595		case CSSM_ALGID_SHA1:
596			*cssmAlg = CSSM_ALGID_SHA1WithECDSA;
597			break;
598		case CSSM_ALGID_SHA224:
599			*cssmAlg = CSSM_ALGID_SHA224WithECDSA;
600			break;
601		case CSSM_ALGID_SHA256:
602			*cssmAlg = CSSM_ALGID_SHA256WithECDSA;
603			break;
604		case CSSM_ALGID_SHA384:
605			*cssmAlg = CSSM_ALGID_SHA384WithECDSA;
606			break;
607		case CSSM_ALGID_SHA512:
608			*cssmAlg = CSSM_ALGID_SHA512WithECDSA;
609			break;
610		default:
611			tpErrorLog("***Unknown algorithm in ECDSA_SigAlgParams\n");
612			ourRtn = -1;
613	}
614errOut:
615	SecAsn1CoderRelease(coder);
616	return ourRtn;
617}
618
619