#include <Security/cssmtype.h>
#include <Security/cssmapi.h>
#include "tpPolicies.h"
#include <Security/oidsattr.h>
#include <Security/cssmerr.h>
#include "tpdebugging.h"
#include "rootCerts.h"
#include "certGroupUtils.h"
#include <Security/x509defs.h>
#include <Security/oidscert.h>
#include <Security/certextensions.h>
#include <Security/cssmapple.h>
#include <string.h>
typedef struct {
CSSM_BOOL present;
CSSM_BOOL critical;
CE_Data *extnData; CSSM_DATA *valToFree; } iSignExtenInfo;
typedef struct {
iSignExtenInfo authorityId;
iSignExtenInfo subjectId;
iSignExtenInfo keyUsage;
iSignExtenInfo extendKeyUsage;
iSignExtenInfo basicConstraints;
iSignExtenInfo netscapeCertType;
CSSM_BOOL foundUnknownCritical;
} iSignCertInfo;
static CSSM_RETURN tpSetupExtension(
CssmAllocator &alloc,
CSSM_DATA *extnData,
iSignExtenInfo *extnInfo) {
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;
}
static CSSM_RETURN iSignFetchExtension(
CssmAllocator &alloc,
TPCertInfo *tpCert,
const CSSM_OID *fieldOid, iSignExtenInfo *extnInfo) {
CSSM_DATA_PTR fieldValue; CSSM_RETURN crtn;
crtn = tpCert->fetchField(fieldOid, &fieldValue);
switch(crtn) {
case CSSM_OK:
break;
case CSSMERR_CL_NO_FIELD_VALUES:
return CSSM_OK;
default:
return crtn;
}
return tpSetupExtension(alloc,
fieldValue,
extnInfo);
}
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:
break;
case CSSMERR_CL_NO_FIELD_VALUES:
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) {
certInfo->foundUnknownCritical = CSSM_TRUE;
goto fini;
}
CSSM_CL_FreeFieldValue(tpCert->clHand(),
&CSSMOID_X509V3CertificateExtensionCStruct,
fieldValue);
fieldValue = NULL;
for(unsigned i=1; i<numFields; i++) {
crtn = CSSM_CL_CertGetNextCachedFieldValue(tpCert->clHand(),
searchHand,
&fieldValue);
if(crtn) {
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) {
certInfo->foundUnknownCritical = CSSM_TRUE;
break;
}
CSSM_CL_FreeFieldValue(tpCert->clHand(),
&CSSMOID_X509V3CertificateExtensionCStruct,
fieldValue);
fieldValue = NULL;
}
fini:
if(fieldValue) {
CSSM_CL_FreeFieldValue(tpCert->clHand(),
&CSSMOID_X509V3CertificateExtensionCStruct,
fieldValue);
}
if(searchHand != CSSM_INVALID_HANDLE) {
CSSM_CL_CertAbortQuery(tpCert->clHand(), searchHand);
}
return crtn;
}
static CSSM_RETURN iSignGetCertInfo(
CssmAllocator &alloc,
TPCertInfo *tpCert,
iSignCertInfo *certInfo)
{
CSSM_RETURN crtn;
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;
}
return iSignSearchUnknownExtensions(tpCert, certInfo);
}
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);
}
}
static CSSM_BOOL tp_isKnownRootCert(
TPCertInfo *rootCert, 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;
}
for(dex=0; dex<numKnownRoots; dex++) {
if(!tpCompareCssmData(subjectName,
knownRoots[dex].subjectName)) {
continue;
}
if(!tpCompareCssmData(publicKey,
knownRoots[dex].publicKey)) {
continue;
}
#if ENABLE_APPLE_DEBUG_ROOT
if( dex == (knownRoots - 1) ){
brtn = CSSM_FALSE;
break;
}
#endif
brtn = CSSM_TRUE;
break;
}
errOut:
tp_CertFreePublicKey(rootCert->clHand(), valToFree);
return brtn;
}
static CSSM_BOOL tp_isIsignRootCert(
CSSM_CL_HANDLE clHand,
TPCertInfo *rootCert) {
return tp_isKnownRootCert(rootCert, iSignRootCerts, numiSignRootCerts);
}
static CSSM_BOOL tp_isSslRootCert(
CSSM_CL_HANDLE clHand,
TPCertInfo *rootCert) {
return tp_isKnownRootCert(rootCert, sslRootCerts, numSslRootCerts);
}
CSSM_BOOL tp_verifyWithSslRoots(
CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
TPCertInfo *certToVfy) {
CSSM_KEY rootKey; CSSM_CC_HANDLE ccHand; 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;
memset(&rootKey, 0, sizeof(CSSM_KEY));
algId = tp_CertGetAlgId(certToVfy, &valToFree);
if(algId == NULL) {
return CSSM_FALSE;
}
sigAlg = tpOidToAldId(&algId->algorithm, &hdr->AlgorithmId);
if(sigAlg == CSSM_ALGID_NONE) {
errorLog0("tp_verifyWithSslRoots: unknown sig alg\n");
goto errOut;
}
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:
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;
for(dex=0; dex<numSslRootCerts; dex++) {
rootInfo = &sslRootCerts[dex];
if(!tpIsSameName(rootInfo->subjectName, certToVfy->issuerName())) {
continue;
}
rootKey.KeyData = *rootInfo->publicKey;
hdr->LogicalKeySizeInBits = rootInfo->keySize;
crtn = CSSM_CSP_CreateSignatureContext(cspHand,
sigAlg,
NULL, &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, NULL, 0); CSSM_DeleteContext(ccHand);
if(crtn == CSSM_OK) {
brtn = CSSM_TRUE;
break;
}
}
errOut:
if(valToFree != NULL) {
tp_CertFreeAlgId(clHand, valToFree);
}
return brtn;
}
#define BASIC_CONSTRAINTS_MUST_BE_CRITICAL 0
#define EXTENDED_KEY_USAGE_REQUIRED_FOR_LEAF 0
#define SUBJECT_ALT_NAME_REQUIRED_FOR_LEAF 0
#define KEY_USAGE_REQUIRED_FOR_ROOT 0
CSSM_RETURN tp_policyVerify(
TPPolicy policy,
CssmAllocator &alloc,
CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
TPCertGroup *certGroup,
CSSM_BOOL verifiedToRoot) {
iSignCertInfo *certInfo = NULL;
uint32 numCerts;
iSignCertInfo *thisCertInfo;
uint16 expUsage;
uint16 actUsage;
unsigned certDex;
CSSM_BOOL cA = CSSM_FALSE; CSSM_BOOL isLeaf; CSSM_BOOL isRoot; CE_ExtendedKeyUsage *extendUsage;
CE_AuthorityKeyID *authorityId;
CSSM_RETURN outErr = CSSM_OK;
TPCertInfo *lastCert;
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) {
return CSSMERR_TP_INVALID_CERTGROUP;
}
if(numCerts <= 1) {
return CSSMERR_TP_INVALID_CERTGROUP;
}
}
certInfo = (iSignCertInfo *)tpCalloc(alloc, numCerts, sizeof(iSignCertInfo));
for(certDex=0; certDex<numCerts; certDex++) {
if(iSignGetCertInfo(alloc,
certGroup->certAtIndex(certDex),
&certInfo[certDex])) {
outErr = CSSMERR_TP_INVALID_CERTIFICATE;
goto errOut;
}
}
for(certDex=0; certDex<numCerts; certDex++) {
thisCertInfo = &certInfo[certDex];
if(thisCertInfo->foundUnknownCritical) {
errorLog0("tp_policyVerify: critical flag in unknown extension\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
isLeaf = (certDex == 0) ? CSSM_TRUE : CSSM_FALSE;
isRoot = (certDex == (numCerts - 1)) ? CSSM_TRUE : CSSM_FALSE;
if(!thisCertInfo->basicConstraints.present) {
if(isLeaf) {
cA = CSSM_FALSE;
}
else if(isRoot) {
cA = CSSM_TRUE;
}
else {
switch(policy) {
case kTPx509Basic:
case kTP_SSL:
cA = CSSM_TRUE;
break;
case kTPiSign:
errorLog0("tp_policyVerify: no basicConstraints\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
default:
break;
}
}
}
else {
#if BASIC_CONSTRAINTS_MUST_BE_CRITICAL
if(!thisCertInfo->basicConstraints.critical) {
errorLog0("tp_policyVerify: basicConstraints marked not critical\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
#endif
cA = thisCertInfo->basicConstraints.extnData->basicConstraints.cA;
}
if(isLeaf) {
if(cA && !isRoot) {
errorLog0("tp_policyVerify: cA true for leaf\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
} else if(!cA) {
errorLog0("tp_policyVerify: cA false for non-leaf\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
if((policy == kTPiSign) && thisCertInfo->authorityId.present) {
if(isRoot) {
errorLog0("tp_policyVerify: authorityId in root\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
if(thisCertInfo->authorityId.critical) {
errorLog0("tp_policyVerify: authorityId marked critical\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
if(thisCertInfo->subjectId.present) {
if((policy == kTPiSign) && thisCertInfo->subjectId.critical) {
errorLog0("tp_policyVerify: subjectId marked critical\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
if(thisCertInfo->keyUsage.present) {
if(isLeaf) {
if(policy == kTPiSign) {
expUsage = CE_KU_DigitalSignature;
}
else {
expUsage = thisCertInfo->keyUsage.extnData->keyUsage;
}
}
else {
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);
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
else if(policy == kTPiSign) {
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");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
else if(!isRoot) {
errorLog0("tp_policyVerify: !isRoot, no keyUsage, !(leaf and netscapeCertType)\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
}
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);
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
if(!tpCompareOids(extendUsage->purposes,
&CSSMOID_ExtendedUseCodeSigning)) {
errorLog0("tp_policyVerify: bad extendKeyUsage\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
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) {
continue;
}
if(!tpCompareCssmData(&authorityId->keyIdentifier,
&certInfo[certDex+1].subjectId.extnData->subjectKeyID)) {
errorLog0("tp_policyVerify: bad key ID linkage\n");
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
goto errOut;
}
}
lastCert = certGroup->lastCert();
if(policy == kTPiSign) {
bool brtn = tp_isIsignRootCert(clHand, lastCert);
if(!brtn) {
outErr = CSSMERR_TP_VERIFY_ACTION_FAILED;
}
}
else if(verifiedToRoot && (policy == kTP_SSL)) {
bool brtn = tp_isSslRootCert(clHand, lastCert);
if(!brtn) {
outErr = CSSMERR_TP_INVALID_ANCHOR_CERT;
}
}
else {
outErr = CSSM_OK;
}
errOut:
for(certDex=0; certDex<numCerts; certDex++) {
thisCertInfo = &certInfo[certDex];
iSignFreeCertInfo(clHand, thisCertInfo);
}
tpFree(alloc, certInfo);
return outErr;
}