--- /dev/null
+/*
+ * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved.
+ *
+ * The contents of this file constitute Original Code as defined in and are
+ * subject to the Apple Public Source License Version 1.2 (the 'License').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ *
+ * This Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
+ * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
+ * specific language governing rights and limitations under the License.
+ */
+
+
+/*
+ certGroupUtils.cpp
+*/
+
+#include <Security/cssmtype.h>
+#include <Security/cssmapi.h>
+#include <Security/x509defs.h>
+#include <Security/oidscert.h>
+#include <Security/oidsalg.h>
+#include <Security/cssmapple.h>
+#include <Security/SecAsn1Coder.h>
+#include <Security/keyTemplates.h>
+
+#include "certGroupUtils.h"
+#include "tpdebugging.h"
+#include "tpTime.h"
+
+#include <string.h> /* for memcmp */
+
+
+/*
+ * Copy one CSSM_DATA to another, mallocing destination.
+ */
+void tpCopyCssmData(
+ Allocator &alloc,
+ const CSSM_DATA *src,
+ CSSM_DATA_PTR dst)
+{
+ dst->Data = (uint8 *)alloc.malloc(src->Length);
+ dst->Length = src->Length;
+ memmove(dst->Data, src->Data, src->Length);
+}
+
+/*
+ * Malloc a CSSM_DATA, copy another one to it.
+ */
+CSSM_DATA_PTR tpMallocCopyCssmData(
+ Allocator &alloc,
+ const CSSM_DATA *src)
+{
+ CSSM_DATA_PTR dst = (CSSM_DATA_PTR)alloc.malloc(sizeof(CSSM_DATA));
+ tpCopyCssmData(alloc, src, dst);
+ return dst;
+}
+
+/*
+ * Free the data referenced by a CSSM data, and optionally, the struct itself.
+ */
+void tpFreeCssmData(
+ Allocator &alloc,
+ CSSM_DATA_PTR data,
+ CSSM_BOOL freeStruct)
+{
+ if(data == NULL) {
+ return;
+ }
+ if(data->Length != 0) {
+ tpFree(alloc, data->Data);
+ }
+ if(freeStruct) {
+ tpFree(alloc, data);
+ }
+ else {
+ data->Length = 0;
+ data->Data = NULL;
+ }
+}
+
+/*
+ * Compare two CSSM_DATAs, return CSSM_TRUE if identical.
+ */
+CSSM_BOOL tpCompareCssmData(
+ const CSSM_DATA *data1,
+ const CSSM_DATA *data2)
+{
+ if((data1 == NULL) || (data1->Data == NULL) ||
+ (data2 == NULL) || (data2->Data == NULL) ||
+ (data1->Length != data2->Length)) {
+ return CSSM_FALSE;
+ }
+ if(data1->Length != data2->Length) {
+ return CSSM_FALSE;
+ }
+ if(memcmp(data1->Data, data2->Data, data1->Length) == 0) {
+ return CSSM_TRUE;
+ }
+ else {
+ return CSSM_FALSE;
+ }
+}
+
+/*
+ * Free memory via specified plugin's app-level allocator
+ */
+void tpFreePluginMemory(
+ CSSM_HANDLE hand,
+ void *p)
+{
+ CSSM_API_MEMORY_FUNCS memFuncs;
+ CSSM_RETURN crtn = CSSM_GetAPIMemoryFunctions(hand, &memFuncs);
+ if(crtn) {
+ tpErrorLog("CSSM_GetAPIMemoryFunctions failure\n");
+ /* oh well, leak and continue */
+ return;
+ }
+ memFuncs.free_func(p, memFuncs.AllocRef);
+}
+
+/*
+ * Obtain the public key blob from a cert.
+ */
+CSSM_DATA_PTR tp_CertGetPublicKey(
+ TPCertInfo *cert,
+ CSSM_DATA_PTR *valueToFree) // used in tp_CertFreePublicKey
+{
+ CSSM_RETURN crtn;
+ CSSM_DATA_PTR val;
+ CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *keyInfo;
+
+ *valueToFree = NULL;
+ crtn = cert->fetchField(&CSSMOID_X509V1SubjectPublicKeyCStruct, &val);
+ if(crtn) {
+ tpErrorLog("Error on CSSM_CL_CertGetFirstFieldValue(PublicKeyCStruct)\n");
+ return NULL;
+ }
+ *valueToFree = val;
+ keyInfo = (CSSM_X509_SUBJECT_PUBLIC_KEY_INFO *)val->Data;
+ return &keyInfo->subjectPublicKey;
+}
+
+void tp_CertFreePublicKey(
+ CSSM_CL_HANDLE clHand,
+ CSSM_DATA_PTR value)
+{
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SubjectPublicKeyCStruct, value);
+}
+
+/*
+ * Obtain signature algorithm info from a cert.
+ */
+CSSM_X509_ALGORITHM_IDENTIFIER_PTR tp_CertGetAlgId(
+ TPCertInfo *cert,
+ CSSM_DATA_PTR *valueToFree) // used in tp_CertFreeAlgId
+{
+ CSSM_RETURN crtn;
+ CSSM_DATA_PTR val;
+
+ *valueToFree = NULL;
+ crtn = cert->fetchField(&CSSMOID_X509V1SignatureAlgorithm, &val);
+ if(crtn) {
+ tpErrorLog("Error on fetchField(CSSMOID_X509V1SignatureAlgorithm)\n");
+ return NULL;
+ }
+ *valueToFree = val;
+ return (CSSM_X509_ALGORITHM_IDENTIFIER_PTR)val->Data;
+}
+
+void tp_CertFreeAlgId(
+ CSSM_CL_HANDLE clHand,
+ CSSM_DATA_PTR value)
+{
+ CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1SignatureAlgorithm, value);
+}
+
+/*
+ * Determine if two certs - passed in encoded form - are equivalent.
+ */
+CSSM_BOOL tp_CompareCerts(
+ const CSSM_DATA *cert1,
+ const CSSM_DATA *cert2)
+{
+ return tpCompareCssmData(cert1, cert2);
+}
+
+/*
+ * Convert a C string to lower case in place. NULL terminator not needed.
+ */
+void tpToLower(
+ char *str,
+ unsigned strLen)
+{
+ for(unsigned i=0; i<strLen; i++) {
+ *str = tolower(*str);
+ str++;
+ }
+}
+
+/*
+ * Normalize an RFC822 addr-spec. This consists of converting
+ * all characters following the '@' character to lower case.
+ * A true normalizeAll results in lower-casing all characters
+ * (e.g. for iChat).
+ */
+void tpNormalizeAddrSpec(
+ char *addr,
+ unsigned addrLen,
+ bool normalizeAll)
+{
+ if (addr == NULL) {
+ tpPolicyError("tpNormalizeAddrSpec: bad addr");
+ return;
+ }
+ if(!normalizeAll) {
+ while((addrLen != 0) && (*addr != '@')) {
+ addr++;
+ addrLen--;
+ }
+ if(addrLen == 0) {
+ tpPolicyError("tpNormalizeAddrSpec: bad addr-spec");
+ return;
+ }
+ }
+ tpToLower(addr, addrLen);
+}
+
+/***
+ *** dnsName compare support.
+ *** Please do not make any changes to this code without talking to
+ *** dmitch about updating (if necessary) and running (always)
+ *** regression tests which specifically test this logic.
+ ***/
+
+/*
+ * Max length of a distinguished name component (label) we handle.
+ * Various RFCs spec this out at 63 bytes; we're just allocating space
+ * for these on the stack, so why not cut some slack.
+ */
+#define MAX_DNS_COMP_LEN 128
+
+/*
+ * Obtain the next component from a DNS Name.
+ * Caller mallocs outBuf, size >= MAX_DNS_COMP_LEN.
+ * Returns true if a component was found.
+ */
+static bool tpNextDnsComp(
+ const char *inBuf,
+ uint32 &inBufLen, // IN/OUT
+ char *outBuf, // component RETURNED here
+ uint32 &outBufLen) // RETURNED length of component
+{
+ outBufLen = 0;
+ if(inBufLen == 0) {
+ return false;
+ }
+
+ /* skip over leading '.' */
+ if(*inBuf == '.') {
+ inBuf++;
+ if(--inBufLen == 0) {
+ return false;
+ }
+ }
+
+ /* copy chars until out of data or next '.' found */
+ do {
+ if(*inBuf == '.') {
+ break;
+ }
+ *outBuf++ = *inBuf++;
+ inBufLen--;
+ outBufLen++;
+ if(outBufLen >= MAX_DNS_COMP_LEN) {
+ /* abort */
+ break;
+ }
+ } while(inBufLen != 0);
+ if(outBufLen) {
+ return true;
+ }
+ else {
+ return false;
+ }
+}
+
+/*
+ * Find location of specified substring in given bigstring. Returns
+ * pointer to start of substring in bigstring, else returns NULL.
+ */
+static const char *tpSubStr(
+ const char *bigstr,
+ uint32 bigstrLen,
+ const char *substr,
+ uint32 substrLen)
+{
+ /* stop searching substrLen chars before end of bigstr */
+ const char *endBigStr = bigstr + bigstrLen - substrLen;
+ for( ; bigstr <= endBigStr; ) {
+ if(*bigstr == *substr) {
+ /* first char match - remainder? */
+ if(substrLen == 1) {
+ /* don't count on memcmp(a,b,0) */
+ return bigstr;
+ }
+ if(!memcmp(bigstr+1, substr+1, substrLen - 1)) {
+ return bigstr;
+ }
+ }
+ bigstr++;
+ }
+ return NULL;
+}
+
+/*
+ * Compare two DNS components, with full wildcard check. We assume
+ * that no '.' chars exist (per the processing performed in
+ * tpNextDnsComp()). Returns CSSM_TRUE on match, else CSSM_FALSE.
+ */
+static CSSM_BOOL tpCompareComps(
+ const char *hostComp, // no wildcards
+ uint32 hostCompLen,
+ const char *certComp, // wildcards OK here
+ uint32 certCompLen)
+{
+ const char *endCertComp = certComp + certCompLen;
+ const char *endHostComp = hostComp + hostCompLen;
+ do {
+ /* wild card in cert name? */
+ const char *wildCard = tpSubStr(certComp, certCompLen,
+ "*", 1);
+ if(wildCard == NULL) {
+ /* no, require perfect literal match right now */
+ if((hostCompLen == certCompLen) &&
+ !memcmp(hostComp, certComp, certCompLen)) {
+ return CSSM_TRUE;
+ }
+ else {
+ return CSSM_FALSE;
+ }
+ }
+
+ if(wildCard != certComp) {
+ /*
+ * Require literal match of hostComp with certComp
+ * up until (but not including) the wildcard
+ */
+ ptrdiff_t subStrLen = wildCard - certComp;
+ if(subStrLen > hostCompLen) {
+ /* out of host name chars */
+ return CSSM_FALSE;
+ }
+ if(memcmp(certComp, hostComp, subStrLen)) {
+ return CSSM_FALSE;
+ }
+ /* OK, skip over substring */
+ hostComp += subStrLen;
+ hostCompLen -= subStrLen;
+ /* start parsing at the wildcard itself */
+ certComp = wildCard;
+ certCompLen -= subStrLen;
+ continue;
+ }
+
+ /*
+ * Currently looking at a wildcard.
+ *
+ * Find substring in hostComp which matches from the char after
+ * the wildcard up to whichever of these comes next:
+ *
+ * -- end of certComp
+ * -- another wildcard
+ */
+ wildCard++;
+ if(wildCard == endCertComp) {
+ /*
+ * -- Wild card at end of cert's DNS
+ * -- nothing else to match - rest of hostComp is the wildcard
+ * match
+ * -- done, success
+ */
+ return CSSM_TRUE;
+ }
+
+ const char *afterSubStr; // in certComp
+ afterSubStr = tpSubStr(wildCard, (uint32)(endCertComp - wildCard),
+ "*", 1);
+ if(afterSubStr == NULL) {
+ /* no more wildcards - use end of certComp */
+ afterSubStr = endCertComp;
+ }
+ uint32 subStrLen = (uint32)(afterSubStr - wildCard);
+ const char *foundSub = tpSubStr(hostComp, hostCompLen,
+ wildCard, subStrLen);
+ if(foundSub == NULL) {
+ /* No match of explicit chars */
+ return CSSM_FALSE;
+ }
+
+ /* found it - skip past this substring */
+ hostComp = foundSub + subStrLen;
+ hostCompLen = (uint32)(endHostComp - hostComp);
+ certComp = afterSubStr;
+ certCompLen = (uint32)(endCertComp - afterSubStr);
+
+ } while((hostCompLen != 0) || (certCompLen != 0));
+ if((hostCompLen == 0) && (certCompLen == 0)) {
+ return CSSM_TRUE;
+ }
+ else {
+ /* end of one but not the other */
+ return CSSM_FALSE;
+ }
+}
+
+/*
+ * Compare hostname, is presented to the TP in
+ * CSSM_APPLE_TP_SSL_OPTIONS.ServerName, to a server name obtained
+ * from the server's cert (i.e., from subjectAltName or commonName).
+ * Limited wildcard checking is performed here.
+ *
+ * The incoming hostname is assumed to have been processed by tpToLower();
+ * we'll perform that processing on certName here.
+ *
+ * Trailing '.' characters in both host names will be ignored per Radar 3996792.
+ *
+ * Returns CSSM_TRUE on match, else CSSM_FALSE.
+ */
+CSSM_BOOL tpCompareHostNames(
+ const char *hostName, // spec'd by app, tpToLower'd
+ uint32 hostNameLen,
+ char *certName, // from cert, we tpToLower
+ uint32 certNameLen)
+{
+ tpToLower(certName, certNameLen);
+
+ /* tolerate optional NULL terminators for both */
+ if(hostNameLen && (hostName[hostNameLen - 1] == '\0')) {
+ hostNameLen--;
+ }
+ if(certNameLen && (certName[certNameLen - 1] == '\0')) {
+ certNameLen--;
+ }
+
+ if((hostNameLen == 0) || (certNameLen == 0)) {
+ /* trivial case with at least one empty name */
+ if(hostNameLen == certNameLen) {
+ return CSSM_TRUE;
+ }
+ else {
+ return CSSM_FALSE;
+ }
+ }
+
+ /* trim off trailing dots */
+ if(hostName[hostNameLen - 1] == '.') {
+ hostNameLen--;
+ }
+ if(certName[certNameLen - 1] == '.') {
+ certNameLen--;
+ }
+
+ /* Case 1: exact match */
+ if((certNameLen == hostNameLen) &&
+ !memcmp(certName, hostName, certNameLen)) {
+ return CSSM_TRUE;
+ }
+
+ /*
+ * Case 2: Compare one component at a time, handling wildcards in
+ * cert's server name. The characters implicitly matched by a
+ * wildcard span only one component of a dnsName.
+ */
+ do {
+ /* get next component from each dnsName */
+ char hostComp[MAX_DNS_COMP_LEN];
+ char certComp[MAX_DNS_COMP_LEN];
+ uint32 hostCompLen;
+ uint32 certCompLen;
+
+ bool foundHost = tpNextDnsComp(hostName, hostNameLen,
+ hostComp, hostCompLen);
+ bool foundCert = tpNextDnsComp(certName, certNameLen,
+ certComp, certCompLen);
+ if(foundHost != foundCert) {
+ /* unequal number of components */
+ tpPolicyError("tpCompareHostNames: wildcard mismatch (1)");
+ return CSSM_FALSE;
+ }
+ if(!foundHost) {
+ /* normal successful termination */
+ return CSSM_TRUE;
+ }
+
+ /* compare individual components */
+ if(!tpCompareComps(hostComp, hostCompLen,
+ certComp, certCompLen)) {
+ tpPolicyError("tpCompareHostNames: wildcard mismatch (2)");
+ return CSSM_FALSE;
+ }
+
+ /* skip over this component
+ * (note: since tpNextDnsComp will first skip over a leading '.',
+ * we must make sure to skip over it here as well.)
+ */
+ if(*hostName == '.') hostName++;
+ hostName += hostCompLen;
+ if(*certName == '.') certName++;
+ certName += certCompLen;
+ } while(1);
+ /* NOT REACHED */
+ //assert(0):
+ return CSSM_FALSE;
+}
+
+/*
+ * Compare email address, is presented to the TP in
+ * CSSM_APPLE_TP_SMIME_OPTIONS.SenderEmail, to a string obtained
+ * from the sender's cert (i.e., from subjectAltName or Subject DN).
+ *
+ * Returns CSSM_TRUE on match, else CSSM_FALSE.
+ *
+ * Incoming appEmail string has already been tpNormalizeAddrSpec'd.
+ * We do that for certEmail string here.
+ */
+CSSM_BOOL tpCompareEmailAddr(
+ const char *appEmail, // spec'd by app, normalized
+ uint32 appEmailLen,
+ char *certEmail, // from cert, we normalize
+ uint32 certEmailLen,
+ bool normalizeAll) // true : lower-case all certEmail characters
+
+{
+ tpNormalizeAddrSpec(certEmail, certEmailLen, normalizeAll);
+
+ /* tolerate optional NULL terminators for both */
+ if(appEmailLen > 0 && appEmail[appEmailLen - 1] == '\0') {
+ appEmailLen--;
+ }
+ if(certEmailLen > 0 && certEmail[certEmailLen - 1] == '\0') {
+ certEmailLen--;
+ }
+ if((certEmailLen == appEmailLen) &&
+ !memcmp(certEmail, appEmail, certEmailLen)) {
+ return CSSM_TRUE;
+ }
+ else {
+ /* mismatch */
+ tpPolicyError("tpCompareEmailAddr: app/cert email addrs mismatch");
+ return CSSM_FALSE;
+ }
+}
+
+/*
+ * Check whether the provided hostName has a domainName suffix.
+ * This function does not process wildcards, and allows hostName to match
+ * any subdomain level of the provided domainName.
+ *
+ * To match, the last domainNameLen chars of hostName must equal domainName,
+ * and the character immediately preceding domainName in hostName (if any)
+ * must be a dot. This means that domainName 'bar.com' will match hostName
+ * values 'host.bar.com' or 'host.sub.bar.com', but not 'host.foobar.com'.
+ *
+ * The incoming hostname is assumed to have been processed by tpToLower();
+ * we'll perform that processing on domainName here.
+ *
+ * Trailing '.' characters in both host names will be ignored per Radar 3996792.
+ *
+ * Returns CSSM_TRUE on match, else CSSM_FALSE.
+ */
+CSSM_BOOL tpCompareDomainSuffix(
+ const char *hostName, // spec'd by app, tpToLower'd
+ uint32 hostNameLen,
+ char *domainName, // we tpToLower
+ uint32 domainNameLen)
+{
+ tpToLower(domainName, domainNameLen);
+
+ /* tolerate optional NULL terminators for both */
+ if(hostNameLen && (hostName[hostNameLen - 1] == '\0')) {
+ hostNameLen--;
+ }
+ if(domainNameLen && (domainName[domainNameLen - 1] == '\0')) {
+ domainNameLen--;
+ }
+
+ if((hostNameLen == 0) || (domainNameLen == 0)) {
+ /* trivial case with at least one empty name */
+ if(hostNameLen == domainNameLen) {
+ return CSSM_TRUE;
+ }
+ else {
+ return CSSM_FALSE;
+ }
+ }
+
+ /* trim off trailing dots */
+ if(hostName[hostNameLen - 1] == '.') {
+ hostNameLen--;
+ }
+ if(domainName[domainNameLen - 1] == '.') {
+ domainNameLen--;
+ }
+
+ /* trim off leading dot in suffix, if present */
+ if((domainNameLen > 0) && (domainName[0] == '.')) {
+ domainName++;
+ domainNameLen--;
+ }
+
+ if(hostNameLen < domainNameLen) {
+ return CSSM_FALSE;
+ }
+
+ if(memcmp(hostName+(hostNameLen-domainNameLen),domainName,domainNameLen)) {
+ return CSSM_FALSE;
+ }
+
+ /* require a dot prior to domain suffix, unless host == domain */
+ if(hostNameLen > domainNameLen) {
+ if(hostName[hostNameLen-(domainNameLen+1)] != '.') {
+ return CSSM_FALSE;
+ }
+ }
+
+ return CSSM_TRUE;
+}
+
+/*
+ * Following a CSSMOID_ECDSA_WithSpecified algorithm is an encoded
+ * ECDSA_SigAlgParams containing the digest algorithm OID. Decode and return
+ * a unified ECDSA/digest alg (e.g. CSSM_ALGID_SHA512WithECDSA).
+ * Returns nonzero on error.
+ */
+int decodeECDSA_SigAlgParams(
+ const CSSM_DATA *params,
+ CSSM_ALGORITHMS *cssmAlg) /* RETURNED */
+{
+ SecAsn1CoderRef coder = NULL;
+ if(SecAsn1CoderCreate(&coder)) {
+ tpErrorLog("***Error in SecAsn1CoderCreate()\n");
+ return -1;
+ }
+ CSSM_X509_ALGORITHM_IDENTIFIER algParams;
+ memset(&algParams, 0, sizeof(algParams));
+ int ourRtn = 0;
+ bool algFound = false;
+ if(SecAsn1DecodeData(coder, params, kSecAsn1AlgorithmIDTemplate,
+ &algParams)) {
+ tpErrorLog("***Error decoding CSSM_X509_ALGORITHM_IDENTIFIER\n");
+ ourRtn = -1;
+ goto errOut;
+ }
+ CSSM_ALGORITHMS digestAlg;
+ algFound = cssmOidToAlg(&algParams.algorithm, &digestAlg);
+ if(!algFound) {
+ tpErrorLog("***Unknown algorithm in CSSM_X509_ALGORITHM_IDENTIFIER\n");
+ ourRtn = -1;
+ goto errOut;
+ }
+ switch(digestAlg) {
+ case CSSM_ALGID_SHA1:
+ *cssmAlg = CSSM_ALGID_SHA1WithECDSA;
+ break;
+ case CSSM_ALGID_SHA224:
+ *cssmAlg = CSSM_ALGID_SHA224WithECDSA;
+ break;
+ case CSSM_ALGID_SHA256:
+ *cssmAlg = CSSM_ALGID_SHA256WithECDSA;
+ break;
+ case CSSM_ALGID_SHA384:
+ *cssmAlg = CSSM_ALGID_SHA384WithECDSA;
+ break;
+ case CSSM_ALGID_SHA512:
+ *cssmAlg = CSSM_ALGID_SHA512WithECDSA;
+ break;
+ default:
+ tpErrorLog("***Unknown algorithm in ECDSA_SigAlgParams\n");
+ ourRtn = -1;
+ }
+errOut:
+ SecAsn1CoderRelease(coder);
+ return ourRtn;
+}
+