/* * 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 pertinent to iSign */ iSignExtenInfo authorityId; iSignExtenInfo subjectId; iSignExtenInfo keyUsage; iSignExtenInfo extendKeyUsage; iSignExtenInfo basicConstraints; iSignExtenInfo netscapeCertType; /* 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)) { errorLog0("tpSetupExtension: malformed CSSM_FIELD\n"); 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)) { errorLog0("iSignSearchUnknownExtensions: malformed CSSM_FIELD\n"); 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 */ errorLog0("searchUnknownExtensions: GetNextCachedFieldValue error\n"); break; } if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) { errorLog0("iSignSearchUnknownExtensions: malformed CSSM_FIELD\n"); 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; } /* 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 TP_ROOT_CERT_ENABLE /* * Common code for comparing a root to a list of known embedded roots. */ static CSSM_BOOL tp_isKnownRootCert( TPCertInfo *rootCert, // raw cert to compare const tpRootCert *knownRoots, unsigned numKnownRoots) { const CSSM_DATA *subjectName = NULL; CSSM_DATA_PTR publicKey = NULL; unsigned dex; CSSM_BOOL brtn = CSSM_FALSE; CSSM_DATA_PTR valToFree = NULL; subjectName = rootCert->subjectName(); publicKey = tp_CertGetPublicKey(rootCert, &valToFree); if(publicKey == NULL) { errorLog0("tp_isKnownRootCert: error retrieving public key info!\n"); goto errOut; } /* * Grind thru the list of known certs, demanding perfect match of * both fields */ for(dex=0; dexclHand(), valToFree); return brtn; } /* * See if specified root cert is a known (embedded) iSign root cert. * Returns CSSM_TRUE if the cert is a known root cert. * * Note as of 6/12/02, we do not distinguish between internally * cached iSign roots and SSL roots. Maybe someday we will do so again, * so let's leave these two functions separate. */ static CSSM_BOOL tp_isIsignRootCert( CSSM_CL_HANDLE clHand, TPCertInfo *rootCert) // raw cert from cert group { const tpRootCert *roots; unsigned numRoots; roots = TPRootStore::tpGlobalRoots().rootCerts(clHand, numRoots); return tp_isKnownRootCert(rootCert, roots, numRoots); } /* * See if specified root cert is a known (embedded) SSL root cert. * Returns CSSM_TRUE if the cert is a known root cert. */ static CSSM_BOOL tp_isSslRootCert( CSSM_CL_HANDLE clHand, TPCertInfo *rootCert) // raw cert from cert group { const tpRootCert *roots; unsigned numRoots; roots = TPRootStore::tpGlobalRoots().rootCerts(clHand, numRoots); return tp_isKnownRootCert(rootCert, roots, numRoots); } /* * Attempt to verify specified cert (from the end of a chain) with one of * our known SSL roots. */ CSSM_BOOL tp_verifyWithSslRoots( CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, TPCertInfo *certToVfy) // last in chain, not root { CSSM_KEY rootKey; // pub key manufactured from tpRootCert info CSSM_CC_HANDLE ccHand; // signature context CSSM_RETURN crtn; unsigned dex; const tpRootCert *rootInfo; CSSM_BOOL brtn = CSSM_FALSE; CSSM_KEYHEADER *hdr = &rootKey.KeyHeader; CSSM_X509_ALGORITHM_IDENTIFIER_PTR algId; CSSM_DATA_PTR valToFree = NULL; CSSM_ALGORITHMS sigAlg; const tpRootCert *rootCerts = NULL; unsigned numRootCerts = 0; memset(&rootKey, 0, sizeof(CSSM_KEY)); /* * Get signature algorithm from subject key */ algId = tp_CertGetAlgId(certToVfy, &valToFree); if(algId == NULL) { /* bad cert */ return CSSM_FALSE; } /* subsequest errors to errOut: */ /* map to key and signature algorithm */ sigAlg = tpOidToAldId(&algId->algorithm, &hdr->AlgorithmId); if(sigAlg == CSSM_ALGID_NONE) { errorLog0("tp_verifyWithSslRoots: unknown sig alg\n"); goto errOut; } /* Set up other constant key fields */ hdr->BlobType = CSSM_KEYBLOB_RAW; switch(hdr->AlgorithmId) { case CSSM_ALGID_RSA: hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_PKCS1; break; case CSSM_ALGID_DSA: hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_FIPS186; break; case CSSM_ALGID_FEE: hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; break; default: /* punt */ hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_NONE; } hdr->KeyClass = CSSM_KEYCLASS_PUBLIC_KEY; hdr->KeyAttr = CSSM_KEYATTR_MODIFIABLE | CSSM_KEYATTR_EXTRACTABLE; hdr->KeyUsage = CSSM_KEYUSE_VERIFY; rootCerts = TPRootStore::tpGlobalRoots().rootCerts(clHand, numRootCerts); for(dex=0; dexsubjectName, certToVfy->issuerName())) { /* not this root */ continue; } /* only variation in key in the loop - raw key bits and size */ rootKey.KeyData = rootInfo->publicKey; hdr->LogicalKeySizeInBits = rootInfo->keySize; crtn = CSSM_CSP_CreateSignatureContext(cspHand, sigAlg, NULL, // AcccedCred &rootKey, &ccHand); if(crtn) { errorLog0("tp_verifyWithSslRoots: CSSM_CSP_CreateSignatureContext err\n"); CssmError::throwMe(CSSMERR_TP_INTERNAL_ERROR); } crtn = CSSM_CL_CertVerify(clHand, ccHand, certToVfy->certData(), NULL, // no signer cert NULL, // VerifyScope 0); // ScopeSize CSSM_DeleteContext(ccHand); if(crtn == CSSM_OK) { /* success! */ brtn = CSSM_TRUE; break; } } errOut: if(valToFree != NULL) { tp_CertFreeAlgId(clHand, valToFree); } return brtn; } #endif /* TP_ROOT_CERT_ENABLE */ /* * See if cert's Subject.commonName matches caller-specified hostname. * Returns CSSM_TRUE if match, else returns CSSM_FALSE. */ static CSSM_BOOL tpCompareCommonName( TPCertInfo &cert, const char *hostName, uint32 hostNameLen) { char *commonName = NULL; // from cert's subject name uint32 commonNameLen = 0; CSSM_DATA_PTR subjNameData = NULL; CSSM_RETURN crtn; CSSM_BOOL ourRtn = CSSM_FALSE; crtn = cert.fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData); if(crtn) { /* should never happen, we shouldn't be here if there is no subject */ errorLog0("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))) { errorLog0("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, &CSSMOID_CommonName)) { commonName = (char *)ptvp->value.Data; commonNameLen = ptvp->value.Length; ourRtn = tpCompareHostNames(hostName, hostNameLen, commonName, commonNameLen); 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 matches caller-specified hostname, either * as a dnsName or an iPAddress. * * Returns CSSM_TRUE if match, else returns CSSM_FALSE. Also indicates * whether or not a dnsName was found (in which case the subject's * common name should NOT be a candidate for verification). */ static CSSM_BOOL tpCompareSubjectAltName( TPCertInfo &cert, const char *hostName, uint32 hostNameLen, bool &dnsNameFound) // RETURNED { CSSM_DATA_PTR subjAltNameData = NULL; CSSM_RETURN crtn; CSSM_BOOL ourRtn = CSSM_FALSE; dnsNameFound = false; crtn = cert.fetchField(&CSSMOID_SubjectAltName, &subjAltNameData); if(crtn) { /* common failure, no subjectAltName found */ return CSSM_FALSE; } CSSM_X509_EXTENSION_PTR exten = (CSSM_X509_EXTENSION_PTR)subjAltNameData->Data; /* Paranoid check of extension integrity */ if((exten == NULL) || (subjAltNameData->Length != sizeof(CSSM_X509_EXTENSION)) || (exten->format != CSSM_X509_DATAFORMAT_PARSED) || (exten->value.parsedValue == NULL)) { errorLog0("tpCompareSubjectAltName: malformed CSSM_X509_EXTENSION"); cert.freeField(&CSSMOID_SubjectAltName, subjAltNameData); return CSSM_FALSE; } CE_GeneralNames *names = (CE_GeneralNames *)exten->value.parsedValue; char *serverName; unsigned serverNameLen; /* Search thru the CE_GeneralNames looking for a DNSName or IP Address */ for(unsigned dex=0; dexnumNames; dex++) { CE_GeneralName *name = &names->generalName[dex]; switch(name->nameType) { case GNT_IPAddress: ourRtn = tpCompIpAddrStr(hostName, hostNameLen, &name->name); break; case GNT_DNSName: if(name->berEncoded) { errorLog0("tpCompareSubjectAltName: malformed " "CE_GeneralName (1)\n"); break; } serverName = (char *)name->name.Data; if(serverName == NULL) { errorLog0("tpCompareSubjectAltName: malformed " "CE_GeneralName (2)\n"); break; } serverNameLen = name->name.Length; ourRtn = tpCompareHostNames(hostName, hostNameLen, serverName, serverNameLen); dnsNameFound = true; break; default: /* not interested, proceed to next name */ break; } if(ourRtn) { /* success */ break; } } cert.freeField(&CSSMOID_SubjectAltName, subjAltNameData); 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; iServerNameLen; 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; match = tpCompareSubjectAltName(*leaf, hostName, hostNameLen, dnsNameFound); /* * 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)) { match = tpCompareCommonName(*leaf, hostName, hostNameLen); } certGroup.alloc().free(hostName); if(match) { return CSSM_OK; } else { leaf->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH); return CSSMERR_TP_VERIFY_ACTION_FAILED; } } /* * 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 /* * 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 const CSSM_APPLE_TP_ACTION_DATA *actionData, const CSSM_APPLE_TP_SSL_OPTIONS *sslOpts, 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 CSSM_BOOL isLeaf; // end entity CSSM_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_INVALID_CERTGROUP; } if(numCerts <= 1) { /* nope, not for iSign */ return CSSMERR_TP_INVALID_CERTGROUP; } } /* 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 */ errorLog0("tp_policyVerify: critical flag in unknown extension\n"); 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 only!) */ isLeaf = (certDex == 0) ? CSSM_TRUE : CSSM_FALSE; isRoot = (certDex == (numCerts - 1)) ? CSSM_TRUE : CSSM_FALSE; /* * 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: 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) { 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: /* * not present, not leaf, not root, kTPx509Basic * ....RFC2459 says this can not be a CA */ cA = CSSM_FALSE; break; case kTPiSign: /* required for iSign in this position */ errorLog0("tp_policyVerify: no basicConstraints\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode( CSSMERR_APPLETP_NO_BASIC_CONSTRAINTS); break; default: /* not reached */ break; } } } else { /* basicConstraints present */ #if BASIC_CONSTRAINTS_MUST_BE_CRITICAL /* disabled for verisign compatibility */ if(!thisCertInfo->basicConstraints.critical) { /* per RFC 2459 */ errorLog0("tp_policyVerify: basicConstraints marked not critical\n"); 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)) { errorLog0("tp_policyVerify: pathLenConstraint exceeded\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode( CSSMERR_APPLETP_PATH_LEN_CONSTRAINT); } } } if(isLeaf) { /* special case to allow a chain of length 1, leaf and root * both true (kTPx509Basic, kTP_SSL only) */ if(cA && !isRoot) { errorLog0("tp_policyVerify: cA true for leaf\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA); } } else if(!cA) { errorLog0("tp_policyVerify: cA false for non-leaf\n"); 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 : ignored (though used later for chain verification) */ if((policy == kTPiSign) && thisCertInfo->authorityId.present) { if(isRoot) { errorLog0("tp_policyVerify: authorityId in root\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID); } if(thisCertInfo->authorityId.critical) { /* illegal per RFC 2459 */ errorLog0("tp_policyVerify: authorityId marked critical\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID); } } /* * Subject Key Identifier optional * iSign : can't be critical. * kTPx509Basic, * kTP_SSL : ignored (though used later for chain verification) */ if(thisCertInfo->subjectId.present) { if((policy == kTPiSign) && thisCertInfo->subjectId.critical) { errorLog0("tp_policyVerify: subjectId marked critical\n"); 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 : non-leaf : usage = keyCertSign * Leaf: don't care */ 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) { if(policy == kTPiSign) { expUsage = CE_KU_DigitalSignature; } else { /* hack to accept whatever's there */ expUsage = thisCertInfo->keyUsage.extnData->keyUsage; } } else { /* this is true for all policies */ expUsage = CE_KU_KeyCertSign; } actUsage = thisCertInfo->keyUsage.extnData->keyUsage; if(!(actUsage & expUsage)) { errorLog2("tp_policyVerify: bad keyUsage (leaf %s; usage 0x%x)\n", (certDex == 0) ? "TRUE" : "FALSE", actUsage); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE); } } 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)) { errorLog0("tp_policyVerify: netscape-cert-type, !ObjectSign\n"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE); } } else if(!isRoot) { errorLog0("tp_policyVerify: !isRoot, no keyUsage, !(leaf and netscapeCertType)\n"); 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) { errorLog1("tp_policyVerify: bad extendUsage->numPurposes (%d)\n", (int)extendUsage->numPurposes); policyFail = CSSM_TRUE; (certGroup->certAtIndex(0))->addStatusCode( CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); } if(!tpCompareOids(extendUsage->purposes, &CSSMOID_ExtendedUseCodeSigning)) { errorLog0("tp_policyVerify: bad extendKeyUsage\n"); 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)) { errorLog0("tp_policyVerify: bad key ID linkage\n"); 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, sslOpts); if(cerr) { policyFail = CSSM_TRUE; } } /* iSign, SSL: compare root against known root certs */ /* FIXME - this goes away soon */ #if TP_ROOT_CERT_ENABLE if((outErr == CSSM_OK) && // skip if we have a gross error (other than policy failure) (actionData != NULL) && (actionData->ActionFlags & 0x80000000)) { // The secret "enable root cert check" flag TPCertInfo *lastCert = certGroup->lastCert(); if(policy == kTPiSign) { bool brtn = tp_isIsignRootCert(clHand, lastCert); if(!brtn) { policyFail = CSSM_TRUE; } } else if(verifiedToRoot && (policy == kTP_SSL)) { /* note SSL doesn't require root here */ bool brtn = tp_isSslRootCert(clHand, lastCert); if(!brtn) { outErr = CSSMERR_TP_INVALID_ANCHOR_CERT; } } } #endif /* TP_ROOT_CERT_ENABLE */ 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