/* * 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 #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( Allocator &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( Allocator &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( Allocator &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, bool normalizeAll, // for SN_Email case: lower-case all of // the cert's value, not just the portion // after the '@' 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: { /* handle odd encodings that we need to convert to 8-bit */ CFStringBuiltInEncodings encoding; CFDataRef cfd = NULL; bool doConvert = false; switch(ptvp->valueType) { case BER_TAG_T61_STRING: /* a.k.a. Teletex */ encoding = kCFStringEncodingISOLatin1; doConvert = true; break; case BER_TAG_PKIX_BMP_STRING: encoding = kCFStringEncodingUnicode; doConvert = true; break; /* * All others - either take as is, or let it fail due to * illegal/incomprehensible format */ default: break; } if(doConvert) { /* raw data ==> CFString */ cfd = CFDataCreate(NULL, (UInt8 *)certName, certNameLen); if(cfd == NULL) { /* try next component */ break; } CFStringRef cfStr = CFStringCreateFromExternalRepresentation( NULL, cfd, encoding); CFRelease(cfd); if(cfStr == NULL) { tpPolicyError("tpCompareSubjectName: bad str (1)"); break; } /* CFString ==> straight ASCII */ cfd = CFStringCreateExternalRepresentation(NULL, cfStr, kCFStringEncodingASCII, 0); CFRelease(cfStr); if(cfd == NULL) { tpPolicyError("tpCompareSubjectName: bad str (2)"); break; } certNameLen = CFDataGetLength(cfd); certName = (char *)CFDataGetBytePtr(cfd); } ourRtn = tpCompareHostNames(callerStr, callerStrLen, certName, certNameLen); if(doConvert) { assert(cfd != NULL); CFRelease(cfd); } break; } case SN_Email: ourRtn = tpCompareEmailAddr(callerStr, callerStrLen, certName, certNameLen, normalizeAll); 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, // caller has lower-cased as appropriate uint32 appStrLen, SubjAltNameSearchType searchType, bool normalizeAll, // for SAN_Email case: lower-case all of // the cert's value, not just the portion // after the '@' 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, normalizeAll); } 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; ivalueType) { case BER_TAG_T61_STRING: /* a.k.a. Teletex */ encoding = kCFStringEncodingISOLatin1; break; case BER_TAG_PKIX_BMP_STRING: encoding = kCFStringEncodingUnicode; break; case BER_TAG_PRINTABLE_STRING: case BER_TAG_IA5_STRING: case BER_TAG_PKIX_UTF8_STRING: encoding = kCFStringEncodingUTF8; break; default: return NULL; } /* raw data ==> CFString */ CFDataRef cfd = CFDataCreate(NULL, tvp->value.Data, tvp->value.Length); if(cfd == NULL) { return NULL; } CFStringRef cfStr = CFStringCreateFromExternalRepresentation(NULL, cfd, encoding); CFRelease(cfd); return cfStr; } /* * Compare a CFString and a string represented by a CSSM_X509_TYPE_VALUE_PAIR. * Returns CSSM_TRUE if they are equal. */ static bool tpCompareTvpToCfString( const CSSM_X509_TYPE_VALUE_PAIR *tvp, CFStringRef refStr, CFOptionFlags flags) // e.g., kCFCompareCaseInsensitive { CFStringRef cfStr = tpTvpToCfString(tvp); if(cfStr == NULL) { return false; } CFComparisonResult res = CFStringCompare(refStr, cfStr, flags); CFRelease(cfStr); if(res == kCFCompareEqualTo) { return true; } else { return false; } } /* * Verify iChat handle. We search for a matching (case-insensitive) string * comprised of: * * -- name component ("dmitch") from subject name's CommonName * -- implicit '@' * -- domain name from subject name's organizationalUnit * * Plus we require an Organization component of "Apple Computer, Inc.". */ static bool tpCompareIChatHandleName( TPCertInfo &cert, const char *iChatHandle, // UTF8 uint32 iChatHandleLen) { CSSM_DATA_PTR subjNameData = NULL; // from fetchField CSSM_RETURN crtn; bool ourRtn = false; CSSM_X509_NAME_PTR x509name; CSSM_X509_TYPE_VALUE_PAIR *ptvp; CSSM_X509_RDN_PTR rdnp; unsigned rdnDex; unsigned pairDex; /* search until all of these are true */ CSSM_BOOL commonNameMatch = CSSM_FALSE; // name before '@' CSSM_BOOL orgUnitMatch = CSSM_FALSE; // domain after '@ CSSM_BOOL orgMatch = CSSM_FALSE; // Apple COmputer, Inc. /* * incoming UTF8 handle ==> two components. * First convert to CFString. */ if(iChatHandle[iChatHandleLen - 1] == '\0') { /* avoid NULL when creating CFStrings */ iChatHandleLen--; } CFDataRef cfd = CFDataCreate(NULL, (const UInt8 *)iChatHandle, iChatHandleLen); if(cfd == NULL) { return false; } CFStringRef handleStr = CFStringCreateFromExternalRepresentation(NULL, cfd, kCFStringEncodingUTF8); CFRelease(cfd); if(handleStr == NULL) { tpPolicyError("tpCompareIChatHandleName: bad incoming handle (1)"); return false; } /* * Find the '@' delimiter */ CFRange whereIsAt; whereIsAt = CFStringFind(handleStr, CFSTR("@"), 0); if(whereIsAt.length == 0) { tpPolicyError("tpCompareIChatHandleName: bad incoming handle: no @"); CFRelease(handleStr); return false; } /* * Two components, before and after delimiter */ CFRange r = {0, whereIsAt.location}; CFStringRef iChatName = CFStringCreateWithSubstring(NULL, handleStr, r); if(iChatName == NULL) { tpPolicyError("tpCompareIChatHandleName: bad incoming handle (2)"); CFRelease(handleStr); return false; } r.location = whereIsAt.location + 1; // after the '@' r.length = CFStringGetLength(handleStr) - r.location; CFStringRef iChatDomain = CFStringCreateWithSubstring(NULL, handleStr, r); CFRelease(handleStr); if(iChatDomain == NULL) { tpPolicyError("tpCompareIChatHandleName: bad incoming handle (3)"); CFRelease(iChatName); return false; } /* subsequent errors to errOut: */ /* get subject name in CSSM form, all subsequent ops work on that */ crtn = cert.fetchField(&CSSMOID_X509V1SubjectNameCStruct, &subjNameData); if(crtn) { /* should never happen, we shouldn't be here if there is no subject */ tpPolicyError("tpCompareIChatHandleName: error retrieving subject name"); goto errOut; } x509name = (CSSM_X509_NAME_PTR)subjNameData->Data; if((x509name == NULL) || (subjNameData->Length != sizeof(CSSM_X509_NAME))) { tpPolicyError("tpCompareIChatHandleName: malformed CSSM_X509_NAME"); goto errOut; } /* Now grunge thru the X509 name looking for three fields */ for(rdnDex=0; rdnDexnumberOfRDNs; rdnDex++) { rdnp = &x509name->RelativeDistinguishedName[rdnDex]; for(pairDex=0; pairDexnumberOfPairs; pairDex++) { ptvp = &rdnp->AttributeTypeAndValue[pairDex]; if(!commonNameMatch && tpCompareOids(&ptvp->type, &CSSMOID_CommonName) && tpCompareTvpToCfString(ptvp, iChatName, kCFCompareCaseInsensitive)) { commonNameMatch = CSSM_TRUE; } if(!orgUnitMatch && tpCompareOids(&ptvp->type, &CSSMOID_OrganizationalUnitName) && tpCompareTvpToCfString(ptvp, iChatDomain, kCFCompareCaseInsensitive)) { orgUnitMatch = CSSM_TRUE; } if(!orgMatch && tpCompareOids(&ptvp->type, &CSSMOID_OrganizationName) && /* this one is case sensitive */ tpCompareTvpToCfString(ptvp, CFSTR("Apple Computer, Inc."), 0)) { orgMatch = CSSM_TRUE; } if(commonNameMatch && orgUnitMatch && orgMatch) { /* TA DA */ ourRtn = true; goto errOut; } } } errOut: cert.freeField(&CSSMOID_X509V1SubjectNameCStruct, subjNameData); CFRelease(iChatName); CFRelease(iChatDomain); return ourRtn; } /* * Verify SSL options. Currently this just consists of matching the * leaf cert's subject common name against the caller's (optional) * server name. */ static CSSM_RETURN tp_verifySslOpts( TPCertGroup &certGroup, const CSSM_DATA *sslFieldOpts, const iSignCertInfo &leafCertInfo) { CSSM_APPLE_TP_SSL_OPTIONS *sslOpts = NULL; unsigned hostNameLen = 0; const char *serverName = NULL; TPCertInfo *leaf = certGroup.certAtIndex(0); assert(leaf != NULL); /* CSSM_APPLE_TP_SSL_OPTIONS is optional */ if((sslFieldOpts != NULL) && (sslFieldOpts->Data != NULL)) { 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; } hostNameLen = sslOpts->ServerNameLen; serverName = sslOpts->ServerName; } /* host name check is optional */ if(hostNameLen != 0) { if(serverName == NULL) { return CSSMERR_TP_INVALID_POINTER; } /* convert caller's hostname string to lower case */ char *hostName = (char *)certGroup.alloc().malloc(hostNameLen); memmove(hostName, serverName, hostNameLen); tpToLower(hostName, hostNameLen); CSSM_BOOL match = CSSM_FALSE; /* First check subjectAltName... */ bool dnsNameFound = false; bool dummy; match = tpCompareSubjectAltName(leafCertInfo.subjectAltName, hostName, hostNameLen, SAN_HostName, false, 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, false, hostName, hostNameLen, fieldFound); } certGroup.alloc().free(hostName); if(!match) { leaf->addStatusCode(CSSMERR_APPLETP_HOSTNAME_MISMATCH); return CSSMERR_APPLETP_HOSTNAME_MISMATCH; } } /* * Ensure that, if an extendedKeyUsage extension is present in the * leaf, that either anyExtendedKeyUsage or the appropriate * CSSMOID_{Server,Client}Auth usage is present. */ const iSignExtenInfo &ekuInfo = leafCertInfo.extendKeyUsage; if(ekuInfo.present) { bool foundGoodEku = false; CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData; assert(eku != NULL); /* * Determine appropriate extended key usage; default is server side */ const CSSM_OID *extUse = &CSSMOID_ServerAuth; if((sslOpts != NULL) && /* optional, default server side */ (sslOpts->Version > 0) && /* this was added in struct version 1 */ (sslOpts->Flags & CSSM_APPLE_TP_SSL_CLIENT)) { extUse = &CSSMOID_ClientAuth; } /* search for that one or for "any" indicator */ for(unsigned i=0; inumPurposes; i++) { if(tpCompareOids(&eku->purposes[i], extUse)) { foundGoodEku = true; break; } if(tpCompareOids(&eku->purposes[i], &CSSMOID_ExtendedKeyUsageAny)) { foundGoodEku = true; break; } } if(!foundGoodEku) { leaf->addStatusCode(CSSMERR_APPLETP_SSL_BAD_EXT_KEY_USE); return CSSMERR_TP_VERIFY_ACTION_FAILED; } } return CSSM_OK; } /* * Verify SMIME and iChat options. * This deals with both S/MIME and iChat policies; within the iChat domain it * deals with Apple-specific .mac certs as well as what we call "generic AIM" * certs, as used in the Windows AIM client. */ #define CE_CIPHER_MASK (~(CE_KU_EncipherOnly | CE_KU_DecipherOnly)) static CSSM_RETURN tp_verifySmimeOpts( TPPolicy policy, TPCertGroup &certGroup, const CSSM_DATA *smimeFieldOpts, const iSignCertInfo &leafCertInfo) { bool iChat = (policy == kTP_iChat) ? true : false; /* * The CSSM_APPLE_TP_SMIME_OPTIONS pointer is optional as is everything in it. */ 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, a.k.a. handle for iChat policy */ unsigned emailLen = 0; if(smimeOpts != NULL) { emailLen = smimeOpts->SenderEmailLen; } bool match = false; bool emailFoundInSAN = false; bool iChatHandleFound = false; /* indicates a genuine Apple iChat cert */ bool emailFoundInDN = false; if(emailLen != 0) { if(smimeOpts->SenderEmail == NULL) { return CSSMERR_TP_INVALID_POINTER; } /* iChat - first try the Apple custom format */ if(iChat) { iChatHandleFound = tpCompareIChatHandleName(*leaf, smimeOpts->SenderEmail, emailLen); if(iChatHandleFound) { match = true; } } if(!match) { /* * normalize caller's email string * SMIME - lowercase only the portion after '@' * iChat - lowercase all of it */ char *email = (char *)certGroup.alloc().malloc(emailLen); memmove(email, smimeOpts->SenderEmail, emailLen); tpNormalizeAddrSpec(email, emailLen, iChat); /* * 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, iChat, dummy, emailFoundInSAN); /* * Then subject DN, CSSMOID_EmailAddress, if no match from * subjectAltName */ if(!match) { match = tpCompareSubjectName(*leaf, SN_Email, iChat, email, emailLen, emailFoundInDN); } certGroup.alloc().free(email); /* * Error here 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_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND; } } /* * iChat only: error if app specified email address but there was * none in the cert. */ if(iChat && !emailFoundInSAN && !emailFoundInDN && !iChatHandleFound) { leaf->addStatusCode(CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS); tpPolicyError("iChat: no email address or handle in cert"); return CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS; } } /* * 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; const iSignExtenInfo &kuInfo = leafCertInfo.keyUsage; const iSignExtenInfo &ekuInfo = leafCertInfo.extendKeyUsage; const CSSM_X509_NAME *x509Name = NULL; if(iChat) { /* empty subject name processing is S/MIME only */ goto checkEku; } 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 */ 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, false, 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.... */ if(kuInfo.present && (smimeOpts != NULL)) { 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; } } } /* * Extended Key Use verification, which is different for the two policies. */ checkEku: if(iChat && !ekuInfo.present) { /* * iChat: whether generic AIM cert or Apple .mac/iChat cert, we must have an * extended key use extension. */ tpPolicyError("iChat: No extended Key Use"); leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE); return CSSMERR_APPLETP_SMIME_BAD_KEY_USE; } if(!iChatHandleFound) { /* * S/MIME and generic AIM certs when evaluating iChat policy. * Look for either emailProtection or anyExtendedKeyUsage usages. * * S/MIME : the whole extension is optional. * iChat : extension must be there (which we've already covered, above) * and we must find one of those extensions. */ 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) { tpPolicyError("iChat/SMIME: No appropriate extended Key Use"); leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE); return CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE; } } } else { /* * Apple iChat cert. Look for anyExtendedKeyUsage, iChatSigning, * ichatEncrypting - the latter of two which can optionally be * required by app. */ assert(iChat); /* or we could not have even looked for an iChat style handle */ assert(ekuInfo.present); /* checked above */ bool foundAnyEku = false; bool foundIChatSign = false; bool foundISignEncrypt = false; CE_ExtendedKeyUsage *eku = (CE_ExtendedKeyUsage *)ekuInfo.extnData; assert(eku != NULL); for(unsigned i=0; inumPurposes; i++) { if(tpCompareOids(&eku->purposes[i], &CSSMOID_APPLE_EKU_ICHAT_SIGNING)) { foundIChatSign = true; } else if(tpCompareOids(&eku->purposes[i], &CSSMOID_APPLE_EKU_ICHAT_ENCRYPTION)) { foundISignEncrypt = true; } else if(tpCompareOids(&eku->purposes[i], &CSSMOID_ExtendedKeyUsageAny)) { foundAnyEku = true; } } if(!foundAnyEku && !foundISignEncrypt && !foundIChatSign) { /* No go - no acceptable uses found */ tpPolicyError("iChat: No valid extended Key Uses found"); leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE); return CSSMERR_APPLETP_SMIME_BAD_KEY_USE; } /* check for specifically required uses */ if((smimeOpts != NULL) && (smimeOpts->IntendedUsage != 0)) { if(smimeOpts->IntendedUsage & CE_KU_DigitalSignature) { if(!foundIChatSign) { tpPolicyError("iChat: ICHAT_SIGNING required, but missing"); leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE); return CSSMERR_APPLETP_SMIME_BAD_KEY_USE; } } if(smimeOpts->IntendedUsage & CE_KU_DataEncipherment) { if(!foundISignEncrypt) { tpPolicyError("iChat: ICHAT_ENCRYPT required, but missing"); leaf->addStatusCode(CSSMERR_APPLETP_SMIME_BAD_KEY_USE); return CSSMERR_APPLETP_SMIME_BAD_KEY_USE; } } } /* checking IntendedUsage */ } /* iChat cert format */ return CSSM_OK; } /* * Verify Apple Code Signing options. * * -- Must have one intermediate cert * -- intermediate must have basic constraints with path length 0 * -- intermediate has CSSMOID_APPLE_EKU_CODE_SIGNING EKU * -- leaf cert has either CODE_SIGNING or CODE_SIGN_DEVELOPMENT EKU (the latter of * which triggers a CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT error) */ static CSSM_RETURN tp_verifyCodeSigningOpts( TPCertGroup &certGroup, const CSSM_DATA *fieldOpts, // currently unused const iSignCertInfo *certInfo) // all certs, size certGroup.numCerts() { unsigned numCerts = certGroup.numCerts(); if(numCerts != 3) { tpPolicyError("tp_verifyCodeSigningOpts: numCerts %u", numCerts); return CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH; } /* verify intermediate cert */ const iSignCertInfo *isCertInfo = &certInfo[1]; TPCertInfo *tpCert = certGroup.certAtIndex(1); if(!isCertInfo->basicConstraints.present) { tpPolicyError("tp_verifyCodeSigningOpts: no basicConstraints in intermediate"); tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS); return CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS; } const CE_BasicConstraints *bc = &isCertInfo->basicConstraints.extnData->basicConstraints; assert(bc != NULL); /* if this extension is present, we already verified CA true in mainline code */ assert(bc->cA); if(!bc->pathLenConstraintPresent || (bc->pathLenConstraint != 0)) { tpPolicyError("tp_verifyCodeSigningOpts: bad pathLengthConstraint in intermediate"); tpCert->addStatusCode(CSSMERR_APPLETP_CS_BAD_PATH_LENGTH); return CSSMERR_APPLETP_CS_BAD_PATH_LENGTH; } /* ExtendedKeyUse required, one legal value */ if(!isCertInfo->extendKeyUsage.present) { tpPolicyError("tp_verifyCodeSigningOpts: no extendedKeyUse in intermediate"); tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE); return CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE; } CE_ExtendedKeyUsage *eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage; assert(eku != NULL); if(eku->numPurposes != 1) { tpPolicyError("tp_verifyCodeSigningOpts: bad eku->numPurposes in intermediate (%lu)", eku->numPurposes); tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); return CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE; } CSSM_RETURN crtn = CSSM_OK; if(!tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING)) { tpPolicyError("tp_verifyCodeSigningOpts: bad EKU"); tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE; } /* verify leaf cert */ isCertInfo = &certInfo[0]; tpCert = certGroup.certAtIndex(0); if(!isCertInfo->extendKeyUsage.present) { tpPolicyError("tp_verifyCodeSigningOpts: no extendedKeyUse in leaf"); tpCert->addStatusCode(CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE); return crtn ? crtn : CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE; } eku = &isCertInfo->extendKeyUsage.extnData->extendedKeyUsage; assert(eku != NULL); if(eku->numPurposes != 1) { tpPolicyError("tp_verifyCodeSigningOpts: bad eku->numPurposes (%lu)", eku->numPurposes); tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); if(crtn == CSSM_OK) { crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE; } } if(!tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING)) { if(tpCompareOids(&eku->purposes[0], &CSSMOID_APPLE_EKU_CODE_SIGNING_DEV)) { tpPolicyError("tp_verifyCodeSigningOpts: DEVELOPMENT cert"); tpCert->addStatusCode(CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT); if(crtn == CSSM_OK) { crtn = CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT; } } else { tpPolicyError("tp_verifyCodeSigningOpts: bad EKU in leaf"); tpCert->addStatusCode(CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE); if(crtn == CSSM_OK) { crtn = CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE; } } } return crtn; } /* * 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_OK 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, Allocator &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;// generic CSSMERR_TP_VERIFY_ACTION_FAILED CSSM_RETURN policyError = CSSM_OK; // policy-specific failure /* 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. */ for(certDex=0; certDexcertAtIndex(certDex); /* * First check for presence of required extensions and * critical extensions we don't understand. */ 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). * all others: 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) { default: /* * 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; } } } /* inferred a default value */ 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. * all others : 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. * all others : 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 required as noted * 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 * kCrlPolicy : Leaf: usage = CRLSign * kTP_SMIME : if present, must be critical * kTP_CodeSign : Leaf : usage = digitalSignature * all others : non-leaf : usage = keyCertSign * Leaf : don't care */ if(thisCertInfo->keyUsage.present) { /* * Leaf cert: * iSign and CodeSigning: usage = digitalSignature * all others : don't care * Others: usage = keyCertSign * We only require that one bit to be set, we ignore others. */ if(isLeaf) { switch(policy) { case kTPiSign: case kTP_CodeSign: expUsage = CE_KU_DigitalSignature; break; case kCrlPolicy: /* if present, this bit must be set */ expUsage = CE_KU_CRLSign; break; default: /* accept whatever's there */ expUsage = thisCertInfo->keyUsage.extnData->keyUsage; break; } } else { /* !leaf: 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 /* * Radar 3523221 renders this whole check obsolete, but I'm leaving * the code here document its conspicuous functional absence. */ 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); } } /* specific per-policy checking */ switch(policy) { case kTP_SSL: case kTP_EAP: case kTP_IPSec: /* * SSL, EAP, IPSec: optionally verify common name; all are identical * other than their names. * FIXME - should this be before or after the root cert test? How can * we return both errors? */ policyError = tp_verifySslOpts(*certGroup, policyFieldData, certInfo[0]); break; case kTP_iChat: tpDebug("iChat policy"); case kTP_SMIME: policyError = tp_verifySmimeOpts(policy, *certGroup, policyFieldData, certInfo[0]); break; case kTP_CodeSign: policyError = tp_verifyCodeSigningOpts(*certGroup, policyFieldData, certInfo); break; default: break; } if(outErr == CSSM_OK) { /* policy-specific error takes precedence here */ if(policyError != CSSM_OK) { outErr = policyError; } else if(policyFail) { /* plain vanilla error return from this module */ outErr = CSSMERR_TP_VERIFY_ACTION_FAILED; } } errOut: /* free resources */ for(certDex=0; certDex