/* * 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 #include #include "tpPolicies.h" #include #include #include "tpdebugging.h" #include "rootCerts.h" #include "certGroupUtils.h" #include #include #include #include #include #include #include /* * 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; iclHand(), 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; rdnDexnumberOfRDNs; rdnDex++) { rdnp = &x509name->RelativeDistinguishedName[rdnDex]; for(pairDex=0; pairDexnumberOfPairs; 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; dexLength; dex++) { /* cp points to start of current string digit */ /* find next dot */ const char *lastChar = cp + strLen; nextDot = cp + 1; for( ; nextDotLength - 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; dexnumNames; 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; iData == 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; inumPurposes; 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; certDexcertAtIndex(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; certDexcertAtIndex(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