X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5a719ac813caa6f2ceaa192274fad2e1c2cec162..df0e469fdcf0e0b3ef74bac6500e5751c40b4ec1:/AppleX509TP/tpPolicies.cpp diff --git a/AppleX509TP/tpPolicies.cpp b/AppleX509TP/tpPolicies.cpp index 476a4705..67423dfd 100644 --- a/AppleX509TP/tpPolicies.cpp +++ b/AppleX509TP/tpPolicies.cpp @@ -38,6 +38,7 @@ #include #include + /* * Our private per-extension info. One of these per (understood) extension per * cert. @@ -54,13 +55,14 @@ typedef struct { */ typedef struct { - /* extensions pertinent to iSign */ + /* 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; @@ -78,7 +80,7 @@ static CSSM_RETURN tpSetupExtension( iSignExtenInfo *extnInfo) // which component of certInfo { if(extnData->Length != sizeof(CSSM_X509_EXTENSION)) { - errorLog0("tpSetupExtension: malformed CSSM_FIELD\n"); + tpPolicyError("tpSetupExtension: malformed CSSM_FIELD"); return CSSMERR_TP_UNKNOWN_FORMAT; } CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)extnData->Data; @@ -147,7 +149,7 @@ static CSSM_RETURN iSignSearchUnknownExtensions( } if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) { - errorLog0("iSignSearchUnknownExtensions: malformed CSSM_FIELD\n"); + tpPolicyError("iSignSearchUnknownExtensions: malformed CSSM_FIELD"); return CSSMERR_TP_UNKNOWN_FORMAT; } CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data; @@ -168,11 +170,13 @@ static CSSM_RETURN iSignSearchUnknownExtensions( &fieldValue); if(crtn) { /* should never happen */ - errorLog0("searchUnknownExtensions: GetNextCachedFieldValue error\n"); + tpPolicyError("searchUnknownExtensions: GetNextCachedFieldValue" + "error"); break; } if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) { - errorLog0("iSignSearchUnknownExtensions: malformed CSSM_FIELD\n"); + tpPolicyError("iSignSearchUnknownExtensions: " + "malformed CSSM_FIELD"); crtn = CSSMERR_TP_UNKNOWN_FORMAT; break; } @@ -253,6 +257,13 @@ static CSSM_RETURN iSignGetCertInfo( 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. */ @@ -290,216 +301,58 @@ static void iSignFreeCertInfo( 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; dexsubjectAltName.present) { + CSSM_CL_FreeFieldValue(clHand, &CSSMOID_SubjectAltName, + certInfo->subjectAltName.valToFree); } -errOut: - tp_CertFreePublicKey(rootCert->clHand(), 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. +/* + * 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. */ -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); -} +typedef enum { + SN_CommonName, // CSSMOID_CommonName, host name format + SN_Email // CSSMOID_EmailAddress +} SubjSubjNameSearchType; -/* - * 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 +static CSSM_BOOL tpCompareSubjectName( + TPCertInfo &cert, + SubjSubjNameSearchType searchType, + const char *callerStr, // already tpToLower'd + uint32 callerStrLen, + bool &fieldFound) { - 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; - } + 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; - /* 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; + fieldFound = false; + switch(searchType) { + case SN_CommonName: + oidSrch = &CSSMOID_CommonName; break; - case CSSM_ALGID_FEE: - hdr->Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING; + case SN_Email: + oidSrch = &CSSMOID_EmailAddress; 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); + assert(0); + return CSSM_FALSE; } - 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"); + 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))) { - errorLog0("tp_verifySslOpts: malformed CSSM_X509_NAME"); + tpPolicyError("tp_verifySslOpts: malformed CSSM_X509_NAME"); cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData); return CSSM_FALSE; } @@ -514,11 +367,20 @@ static CSSM_BOOL tpCompareCommonName( 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(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; @@ -593,73 +455,104 @@ static CSSM_BOOL tpCompIpAddrStr( } /* - * See if cert's subjectAltName matches caller-specified hostname, either - * as a dnsName or an iPAddress. + * 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. * - * 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). + * 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( - TPCertInfo &cert, - const char *hostName, - uint32 hostNameLen, - bool &dnsNameFound) // RETURNED + const iSignExtenInfo &subjAltNameInfo, + const char *appStr, + uint32 appStrLen, + SubjAltNameSearchType searchType, + bool &dnsNameFound, // RETURNED, SAN_HostName case + bool &emailFound) // RETURNED, SAN_Email case { - CSSM_DATA_PTR subjAltNameData = NULL; - CSSM_RETURN crtn; - CSSM_BOOL ourRtn = CSSM_FALSE; - dnsNameFound = false; - crtn = cert.fetchField(&CSSMOID_SubjectAltName, &subjAltNameData); - if(crtn) { + emailFound = false; + if(!subjAltNameInfo.present) { /* 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; + CE_GeneralNames *names = &subjAltNameInfo.extnData->subjectAltName; + CSSM_BOOL ourRtn = CSSM_FALSE; + char *certName; + unsigned certNameLen; - /* Search thru the CE_GeneralNames looking for a DNSName or IP Address */ + /* Search thru the CE_GeneralNames looking for the appropriate attribute */ 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"); + 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; } - serverName = (char *)name->name.Data; - if(serverName == NULL) { - errorLog0("tpCompareSubjectAltName: malformed " - "CE_GeneralName (2)\n"); + certName = (char *)name->name.Data; + if(certName == NULL) { + tpErrorLog("tpCompareSubjectAltName: malformed " + "GNT_RFC822Name\n"); break; } - serverNameLen = name->name.Length; - ourRtn = tpCompareHostNames(hostName, hostNameLen, - serverName, serverNameLen); - dnsNameFound = true; - break; - - default: - /* not interested, proceed to next name */ + certNameLen = name->name.Length; + emailFound = true; + if(appStr != NULL) { + ourRtn = tpCompareEmailAddr(appStr, appStrLen, certName, + certNameLen); + } break; } if(ourRtn) { @@ -667,7 +560,6 @@ static CSSM_BOOL tpCompareSubjectAltName( break; } } - cert.freeField(&CSSMOID_SubjectAltName, subjAltNameData); return ourRtn; } @@ -699,12 +591,26 @@ static CSSM_BOOL tpIsNumeric( */ static CSSM_RETURN tp_verifySslOpts( TPCertGroup &certGroup, - const CSSM_APPLE_TP_SSL_OPTIONS *sslOpts) + const CSSM_DATA *sslFieldOpts, + const iSignCertInfo &leafCertInfo) { - if(sslOpts == NULL) { + /* 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; @@ -728,8 +634,11 @@ static CSSM_RETURN tp_verifySslOpts( /* First check subjectAltName... */ bool dnsNameFound = false; - match = tpCompareSubjectAltName(*leaf, hostName, hostNameLen, - dnsNameFound); + bool dummy; + match = tpCompareSubjectAltName(leafCertInfo.subjectAltName, + hostName, hostNameLen, + SAN_HostName, dnsNameFound, dummy); + /* * Then common name, if * -- no match from subjectAltName, AND @@ -737,7 +646,9 @@ static CSSM_RETURN tp_verifySslOpts( * -- hostName is not strictly numeric form (1.2.3.4) */ if(!match && !dnsNameFound && !tpIsNumeric(hostName, hostNameLen)) { - match = tpCompareCommonName(*leaf, hostName, hostNameLen); + bool fieldFound; + match = tpCompareSubjectName(*leaf, SN_CommonName, hostName, hostNameLen, + fieldFound); } certGroup.alloc().free(hostName); if(match) { @@ -749,6 +660,239 @@ static CSSM_RETURN tp_verifySslOpts( } } +/* + * 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. @@ -773,6 +917,14 @@ static CSSM_RETURN tp_verifySslOpts( */ #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. @@ -792,9 +944,9 @@ CSSM_RETURN tp_policyVerify( 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 + CSSM_APPLE_TP_ACTION_FLAGS actionFlags, + const CSSM_DATA *policyFieldData, // optional + void *policyOpts) // future options { iSignCertInfo *certInfo = NULL; uint32 numCerts; @@ -803,8 +955,8 @@ CSSM_RETURN tp_policyVerify( uint16 actUsage; unsigned certDex; CSSM_BOOL cA = CSSM_FALSE; // init for compiler warning - CSSM_BOOL isLeaf; // end entity - CSSM_BOOL isRoot; // root cert + bool isLeaf; // end entity + bool isRoot; // root cert CE_ExtendedKeyUsage *extendUsage; CE_AuthorityKeyID *authorityId; CSSM_RETURN outErr = CSSM_OK; // for gross, non-policy errors @@ -825,11 +977,11 @@ CSSM_RETURN tp_policyVerify( if(policy == kTPiSign) { if(!verifiedToRoot) { /* no way, this requires a root cert */ - return CSSMERR_TP_INVALID_CERTGROUP; + return CSSMERR_TP_VERIFY_ACTION_FAILED; } if(numCerts <= 1) { /* nope, not for iSign */ - return CSSMERR_TP_INVALID_CERTGROUP; + return CSSMERR_TP_VERIFY_ACTION_FAILED; } } @@ -861,17 +1013,20 @@ CSSM_RETURN tp_policyVerify( if(thisCertInfo->foundUnknownCritical) { /* illegal for all policies */ - errorLog0("tp_policyVerify: critical flag in unknown extension\n"); + 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 only!) + * of length one (kTPx509Basic, kCrlPolicy only!) + * FIXME: should this code work of the last cert in the chain is + * NOT a root? */ - isLeaf = (certDex == 0) ? CSSM_TRUE : CSSM_FALSE; - isRoot = (certDex == (numCerts - 1)) ? CSSM_TRUE : CSSM_FALSE; + isLeaf = thisTpCertInfo->isLeaf(); + isRoot = thisTpCertInfo->isSelfSigned(); /* * BasicConstraints.cA @@ -879,11 +1034,15 @@ CSSM_RETURN tp_policyVerify( * 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 + * 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 */ @@ -897,21 +1056,25 @@ CSSM_RETURN tp_policyVerify( switch(policy) { case kTPx509Basic: case kTP_SSL: + case kCrlPolicy: + case kTP_SMIME: /* - * not present, not leaf, not root, kTPx509Basic + * 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 */ - errorLog0("tp_policyVerify: no basicConstraints\n"); + tpPolicyError("tp_policyVerify: no " + "basicConstraints"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode( CSSMERR_APPLETP_NO_BASIC_CONSTRAINTS); break; default: /* not reached */ + assert(0); break; } } @@ -922,7 +1085,8 @@ CSSM_RETURN tp_policyVerify( /* disabled for verisign compatibility */ if(!thisCertInfo->basicConstraints.critical) { /* per RFC 2459 */ - errorLog0("tp_policyVerify: basicConstraints marked not critical\n"); + tpPolicyError("tp_policyVerify: basicConstraints marked " + "not critical"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_TP_VERIFY_ACTION_FAILED); } @@ -943,7 +1107,8 @@ CSSM_RETURN tp_policyVerify( * etc. */ if(certDex > (bcp->pathLenConstraint + 1)) { - errorLog0("tp_policyVerify: pathLenConstraint exceeded\n"); + tpPolicyError("tp_policyVerify: pathLenConstraint " + "exceeded"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode( CSSMERR_APPLETP_PATH_LEN_CONSTRAINT); @@ -952,15 +1117,20 @@ CSSM_RETURN tp_policyVerify( } 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"); + /* + * 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) { - errorLog0("tp_policyVerify: cA false for non-leaf\n"); + tpPolicyError("tp_policyVerify: cA false for non-leaf"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_CA); } @@ -969,18 +1139,20 @@ CSSM_RETURN tp_policyVerify( * 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) + * kTPx509Basic, + * kTP_SSL, + * kTP_SMIME : ignored (though used later for chain verification) */ if((policy == kTPiSign) && thisCertInfo->authorityId.present) { if(isRoot) { - errorLog0("tp_policyVerify: authorityId in root\n"); + tpPolicyError("tp_policyVerify: authorityId in root"); 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"); + tpPolicyError("tp_policyVerify: authorityId marked " + "critical"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_AUTHORITY_ID); } @@ -990,11 +1162,12 @@ CSSM_RETURN tp_policyVerify( * Subject Key Identifier optional * iSign : can't be critical. * kTPx509Basic, - * kTP_SSL : ignored (though used later for chain verification) + * kTP_SSL, + * kTP_SMIME : ignored (though used later for chain verification) */ if(thisCertInfo->subjectId.present) { if((policy == kTPiSign) && thisCertInfo->subjectId.critical) { - errorLog0("tp_policyVerify: subjectId marked critical\n"); + tpPolicyError("tp_policyVerify: subjectId marked critical"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_SUBJECT_ID); } @@ -1007,8 +1180,12 @@ CSSM_RETURN tp_policyVerify( * Exception : if leaf, and keyUsage not present, * netscape-cert-type must be present, with * Object Signing bit set - * kTPx509Basic : non-leaf : usage = keyCertSign + * 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) { /* @@ -1017,12 +1194,18 @@ CSSM_RETURN tp_policyVerify( * 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; + 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 { @@ -1031,11 +1214,24 @@ CSSM_RETURN tp_policyVerify( } actUsage = thisCertInfo->keyUsage.extnData->keyUsage; if(!(actUsage & expUsage)) { - errorLog2("tp_policyVerify: bad keyUsage (leaf %s; usage 0x%x)\n", + 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((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); + } + } + } else if(policy == kTPiSign) { /* @@ -1047,13 +1243,15 @@ CSSM_RETURN tp_policyVerify( thisCertInfo->netscapeCertType.extnData->netscapeCertType; if(!(ct & CE_NCT_ObjSign)) { - errorLog0("tp_policyVerify: netscape-cert-type, !ObjectSign\n"); + tpPolicyError("tp_policyVerify: netscape-cert-type, " + "!ObjectSign"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE); } } else if(!isRoot) { - errorLog0("tp_policyVerify: !isRoot, no keyUsage, !(leaf and netscapeCertType)\n"); + tpPolicyError("tp_policyVerify: !isRoot, no keyUsage, " + "!(leaf and netscapeCertType)"); policyFail = CSSM_TRUE; thisTpCertInfo->addStatusCode(CSSMERR_APPLETP_INVALID_KEY_USAGE); } @@ -1069,7 +1267,8 @@ CSSM_RETURN tp_policyVerify( 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", + tpPolicyError("tp_policyVerify: bad extendUsage->numPurposes " + "(%d)", (int)extendUsage->numPurposes); policyFail = CSSM_TRUE; (certGroup->certAtIndex(0))->addStatusCode( @@ -1077,7 +1276,7 @@ CSSM_RETURN tp_policyVerify( } if(!tpCompareOids(extendUsage->purposes, &CSSMOID_ExtendedUseCodeSigning)) { - errorLog0("tp_policyVerify: bad extendKeyUsage\n"); + tpPolicyError("tp_policyVerify: bad extendKeyUsage"); policyFail = CSSM_TRUE; (certGroup->certAtIndex(0))->addStatusCode( CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); @@ -1101,7 +1300,7 @@ CSSM_RETURN tp_policyVerify( } if(!tpCompareCssmData(&authorityId->keyIdentifier, &certInfo[certDex+1].subjectId.extnData->subjectKeyID)) { - errorLog0("tp_policyVerify: bad key ID linkage\n"); + tpPolicyError("tp_policyVerify: bad key ID linkage"); policyFail = CSSM_TRUE; (certGroup->certAtIndex(certDex))->addStatusCode( CSSMERR_APPLETP_INVALID_ID_LINKAGE); @@ -1114,34 +1313,22 @@ CSSM_RETURN tp_policyVerify( * we return both errors? */ if(policy == kTP_SSL) { - CSSM_RETURN cerr = tp_verifySslOpts(*certGroup, sslOpts); + CSSM_RETURN cerr = tp_verifySslOpts(*certGroup, policyFieldData, + certInfo[0]); 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; - } + /* S/MIME */ + if(policy == kTP_SMIME) { + CSSM_RETURN cerr = tp_verifySmimeOpts(*certGroup, policyFieldData, + certInfo[0]); + if(cerr) { + policyFail = CSSM_TRUE; } } - #endif /* TP_ROOT_CERT_ENABLE */ + if(policyFail && (outErr == CSSM_OK)) { /* only error in this function was policy failure */ outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;