]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_apple_x509_tp/lib/certGroupUtils.cpp
Security-57336.1.9.tar.gz
[apple/security.git] / OSX / libsecurity_apple_x509_tp / lib / certGroupUtils.cpp
diff --git a/OSX/libsecurity_apple_x509_tp/lib/certGroupUtils.cpp b/OSX/libsecurity_apple_x509_tp/lib/certGroupUtils.cpp
new file mode 100644 (file)
index 0000000..97700eb
--- /dev/null
@@ -0,0 +1,691 @@
+/*
+ * 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;
+}
+