+++ /dev/null
-/*
- * Copyright (c) 2000-2001 Apple Computer, 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.
- */
-
-
-/*
- policies.cpp - TP module policy implementation
-
- Created 10/9/2000 by Doug Mitchell.
-*/
-
-#include <Security/cssmtype.h>
-#include <Security/cssmapi.h>
-#include "tpPolicies.h"
-#include <Security/oidsattr.h>
-#include <Security/cssmerr.h>
-#include "tpdebugging.h"
-#include "rootCerts.h"
-#include "certGroupUtils.h"
-#include <Security/x509defs.h>
-#include <Security/oidscert.h>
-#include <Security/certextensions.h>
-#include <Security/cssmapple.h>
-#include <string.h>
-#include <ctype.h>
-#include <assert.h>
-
-
-/*
- * Our private per-extension info. One of these per (understood) extension per
- * cert.
- */
-typedef struct {
- CSSM_BOOL present;
- CSSM_BOOL critical;
- CE_Data *extnData; // mallocd by CL
- CSSM_DATA *valToFree; // the data we pass to freeField()
-} iSignExtenInfo;
-
-/*
- * Struct to keep track of info pertinent to one cert.
- */
-typedef struct {
-
- /* extensions we're interested in */
- iSignExtenInfo authorityId;
- iSignExtenInfo subjectId;
- iSignExtenInfo keyUsage;
- iSignExtenInfo extendKeyUsage;
- iSignExtenInfo basicConstraints;
- iSignExtenInfo netscapeCertType;
- iSignExtenInfo subjectAltName;
-
- /* flag indicating presence of a critical extension we don't understand */
- CSSM_BOOL foundUnknownCritical;
-
-} iSignCertInfo;
-
-
-/*
- * Setup a single iSignExtenInfo. Called once per known extension
- * per cert.
- */
-static CSSM_RETURN tpSetupExtension(
- CssmAllocator &alloc,
- CSSM_DATA *extnData,
- iSignExtenInfo *extnInfo) // which component of certInfo
-{
- if(extnData->Length != sizeof(CSSM_X509_EXTENSION)) {
- tpPolicyError("tpSetupExtension: malformed CSSM_FIELD");
- return CSSMERR_TP_UNKNOWN_FORMAT;
- }
- CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)extnData->Data;
- extnInfo->present = CSSM_TRUE;
- extnInfo->critical = cssmExt->critical;
- extnInfo->extnData = (CE_Data *)cssmExt->value.parsedValue;
- extnInfo->valToFree = extnData;
- return CSSM_OK;
-}
-
-/*
- * Fetch a known extension, set up associated iSignExtenInfo if present.
- */
-static CSSM_RETURN iSignFetchExtension(
- CssmAllocator &alloc,
- TPCertInfo *tpCert,
- const CSSM_OID *fieldOid, // which extension to fetch
- iSignExtenInfo *extnInfo) // where the info goes
-{
- CSSM_DATA_PTR fieldValue; // mallocd by CL
- CSSM_RETURN crtn;
-
- crtn = tpCert->fetchField(fieldOid, &fieldValue);
- switch(crtn) {
- case CSSM_OK:
- break;
- case CSSMERR_CL_NO_FIELD_VALUES:
- /* field not present, OK */
- return CSSM_OK;
- default:
- return crtn;
- }
- return tpSetupExtension(alloc,
- fieldValue,
- extnInfo);
-}
-
-/*
- * Search for al unknown extensions. If we find one which is flagged critical,
- * flag certInfo->foundUnknownCritical. Only returns error on gross errors.
- */
-static CSSM_RETURN iSignSearchUnknownExtensions(
- TPCertInfo *tpCert,
- iSignCertInfo *certInfo)
-{
- CSSM_RETURN crtn;
- CSSM_DATA_PTR fieldValue = NULL;
- CSSM_HANDLE searchHand = CSSM_INVALID_HANDLE;
- uint32 numFields = 0;
-
- crtn = CSSM_CL_CertGetFirstCachedFieldValue(tpCert->clHand(),
- tpCert->cacheHand(),
- &CSSMOID_X509V3CertificateExtensionCStruct,
- &searchHand,
- &numFields,
- &fieldValue);
- switch(crtn) {
- case CSSM_OK:
- /* found one, proceed */
- break;
- case CSSMERR_CL_NO_FIELD_VALUES:
- /* no unknown extensions present, OK */
- return CSSM_OK;
- default:
- return crtn;
- }
-
- if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
- tpPolicyError("iSignSearchUnknownExtensions: malformed CSSM_FIELD");
- return CSSMERR_TP_UNKNOWN_FORMAT;
- }
- CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
- if(cssmExt->critical) {
- /* BRRZAPP! Found an unknown extension marked critical */
- certInfo->foundUnknownCritical = CSSM_TRUE;
- goto fini;
- }
- CSSM_CL_FreeFieldValue(tpCert->clHand(),
- &CSSMOID_X509V3CertificateExtensionCStruct,
- fieldValue);
- fieldValue = NULL;
-
- /* process remaining unknown extensions */
- for(unsigned i=1; i<numFields; i++) {
- crtn = CSSM_CL_CertGetNextCachedFieldValue(tpCert->clHand(),
- searchHand,
- &fieldValue);
- if(crtn) {
- /* should never happen */
- tpPolicyError("searchUnknownExtensions: GetNextCachedFieldValue"
- "error");
- break;
- }
- if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
- tpPolicyError("iSignSearchUnknownExtensions: "
- "malformed CSSM_FIELD");
- crtn = CSSMERR_TP_UNKNOWN_FORMAT;
- break;
- }
- CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
- if(cssmExt->critical) {
- /* BRRZAPP! Found an unknown extension marked critical */
- certInfo->foundUnknownCritical = CSSM_TRUE;
- break;
- }
- CSSM_CL_FreeFieldValue(tpCert->clHand(),
- &CSSMOID_X509V3CertificateExtensionCStruct,
- fieldValue);
- fieldValue = NULL;
- } /* for additional fields */
-
-fini:
- if(fieldValue) {
- CSSM_CL_FreeFieldValue(tpCert->clHand(),
- &CSSMOID_X509V3CertificateExtensionCStruct,
- fieldValue);
- }
- if(searchHand != CSSM_INVALID_HANDLE) {
- CSSM_CL_CertAbortQuery(tpCert->clHand(), searchHand);
- }
- return crtn;
-}
-/*
- * Given a TPCertInfo, fetch the associated iSignCertInfo fields.
- * Returns CSSM_FAIL on error.
- */
-static CSSM_RETURN iSignGetCertInfo(
- CssmAllocator &alloc,
- TPCertInfo *tpCert,
- iSignCertInfo *certInfo)
-{
- CSSM_RETURN crtn;
-
- /* first grind thru the extensions we're interested in */
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_AuthorityKeyIdentifier,
- &certInfo->authorityId);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_SubjectKeyIdentifier,
- &certInfo->subjectId);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_KeyUsage,
- &certInfo->keyUsage);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_ExtendedKeyUsage,
- &certInfo->extendKeyUsage);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_BasicConstraints,
- &certInfo->basicConstraints);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_NetscapeCertType,
- &certInfo->netscapeCertType);
- if(crtn) {
- return crtn;
- }
- crtn = iSignFetchExtension(alloc,
- tpCert,
- &CSSMOID_SubjectAltName,
- &certInfo->subjectAltName);
- if(crtn) {
- return crtn;
- }
-
- /* now look for extensions we don't understand - the only thing we're interested
- * in is the critical flag. */
- return iSignSearchUnknownExtensions(tpCert, certInfo);
-}
-
-/*
- * Free (via CL) the fields allocated in iSignGetCertInfo().
- */
-static void iSignFreeCertInfo(
- CSSM_CL_HANDLE clHand,
- iSignCertInfo *certInfo)
-{
- if(certInfo->authorityId.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_AuthorityKeyIdentifier,
- certInfo->authorityId.valToFree);
- }
- if(certInfo->subjectId.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_SubjectKeyIdentifier,
- certInfo->subjectId.valToFree);
- }
- if(certInfo->keyUsage.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_KeyUsage,
- certInfo->keyUsage.valToFree);
- }
- if(certInfo->extendKeyUsage.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_ExtendedKeyUsage,
- certInfo->extendKeyUsage.valToFree);
- }
- if(certInfo->basicConstraints.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_BasicConstraints,
- certInfo->basicConstraints.valToFree);
- }
- if(certInfo->netscapeCertType.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_NetscapeCertType,
- certInfo->netscapeCertType.valToFree);
- }
- if(certInfo->subjectAltName.present) {
- CSSM_CL_FreeFieldValue(clHand, &CSSMOID_SubjectAltName,
- certInfo->subjectAltName.valToFree);
- }
-}
-
-/*
- * See if cert's Subject.{commonName,EmailAddress} matches caller-specified
- * string. Returns CSSM_TRUE if match, else returns CSSM_FALSE.
- * Also indicates whether *any* of the specified fields were found, regardless
- * of match state.
- */
-typedef enum {
- SN_CommonName, // CSSMOID_CommonName, host name format
- SN_Email // CSSMOID_EmailAddress
-} SubjSubjNameSearchType;
-
-static CSSM_BOOL tpCompareSubjectName(
- TPCertInfo &cert,
- SubjSubjNameSearchType searchType,
- const char *callerStr, // already tpToLower'd
- uint32 callerStrLen,
- bool &fieldFound)
-{
- char *certName = NULL; // from cert's subject name
- uint32 certNameLen = 0;
- CSSM_DATA_PTR subjNameData = NULL;
- CSSM_RETURN crtn;
- CSSM_BOOL ourRtn = CSSM_FALSE;
- const CSSM_OID *oidSrch;
-
- fieldFound = false;
- switch(searchType) {
- case SN_CommonName:
- oidSrch = &CSSMOID_CommonName;
- break;
- case SN_Email:
- oidSrch = &CSSMOID_EmailAddress;
- break;
- default:
- assert(0);
- return CSSM_FALSE;
- }
- crtn = cert.fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData);
- if(crtn) {
- /* should never happen, we shouldn't be here if there is no subject */
- tpPolicyError("tp_verifySslOpts: error retrieving subject name");
- return CSSM_FALSE;
- }
- CSSM_X509_NAME_PTR x509name = (CSSM_X509_NAME_PTR)subjNameData->Data;
- if((x509name == NULL) || (subjNameData->Length != sizeof(CSSM_X509_NAME))) {
- tpPolicyError("tp_verifySslOpts: malformed CSSM_X509_NAME");
- cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
- return CSSM_FALSE;
- }
-
- /* Now grunge thru the X509 name looking for a common name */
- CSSM_X509_TYPE_VALUE_PAIR *ptvp;
- CSSM_X509_RDN_PTR rdnp;
- unsigned rdnDex;
- unsigned pairDex;
-
- for(rdnDex=0; rdnDex<x509name->numberOfRDNs; rdnDex++) {
- rdnp = &x509name->RelativeDistinguishedName[rdnDex];
- for(pairDex=0; pairDex<rdnp->numberOfPairs; pairDex++) {
- ptvp = &rdnp->AttributeTypeAndValue[pairDex];
- if(tpCompareOids(&ptvp->type, oidSrch)) {
- fieldFound = true;
- certName = (char *)ptvp->value.Data;
- certNameLen = ptvp->value.Length;
- switch(searchType) {
- case SN_CommonName:
- ourRtn = tpCompareHostNames(callerStr, callerStrLen,
- certName, certNameLen);
- break;
- case SN_Email:
- ourRtn = tpCompareEmailAddr(callerStr, callerStrLen,
- certName, certNameLen);
- break;
- }
- if(ourRtn) {
- /* success */
- break;
- }
- /* else keep going, maybe there's another common name */
- }
- }
- if(ourRtn) {
- break;
- }
- }
- cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
- return ourRtn;
-}
-
-/*
- * Compare ASCII form of an IP address to a CSSM_DATA containing
- * the IP address's numeric components. Returns true on match.
- */
-static CSSM_BOOL tpCompIpAddrStr(
- const char *str,
- unsigned strLen,
- const CSSM_DATA *numeric)
-{
- const char *cp = str;
- const char *nextDot;
- char buf[100];
-
- if((numeric == NULL) || (numeric->Length == 0) || (str == NULL)) {
- return CSSM_FALSE;
- }
- if(cp[strLen - 1] == '\0') {
- /* ignore NULL terminator */
- strLen--;
- }
- for(unsigned dex=0; dex<numeric->Length; dex++) {
- /* cp points to start of current string digit */
- /* find next dot */
- const char *lastChar = cp + strLen;
- nextDot = cp + 1;
- for( ; nextDot<lastChar; nextDot++) {
- if(*nextDot == '.') {
- break;
- }
- }
- if(nextDot == lastChar) {
- /* legal and required on last digit */
- if(dex != (numeric->Length - 1)) {
- return CSSM_FALSE;
- }
- }
- else if(dex == (numeric->Length - 1)) {
- return CSSM_FALSE;
- }
- unsigned digLen = nextDot - cp;
- if(digLen >= sizeof(buf)) {
- /* preposterous */
- return CSSM_FALSE;
- }
- memmove(buf, cp, digLen);
- buf[digLen] = '\0';
- /* incr digLen to include the next dot */
- digLen++;
- cp += digLen;
- strLen -= digLen;
- int digVal = atoi(buf);
- if(digVal != numeric->Data[dex]) {
- return CSSM_FALSE;
- }
- }
- return CSSM_TRUE;
-}
-
-/*
- * See if cert's subjectAltName contains an element matching caller-specified
- * string, hostname, in the following forms:
- *
- * SAN_HostName : dnsName, iPAddress
- * SAN_Email : RFC822Name
- *
- * Returns CSSM_TRUE if match, else returns CSSM_FALSE.
- *
- * Also indicates whether or not a dnsName (search type HostName) or
- * RFC822Name (search type SAM_Email) was found, regardless of result
- * of comparison.
- *
- * The appStr/appStrLen args are optional - if NULL/0, only the
- * search for dnsName/RFC822Name is done.
- */
-typedef enum {
- SAN_HostName,
- SAN_Email
-} SubjAltNameSearchType;
-
-static CSSM_BOOL tpCompareSubjectAltName(
- const iSignExtenInfo &subjAltNameInfo,
- const char *appStr,
- uint32 appStrLen,
- SubjAltNameSearchType searchType,
- bool &dnsNameFound, // RETURNED, SAN_HostName case
- bool &emailFound) // RETURNED, SAN_Email case
-{
- dnsNameFound = false;
- emailFound = false;
- if(!subjAltNameInfo.present) {
- /* common failure, no subjectAltName found */
- return CSSM_FALSE;
- }
-
- CE_GeneralNames *names = &subjAltNameInfo.extnData->subjectAltName;
- CSSM_BOOL ourRtn = CSSM_FALSE;
- char *certName;
- unsigned certNameLen;
-
- /* Search thru the CE_GeneralNames looking for the appropriate attribute */
- for(unsigned dex=0; dex<names->numNames; dex++) {
- CE_GeneralName *name = &names->generalName[dex];
- switch(searchType) {
- case SAN_HostName:
- switch(name->nameType) {
- case GNT_IPAddress:
- if(appStr == NULL) {
- /* nothing to do here */
- break;
- }
- ourRtn = tpCompIpAddrStr(appStr, appStrLen, &name->name);
- break;
-
- case GNT_DNSName:
- if(name->berEncoded) {
- tpErrorLog("tpCompareSubjectAltName: malformed "
- "CE_GeneralName (1)\n");
- break;
- }
- certName = (char *)name->name.Data;
- if(certName == NULL) {
- tpErrorLog("tpCompareSubjectAltName: malformed "
- "CE_GeneralName (2)\n");
- break;
- }
- certNameLen = name->name.Length;
- dnsNameFound = true;
- if(appStr != NULL) {
- /* skip if caller passed in NULL */
- ourRtn = tpCompareHostNames(appStr, appStrLen,
- certName, certNameLen);
- }
- break;
-
- default:
- /* not interested, proceed to next name */
- break;
- }
- break; /* from case HostName */
-
- case SAN_Email:
- if(name->nameType != GNT_RFC822Name) {
- /* not interested */
- break;
- }
- certName = (char *)name->name.Data;
- if(certName == NULL) {
- tpErrorLog("tpCompareSubjectAltName: malformed "
- "GNT_RFC822Name\n");
- break;
- }
- certNameLen = name->name.Length;
- emailFound = true;
- if(appStr != NULL) {
- ourRtn = tpCompareEmailAddr(appStr, appStrLen, certName,
- certNameLen);
- }
- break;
- }
- if(ourRtn) {
- /* success */
- break;
- }
- }
- return ourRtn;
-}
-
-/* is host name in the form of a.b.c.d, where a,b,c, and d are digits? */
-static CSSM_BOOL tpIsNumeric(
- const char *hostName,
- unsigned hostNameLen)
-{
- if(hostName[hostNameLen - 1] == '\0') {
- /* ignore NULL terminator */
- hostNameLen--;
- }
- for(unsigned i=0; i<hostNameLen; i++) {
- char c = *hostName++;
- if(isdigit(c)) {
- continue;
- }
- if(c != '.') {
- return CSSM_FALSE;
- }
- }
- return CSSM_TRUE;
-}
-
-/*
- * Verify SSL options. Currently this just consists of matching the
- * leaf cert's subject common name against the caller's (optional)
- * server name.
- */
-static CSSM_RETURN tp_verifySslOpts(
- TPCertGroup &certGroup,
- const CSSM_DATA *sslFieldOpts,
- const iSignCertInfo &leafCertInfo)
-{
- /* first validate optional SSL options */
- if((sslFieldOpts == NULL) || (sslFieldOpts->Data == NULL)) {
- /* optional */
- return CSSM_OK;
- }
- CSSM_APPLE_TP_SSL_OPTIONS *sslOpts;
- sslOpts = (CSSM_APPLE_TP_SSL_OPTIONS *)sslFieldOpts->Data;
- switch(sslOpts->Version) {
- case CSSM_APPLE_TP_SSL_OPTS_VERSION:
- if(sslFieldOpts->Length != sizeof(CSSM_APPLE_TP_SSL_OPTIONS)) {
- return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
- }
- break;
- /* handle backwards compatibility here if necessary */
- default:
- return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
- }
-
- unsigned hostNameLen = sslOpts->ServerNameLen;
-
- if(hostNameLen == 0) {
- /* optional */
- return CSSM_OK;
- }
- if(sslOpts->ServerName == NULL) {
- return CSSMERR_TP_INVALID_POINTER;
- }
-
- /* convert caller's hostname string to lower case */
- char *hostName = (char *)certGroup.alloc().malloc(hostNameLen);
- memmove(hostName, sslOpts->ServerName, hostNameLen);
- tpToLower(hostName, hostNameLen);
-
- TPCertInfo *leaf = certGroup.certAtIndex(0);
- assert(leaf != NULL);
-
- CSSM_BOOL match = CSSM_FALSE;
-
- /* First check subjectAltName... */
- bool dnsNameFound = false;
- bool dummy;
- match = tpCompareSubjectAltName(leafCertInfo.subjectAltName,
- hostName, hostNameLen,
- SAN_HostName, dnsNameFound, dummy);
-
- /*
- * Then common name, if
- * -- no match from subjectAltName, AND
- * -- dnsName was NOT found, AND
- * -- hostName is not strictly numeric form (1.2.3.4)
- */
- if(!match && !dnsNameFound && !tpIsNumeric(hostName, hostNameLen)) {
- bool fieldFound;
- match = tpCompareSubjectName(*leaf, SN_CommonName, hostName, hostNameLen,
- fieldFound);
- }
- certGroup.alloc().free(hostName);
- if(match) {
- return CSSM_OK;
- }
- else {
- leaf->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-}
-
-/*
- * Verify SMIME options.
- */
-#define CE_CIPHER_MASK (~(CE_KU_EncipherOnly | CE_KU_DecipherOnly))
-
-static CSSM_RETURN tp_verifySmimeOpts(
- TPCertGroup &certGroup,
- const CSSM_DATA *smimeFieldOpts,
- const iSignCertInfo &leafCertInfo)
-{
- /*
- * First validate optional S/MIME options.
- */
- CSSM_APPLE_TP_SMIME_OPTIONS *smimeOpts = NULL;
- if(smimeFieldOpts != NULL) {
- smimeOpts = (CSSM_APPLE_TP_SMIME_OPTIONS *)smimeFieldOpts->Data;
- }
- if(smimeOpts != NULL) {
- switch(smimeOpts->Version) {
- case CSSM_APPLE_TP_SMIME_OPTS_VERSION:
- if(smimeFieldOpts->Length !=
- sizeof(CSSM_APPLE_TP_SMIME_OPTIONS)) {
- return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
- }
- break;
- /* handle backwards compatibility here if necessary */
- default:
- return CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
- }
- }
-
- TPCertInfo *leaf = certGroup.certAtIndex(0);
- assert(leaf != NULL);
-
- /* Verify optional email address */
- unsigned emailLen = 0;
- if(smimeOpts != NULL) {
- emailLen = smimeOpts->SenderEmailLen;
- }
- bool emailFoundInSAN = false;
- if(emailLen != 0) {
- if(smimeOpts->SenderEmail == NULL) {
- return CSSMERR_TP_INVALID_POINTER;
- }
-
- /* normalize caller's email string */
- char *email = (char *)certGroup.alloc().malloc(emailLen);
- memmove(email, smimeOpts->SenderEmail, emailLen);
- tpNormalizeAddrSpec(email, emailLen);
-
- CSSM_BOOL match = false;
-
- /*
- * First check subjectAltName. The emailFound bool indicates
- * that *some* email address was found, regardless of a match
- * condition.
- */
- bool dummy;
- match = tpCompareSubjectAltName(leafCertInfo.subjectAltName,
- email, emailLen,
- SAN_Email, dummy, emailFoundInSAN);
-
- /*
- * Then subject DN, CSSMOID_EmailAddress, if no match from
- * subjectAltName
- */
- bool emailFoundInDn = false;
- if(!match) {
- match = tpCompareSubjectName(*leaf, SN_Email, email, emailLen,
- emailFoundInDn);
- }
- certGroup.alloc().free(email);
-
- /*
- * Error here only if no match found but there was indeed *some*
- * email address in the cert.
- */
- if(!match && (emailFoundInSAN || emailFoundInDn)) {
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND);
- tpPolicyError("SMIME email addrs in cert but no match");
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- }
-
- /*
- * Going by the letter of the law, here's what RFC 2632 has to say
- * about the legality of an empty Subject Name:
- *
- * ...the subject DN in a user's (i.e. end-entity) certificate MAY
- * be an empty SEQUENCE in which case the subjectAltName extension
- * will include the subject's identifier and MUST be marked as
- * critical.
- *
- * OK, first examine the leaf cert's subject name.
- */
- CSSM_RETURN crtn;
- CSSM_DATA_PTR subjNameData = NULL;
- crtn = leaf->fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData);
- if(crtn) {
- /* This should really never happen */
- tpPolicyError("SMIME policy: error fetching subjectName");
- leaf->addStatusCode(CSSMERR_TP_INVALID_CERTIFICATE);
- return CSSMERR_TP_INVALID_CERTIFICATE;
- }
- /* must do a leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct on exit */
-
- const CSSM_X509_NAME *x509Name = (const CSSM_X509_NAME *)subjNameData->Data;
- if(x509Name->numberOfRDNs == 0) {
- /*
- * Empty subject name. If we haven't already seen a valid
- * email address in the subject alternate name (by looking
- * for a specific address specified by app), try to find
- * one now.
- */
- if(!emailFoundInSAN && // haven't found one, and
- (emailLen == 0)) { // didn't even look yet
- bool dummy;
- tpCompareSubjectAltName(leafCertInfo.subjectAltName,
- NULL, 0, // email, emailLen,
- SAN_Email, dummy,
- emailFoundInSAN); // the variable we're updating
- }
- if(!emailFoundInSAN) {
- tpPolicyError("SMIME policy fail: empty subject name and "
- "no Email Addrs in SubjectAltName");
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS);
- leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-
- /*
- * One more thing: this leaf must indeed have a subjAltName
- * extension and it must be critical. We would not have gotten this
- * far if the subjAltName extension was not actually present....
- */
- assert(leafCertInfo.subjectAltName.present);
- if(!leafCertInfo.subjectAltName.critical) {
- tpPolicyError("SMIME policy fail: empty subject name and "
- "no Email Addrs in SubjectAltName");
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_SUBJ_ALT_NAME_NOT_CRIT);
- leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- }
- leaf->freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData);
-
- /*
- * Enforce the usage of the key associated with the leaf cert.
- * Cert's KeyUsage must be a superset of what the app is trying to do.
- * Note the {en,de}cipherONly flags are handledÊseparately....
- */
- const iSignExtenInfo &kuInfo = leafCertInfo.keyUsage;
- if(kuInfo.present) {
- CE_KeyUsage certKu = *((CE_KeyUsage *)kuInfo.extnData);
- CE_KeyUsage appKu = smimeOpts->IntendedUsage;
- CE_KeyUsage intersection = certKu & appKu;
- if((intersection & CE_CIPHER_MASK) != (appKu & CE_CIPHER_MASK)) {
- tpPolicyError("SMIME KeyUsage err: appKu 0x%x certKu 0x%x",
- appKu, certKu);
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-
- /* Now the en/de cipher only bits - for keyAgreement only */
- if(appKu & CE_KU_KeyAgreement) {
- /*
- * 1. App wants to use this for key agreement; it must
- * say what it wants to do with the derived key.
- * In this context, the app's XXXonly bit means that
- * it wants to use the key for that op - not necessarliy
- * "only".
- */
- if((appKu & (CE_KU_EncipherOnly | CE_KU_DecipherOnly)) == 0) {
- tpPolicyError("SMIME KeyUsage err: KeyAgreement with "
- "no Encipher or Decipher");
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-
- /*
- * 2. If cert restricts to encipher only make sure the
- * app isn't trying to decipher.
- */
- if((certKu & CE_KU_EncipherOnly) &&
- (appKu & CE_KU_DecipherOnly)) {
- tpPolicyError("SMIME KeyUsage err: cert EncipherOnly, "
- "app wants to decipher");
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-
- /*
- * 3. If cert restricts to decipher only make sure the
- * app isn't trying to encipher.
- */
- if((certKu & CE_KU_DecipherOnly) &&
- (appKu & CE_KU_EncipherOnly)) {
- tpPolicyError("SMIME KeyUsage err: cert DecipherOnly, "
- "app wants to encipher");
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- }
- }
-
- /*
- * Ensure that, if an extendedKeyUsage extension is present in the
- * leaf, that either emailProtection or anyExtendedKeyUsage usages is present
- */
- const iSignExtenInfo &ekuInfo = leafCertInfo.extendKeyUsage;
- if(ekuInfo.present) {
- bool foundGoodEku = false;
- CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData;
- assert(eku != NULL);
- for(unsigned i=0; i<eku->numPurposes; i++) {
- if(tpCompareOids(&eku->purposes[i], &CSSMOID_EmailProtection)) {
- foundGoodEku = true;
- break;
- }
- if(tpCompareOids(&eku->purposes[i], &CSSMOID_ExtendedKeyUsageAny)) {
- foundGoodEku = true;
- break;
- }
- }
- if(!foundGoodEku) {
- leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE);
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- }
-
- return CSSM_OK;
-}
-
-/*
- * RFC2459 says basicConstraints must be flagged critical for
- * CA certs, but Verisign doesn't work that way.
- */
-#define BASIC_CONSTRAINTS_MUST_BE_CRITICAL 0
-
-/*
- * TP iSign spec says Extended Key Usage required for leaf certs,
- * but Verisign doesn't work that way.
- */
-#define EXTENDED_KEY_USAGE_REQUIRED_FOR_LEAF 0
-
-/*
- * TP iSign spec says Subject Alternate Name required for leaf certs,
- * but Verisign doesn't work that way.
- */
-#define SUBJECT_ALT_NAME_REQUIRED_FOR_LEAF 0
-
-/*
- * TP iSign spec originally required KeyUsage for all certs, but
- * Verisign doesn't have that in their roots.
- */
-#define KEY_USAGE_REQUIRED_FOR_ROOT 0
-
-/*
- * RFC 2632, "S/MIME Version 3 Certificate Handling", section
- * 4.4.2, says that KeyUsage extensions MUST be flagged critical,
- * but Thawte's intermediate cert (common namd "Thawte Personal
- * Freemail Issuing CA" does not meet this requirement.
- */
-#define SMIME_KEY_USAGE_MUST_BE_CRITICAL 0
-
-/*
- * Public routine to perform TP verification on a constructed
- * cert group.
- * Returns CSSM_TRUE on success.
- * Asumes the chain has passed basic subject/issuer verification. First cert of
- * incoming certGroup is end-entity (leaf).
- *
- * Per-policy details:
- * iSign: Assumes that last cert in incoming certGroup is a root cert.
- * Also assumes a cert group of more than one cert.
- * kTPx509Basic: CertGroup of length one allowed.
- */
-CSSM_RETURN tp_policyVerify(
- TPPolicy policy,
- CssmAllocator &alloc,
- CSSM_CL_HANDLE clHand,
- CSSM_CSP_HANDLE cspHand,
- TPCertGroup *certGroup,
- CSSM_BOOL verifiedToRoot, // last cert is good root
- CSSM_APPLE_TP_ACTION_FLAGS actionFlags,
- const CSSM_DATA *policyFieldData, // optional
- void *policyOpts) // future options
-{
- iSignCertInfo *certInfo = NULL;
- uint32 numCerts;
- iSignCertInfo *thisCertInfo;
- uint16 expUsage;
- uint16 actUsage;
- unsigned certDex;
- CSSM_BOOL cA = CSSM_FALSE; // init for compiler warning
- bool isLeaf; // end entity
- bool isRoot; // root cert
- CE_ExtendedKeyUsage *extendUsage;
- CE_AuthorityKeyID *authorityId;
- CSSM_RETURN outErr = CSSM_OK; // for gross, non-policy errors
- CSSM_BOOL policyFail = CSSM_FALSE;
-
- /* First, kTPDefault is a nop here */
- if(policy == kTPDefault) {
- return CSSM_OK;
- }
-
- if(certGroup == NULL) {
- return CSSMERR_TP_INVALID_CERTGROUP;
- }
- numCerts = certGroup->numCerts();
- if(numCerts == 0) {
- return CSSMERR_TP_INVALID_CERTGROUP;
- }
- if(policy == kTPiSign) {
- if(!verifiedToRoot) {
- /* no way, this requires a root cert */
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- if(numCerts <= 1) {
- /* nope, not for iSign */
- return CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
- }
-
- /* cook up an iSignCertInfo array */
- certInfo = (iSignCertInfo *)tpCalloc(alloc, numCerts, sizeof(iSignCertInfo));
- /* subsequent errors to errOut: */
-
- /* fill it with interesting info from parsed certs */
- for(certDex=0; certDex<numCerts; certDex++) {
- if(iSignGetCertInfo(alloc,
- certGroup->certAtIndex(certDex),
- &certInfo[certDex])) {
- (certGroup->certAtIndex(certDex))->addStatusCode(
- CSSMERR_TP_INVALID_CERTIFICATE);
- /* this one is fatal */
- outErr = CSSMERR_TP_INVALID_CERTIFICATE;
- goto errOut;
- }
- }
-
- /*
- * OK, the heart of TP enforcement.
- * First check for presence of required extensions and
- * critical extensions we don't understand.
- */
- for(certDex=0; certDex<numCerts; certDex++) {
- thisCertInfo = &certInfo[certDex];
- TPCertInfo *thisTpCertInfo = certGroup->certAtIndex(certDex);
-
- if(thisCertInfo->foundUnknownCritical) {
- /* illegal for all policies */
- tpPolicyError("tp_policyVerify: critical flag in unknown "
- "extension");
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN);
- policyFail = CSSM_TRUE;
- }
-
- /*
- * Note it's possible for both of these to be true, for a
- * of length one (kTPx509Basic, kCrlPolicy only!)
- * FIXME: should this code work of the last cert in the chain is
- * NOT a root?
- */
- isLeaf = thisTpCertInfo->isLeaf();
- isRoot = thisTpCertInfo->isSelfSigned();
-
- /*
- * BasicConstraints.cA
- * iSign: required in all but leaf and root,
- * for which it is optional (with default values of false
- * for leaf and true for root).
- * kTPx509Basic,
- * kTP_SSL,
- * kTP_SMIME always optional, default of false for leaf and
- * true for others
- * All: cA must be false for leaf, true for others
- */
- if(!thisCertInfo->basicConstraints.present) {
- /*
- * No basicConstraints present; infer a cA value if appropriate.
- */
- if(isLeaf) {
- /* cool, use default; note that kTPx509Basic with
- * certGroup length of one may take this case */
- cA = CSSM_FALSE;
- }
- else if(isRoot) {
- /* cool, use default */
- cA = CSSM_TRUE;
- }
- else {
- switch(policy) {
- case kTPx509Basic:
- case kTP_SSL:
- case kCrlPolicy:
- case kTP_SMIME:
- /*
- * not present, not leaf, not root....
- * ....RFC2459 says this can not be a CA
- */
- cA = CSSM_FALSE;
- break;
- case kTPiSign:
- /* required for iSign in this position */
- tpPolicyError("tp_policyVerify: no "
- "basicConstraints");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(
- CSSMERR_APPLETP_NO_BASIC_CONSTRAINTS);
- break;
- default:
- /* not reached */
- assert(0);
- break;
- }
- }
- }
- else {
- /* basicConstraints present */
- #if BASIC_CONSTRAINTS_MUST_BE_CRITICAL
- /* disabled for verisign compatibility */
- if(!thisCertInfo->basicConstraints.critical) {
- /* per RFC 2459 */
- tpPolicyError("tp_policyVerify: basicConstraints marked "
- "not critical");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_TP_VERIFY_ACTION_FAILED);
- }
- #endif /* BASIC_CONSTRAINTS_MUST_BE_CRITICAL */
-
- const CE_BasicConstraints *bcp =
- &thisCertInfo->basicConstraints.extnData->basicConstraints;
-
- cA = bcp->cA;
-
- /* Verify pathLenConstraint if present */
- if(!isLeaf && // leaf, certDex=0, don't care
- cA && // p.l.c. only valid for CAs
- bcp->pathLenConstraintPresent) { // present?
- /*
- * pathLenConstraint=0 legal for certDex 1 only
- * pathLenConstraint=1 legal for certDex {1,2}
- * etc.
- */
- if(certDex > (bcp->pathLenConstraint + 1)) {
- tpPolicyError("tp_policyVerify: pathLenConstraint "
- "exceeded");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(
- CSSMERR_APPLETP_PATH_LEN_CONSTRAINT);
- }
- }
- }
-
- if(isLeaf) {
- /*
- * Special cases to allow a chain of length 1, leaf and root
- * both true, and for caller to override the "leaf can't be a CA"
- * requirement when a CA cert is explicitly being evaluated as the
- * leaf.
- */
- if(cA && !isRoot &&
- !(actionFlags & CSSM_TP_ACTION_LEAF_IS_CA)) {
- tpPolicyError("tp_policyVerify: cA true for leaf");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA);
- }
- } else if(!cA) {
- tpPolicyError("tp_policyVerify: cA false for non-leaf");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA);
- }
-
- /*
- * Authority Key Identifier optional
- * iSign : only allowed in !root.
- * If present, must not be critical.
- * kTPx509Basic,
- * kTP_SSL,
- * kTP_SMIME : ignored (though used later for chain verification)
- */
- if((policy == kTPiSign) && thisCertInfo->authorityId.present) {
- if(isRoot) {
- tpPolicyError("tp_policyVerify: authorityId in root");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID);
- }
- if(thisCertInfo->authorityId.critical) {
- /* illegal per RFC 2459 */
- tpPolicyError("tp_policyVerify: authorityId marked "
- "critical");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID);
- }
- }
-
- /*
- * Subject Key Identifier optional
- * iSign : can't be critical.
- * kTPx509Basic,
- * kTP_SSL,
- * kTP_SMIME : ignored (though used later for chain verification)
- */
- if(thisCertInfo->subjectId.present) {
- if((policy == kTPiSign) && thisCertInfo->subjectId.critical) {
- tpPolicyError("tp_policyVerify: subjectId marked critical");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_SUBJECT_ID);
- }
- }
-
- /*
- * Key Usage optional except as noted required
- * iSign : required for non-root/non-leaf
- * Leaf cert : if present, usage = digitalSignature
- * Exception : if leaf, and keyUsage not present,
- * netscape-cert-type must be present, with
- * Object Signing bit set
- * kTPx509Basic,
- * kTP_SSL,
- * kTP_SMIME, : non-leaf : usage = keyCertSign
- * Leaf: don't care
- * kCrlPolicy : Leaf: usage = CRLSign
- * kTP_SMIME : if present, must be critical
- */
- if(thisCertInfo->keyUsage.present) {
- /*
- * Leaf cert: usage = digitalSignature
- * Others: usage = keyCertSign
- * We only require that one bit to be set, we ignore others.
- */
- if(isLeaf) {
- switch(policy) {
- case kTPiSign:
- expUsage = CE_KU_DigitalSignature;
- break;
- case kCrlPolicy:
- /* if present, this bit must be set */
- expUsage = CE_KU_CRLSign;
- break;
- default:
- /* hack to accept whatever's there */
- expUsage = thisCertInfo->keyUsage.extnData->keyUsage;
- break;
- }
- }
- else {
- /* this is true for all policies */
- expUsage = CE_KU_KeyCertSign;
- }
- actUsage = thisCertInfo->keyUsage.extnData->keyUsage;
- if(!(actUsage & expUsage)) {
- tpPolicyError("tp_policyVerify: bad keyUsage (leaf %s; "
- "usage 0x%x)",
- (certDex == 0) ? "TRUE" : "FALSE", actUsage);
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE);
- }
- #if 0
- if((policy == kTP_SMIME) && !thisCertInfo->keyUsage.critical) {
- /*
- * Per Radar 3410245, allow this for intermediate certs.
- */
- if(SMIME_KEY_USAGE_MUST_BE_CRITICAL || isLeaf || isRoot) {
- tpPolicyError("tp_policyVerify: key usage, !critical, SMIME");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_SMIME_KEYUSAGE_NOT_CRITICAL);
- }
- }
- #endif
- }
- else if(policy == kTPiSign) {
- /*
- * iSign requires keyUsage present for non root OR
- * netscape-cert-type/ObjectSigning for leaf
- */
- if(isLeaf && thisCertInfo->netscapeCertType.present) {
- CE_NetscapeCertType ct =
- thisCertInfo->netscapeCertType.extnData->netscapeCertType;
-
- if(!(ct & CE_NCT_ObjSign)) {
- tpPolicyError("tp_policyVerify: netscape-cert-type, "
- "!ObjectSign");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE);
- }
- }
- else if(!isRoot) {
- tpPolicyError("tp_policyVerify: !isRoot, no keyUsage, "
- "!(leaf and netscapeCertType)");
- policyFail = CSSM_TRUE;
- thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE);
- }
- }
- } /* for certDex, checking presence of extensions */
-
- /*
- * Special case checking for leaf (end entity) cert
- *
- * iSign only: Extended key usage, optional for leaf,
- * value CSSMOID_ExtendedUseCodeSigning
- */
- if((policy == kTPiSign) && certInfo[0].extendKeyUsage.present) {
- extendUsage = &certInfo[0].extendKeyUsage.extnData->extendedKeyUsage;
- if(extendUsage->numPurposes != 1) {
- tpPolicyError("tp_policyVerify: bad extendUsage->numPurposes "
- "(%d)",
- (int)extendUsage->numPurposes);
- policyFail = CSSM_TRUE;
- (certGroup->certAtIndex(0))->addStatusCode(
- CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
- }
- if(!tpCompareOids(extendUsage->purposes,
- &CSSMOID_ExtendedUseCodeSigning)) {
- tpPolicyError("tp_policyVerify: bad extendKeyUsage");
- policyFail = CSSM_TRUE;
- (certGroup->certAtIndex(0))->addStatusCode(
- CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE);
- }
- }
-
- /*
- * Verify authorityId-->subjectId linkage.
- * All optional - skip if needed fields not present.
- * Also, always skip last (root) cert.
- */
- for(certDex=0; certDex<(numCerts-1); certDex++) {
- if(!certInfo[certDex].authorityId.present ||
- !certInfo[certDex+1].subjectId.present) {
- continue;
- }
- authorityId = &certInfo[certDex].authorityId.extnData->authorityKeyID;
- if(!authorityId->keyIdentifierPresent) {
- /* we only know how to compare keyIdentifier */
- continue;
- }
- if(!tpCompareCssmData(&authorityId->keyIdentifier,
- &certInfo[certDex+1].subjectId.extnData->subjectKeyID)) {
- tpPolicyError("tp_policyVerify: bad key ID linkage");
- policyFail = CSSM_TRUE;
- (certGroup->certAtIndex(certDex))->addStatusCode(
- CSSMERR_APPLETP_INVALID_ID_LINKAGE);
- }
- }
-
- /*
- * SSL: optionally verify common name.
- * FIXME - should this be before or after the root cert test? How can
- * we return both errors?
- */
- if(policy == kTP_SSL) {
- CSSM_RETURN cerr = tp_verifySslOpts(*certGroup, policyFieldData,
- certInfo[0]);
- if(cerr) {
- policyFail = CSSM_TRUE;
- }
- }
-
- /* S/MIME */
- if(policy == kTP_SMIME) {
- CSSM_RETURN cerr = tp_verifySmimeOpts(*certGroup, policyFieldData,
- certInfo[0]);
- if(cerr) {
- policyFail = CSSM_TRUE;
- }
- }
-
- if(policyFail && (outErr == CSSM_OK)) {
- /* only error in this function was policy failure */
- outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
- }
-errOut:
- /* free resources */
- for(certDex=0; certDex<numCerts; certDex++) {
- thisCertInfo = &certInfo[certDex];
- iSignFreeCertInfo(clHand, thisCertInfo);
- }
- tpFree(alloc, certInfo);
- return outErr;
-}