#include "tpOcspCertVfy.h"
#include "tpdebugging.h"
#include "certGroupUtils.h"
#include <Security/oidscert.h>
#include <CommonCrypto/CommonDigest.h>
#include <security_ocspd/ocspdUtils.h>
#include "tpTime.h"
#ifndef NDEBUG
#include <Security/SecCertificate.h>
#endif
static bool tpIsAuthorizedOcspSigner(
TPCertInfo &issuerCert, TPCertInfo &signerCert) {
CSSM_DATA_PTR fieldValue = NULL; CSSM_RETURN crtn;
bool ourRtn = false;
CE_ExtendedKeyUsage *eku = NULL;
bool foundEku = false;
if(!issuerCert.isIssuerOf(signerCert)) {
#ifndef NDEBUG
SecCertificateRef issuerRef = NULL;
SecCertificateRef signerRef = NULL;
const CSSM_DATA *issuerData = issuerCert.itemData();
const CSSM_DATA *signerData = signerCert.itemData();
crtn = SecCertificateCreateFromData(issuerData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER, &issuerRef);
crtn = SecCertificateCreateFromData(signerData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER, &signerRef);
CFStringRef issuerName = SecCertificateCopySubjectSummary(issuerRef);
CFStringRef signerName = SecCertificateCopySubjectSummary(signerRef);
if(issuerName) {
CFIndex maxLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(issuerName), kCFStringEncodingUTF8) + 1;
char* buf = (char*) malloc(maxLength);
if (buf) {
if (CFStringGetCString(issuerName, buf, (CFIndex)maxLength, kCFStringEncodingUTF8)) {
tpOcspDebug("tpIsAuthorizedOcspSigner: issuerCert \"%s\"", buf);
}
free(buf);
}
CFRelease(issuerName);
}
if(signerName) {
CFIndex maxLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(signerName), kCFStringEncodingUTF8) + 1;
char* buf = (char*) malloc(maxLength);
if (buf) {
if (CFStringGetCString(signerName, buf, (CFIndex)maxLength, kCFStringEncodingUTF8)) {
tpOcspDebug("tpIsAuthorizedOcspSigner: signerCert \"%s\"", buf);
}
free(buf);
}
CFRelease(signerName);
}
if(issuerRef) {
CFRelease(issuerRef);
}
if(signerRef) {
CFRelease(signerRef);
}
#endif
tpOcspDebug("tpIsAuthorizedOcspSigner: signer is not issued by issuerCert");
return false;
}
crtn = signerCert.fetchField(&CSSMOID_ExtendedKeyUsage, &fieldValue);
if(crtn) {
tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no EKU");
return false;
}
CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
if(cssmExt->format != CSSM_X509_DATAFORMAT_PARSED) {
tpOcspDebug("tpIsAuthorizedOcspSigner: bad extension format");
goto errOut;
}
eku = (CE_ExtendedKeyUsage *)cssmExt->value.parsedValue;
for(unsigned dex=0; dex<eku->numPurposes; dex++) {
if(tpCompareCssmData(&eku->purposes[dex], &CSSMOID_OCSPSigning)) {
foundEku = true;
break;
}
}
if(!foundEku) {
tpOcspDebug("tpIsAuthorizedOcspSigner: signer is issued by issuer, no OCSP "
"signing EKU");
goto errOut;
}
crtn = signerCert.verifyWithIssuer(&issuerCert, NULL);
if(crtn == CSSM_OK) {
tpOcspDebug("tpIsAuthorizedOcspSigner: FOUND authorized signer");
ourRtn = true;
}
else {
tpOcspDebug("tpIsAuthorizedOcspSigner: signer sig verify FAIL");
}
errOut:
if(fieldValue != NULL) {
signerCert.freeField(&CSSMOID_ExtendedKeyUsage, fieldValue);
}
return ourRtn;
}
static
bool tpOcspResponderIDCheck(
OCSPResponse &ocspResp,
TPCertInfo &signer)
{
bool shouldBeSigner = false;
if(ocspResp.responderIDTag() == RIT_Name) {
const CSSM_DATA *respIdName = ocspResp.encResponderName();
CSSM_DATA *subjectName = NULL;
CSSM_RETURN crtn = signer.fetchField(&CSSMOID_X509V1SubjectNameStd,
&subjectName);
if(crtn) {
tpOcspDebug("tpOcspResponderIDCheck: error on fetchField(subjectName");
return false;
}
if(tpCompareCssmData(respIdName, subjectName)) {
tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byName");
shouldBeSigner = true;
}
else {
tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byName");
}
signer.freeField(&CSSMOID_X509V1SubjectNameStd, subjectName);
}
else {
const CSSM_KEY_PTR pubKey = signer.pubKey();
assert(pubKey != NULL);
uint8 digest[CC_SHA1_DIGEST_LENGTH];
CSSM_DATA keyHash = {CC_SHA1_DIGEST_LENGTH, digest};
CSSM_DATA pubKeyBytes = {0, NULL};
ocspdGetPublicKeyBytes(NULL, pubKey, pubKeyBytes);
ocspdSha1(pubKeyBytes.Data, (CC_LONG)pubKeyBytes.Length, digest);
const CSSM_DATA *respKeyHash = &ocspResp.responderID().byKey;
if(tpCompareCssmData(&keyHash, respKeyHash)) {
tpOcspDebug("tpOcspResponderIDCheck: good ResponderID.byKey");
shouldBeSigner = true;
}
else {
tpOcspDebug("tpOcspResponderIDCheck: BAD ResponderID.byKey");
}
}
return shouldBeSigner;
}
static bool tpOcspResponseSigVerify(
TPVerifyContext &vfyCtx,
OCSPResponse &ocspResp, TPCertInfo &signer)
{
const SecAsn1OCSPBasicResponse &basicResp = ocspResp.basicResponse();
const CSSM_OID *algOid = &basicResp.algId.algorithm;
CSSM_ALGORITHMS sigAlg;
if(!cssmOidToAlg(algOid, &sigAlg)) {
tpOcspDebug("tpOcspResponseSigVerify: unknown signature algorithm");
}
const CSSM_KEY *pubKey = signer.pubKey();
CSSM_DATA sig = basicResp.sig;
sig.Length /= 8;
CSSM_RETURN crtn;
CSSM_CC_HANDLE sigHand;
bool ourRtn = false;
crtn = CSSM_CSP_CreateSignatureContext(vfyCtx.cspHand, sigAlg, NULL,
pubKey, &sigHand);
if(crtn) {
#ifndef NDEBUG
cssmPerror("tpOcspResponseSigVerify, CSSM_CSP_CreateSignatureContext", crtn);
#endif
return false;
}
crtn = CSSM_VerifyData(sigHand, &basicResp.tbsResponseData, 1,
CSSM_ALGID_NONE, &sig);
if(crtn) {
#ifndef NDEBUG
cssmPerror("tpOcspResponseSigVerify, CSSM_VerifyData", crtn);
#endif
}
else {
ourRtn = true;
}
CSSM_DeleteContext(sigHand);
return ourRtn;
}
typedef enum {
OIS_No, OIS_Good, OIS_BadSig, } OcspIssuerStatus;
typedef enum {
OCT_Local, OCT_Issuer, OCT_Provided, } OcspCertType;
static OcspIssuerStatus tpIsOcspIssuer(
TPVerifyContext &vfyCtx,
OCSPResponse &ocspResp,
const CSSM_DATA *signerData,
TPCertInfo *signer,
OcspCertType certType, TPCertInfo *issuer, TPCertInfo **signerRtn) {
assert((signerData != NULL) || (signer != NULL));
TPCertInfo *tmpSigner = NULL;
if(signer == NULL) {
try {
tmpSigner = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, signerData,
TIC_CopyData, vfyCtx.verifyTime);
}
catch(...) {
tpOcspDebug("tpIsOcspIssuer: bad cert");
return OIS_No;
}
signer = tmpSigner;
}
if(signer == NULL) {
return OIS_No;
}
if(signerRtn != NULL) {
*signerRtn = signer;
}
bool shouldBeSigner = false;
OcspIssuerStatus ourRtn = OIS_No;
switch(certType) {
case OCT_Local: shouldBeSigner = true;
break;
case OCT_Issuer:
shouldBeSigner = tpOcspResponderIDCheck(ocspResp, *signer);
break;
case OCT_Provided:
{
if(issuer == NULL) {
break;
}
shouldBeSigner = tpIsAuthorizedOcspSigner(*issuer, *signer);
break;
}
}
if(!shouldBeSigner) {
goto errOut;
}
if(tpOcspResponseSigVerify(vfyCtx, ocspResp, *signer)) {
ourRtn = OIS_Good;
}
errOut:
if((signerRtn == NULL) && (tmpSigner != NULL)) {
delete tmpSigner;
}
return ourRtn;
}
OcspRespStatus tpVerifyOcspResp(
TPVerifyContext &vfyCtx,
SecNssCoder &coder,
TPCertInfo *issuer, OCSPResponse &ocspResp,
CSSM_RETURN &cssmErr) {
OcspRespStatus ourRtn = ORS_Unknown;
CSSM_RETURN crtn;
char *ocspResp_genTime = NULL;
char *ocspResp_cssmTime = NULL;
CSSM_TIMESTRING cssmTimeStr = NULL;
tpOcspDebug("tpVerifyOcspResp top");
switch(ocspResp.responseStatus()) {
case RS_Success:
crtn = CSSM_OK;
break;
case RS_MalformedRequest:
crtn = CSSMERR_APPLETP_OCSP_RESP_MALFORMED_REQ;
break;
case RS_InternalError:
crtn = CSSMERR_APPLETP_OCSP_RESP_INTERNAL_ERR;
break;
case RS_TryLater:
crtn = CSSMERR_APPLETP_OCSP_RESP_TRY_LATER;
break;
case RS_SigRequired:
crtn = CSSMERR_APPLETP_OCSP_RESP_SIG_REQUIRED;
break;
case RS_Unauthorized:
crtn = CSSMERR_APPLETP_OCSP_RESP_UNAUTHORIZED;
break;
default:
crtn = CSSMERR_APPLETP_OCSP_BAD_RESPONSE;
break;
}
if(crtn) {
tpOcspDebug("tpVerifyOcspResp aborting due to response status %d",
(int)(ocspResp.responseStatus()));
cssmErr = crtn;
return ORS_Unknown;
}
cssmErr = CSSM_OK;
TPCertInfo *signerInfo = NULL;
TPCertInfo *signerInfoTBD = NULL;
TPCertGroup ocspCerts(vfyCtx.alloc, TGO_Caller);
CSSM_BOOL verifiedToRoot;
CSSM_BOOL verifiedToAnchor;
CSSM_BOOL verifiedViaTrustSetting;
const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts = vfyCtx.ocspOpts;
OcspIssuerStatus issuerStat;
ocspResp_cssmTime = (char *)malloc(CSSM_TIME_STRLEN + 1);
ocspResp_genTime = (char *)malloc(GENERAL_TIME_STRLEN + 1);
if (ocspResp_cssmTime && ocspResp_genTime) {
cssmTimeStr = vfyCtx.verifyTime; cfAbsTimeToGgenTime(ocspResp.producedAt(), ocspResp_genTime);
tpTimeToCssmTimestring(ocspResp_genTime,GENERAL_TIME_STRLEN,ocspResp_cssmTime);
vfyCtx.verifyTime = ocspResp_cssmTime;
}
bool foundBadIssuer = false;
bool foundLocalResponder = false;
uint32 numSignerCerts = ocspResp.numSignerCerts();
assert(vfyCtx.signerCerts != NULL);
TPCertGroup &gatheredCerts = vfyCtx.gatheredCerts;
TPCertGroup certsToBeFreed(vfyCtx.alloc, TGO_Group);
if((ocspOpts != NULL) && (ocspOpts->LocalResponderCert != NULL)) {
TPCertInfo *responderInfo = NULL;
issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
ocspOpts->LocalResponderCert, NULL,
OCT_Local, issuer, &responderInfo);
switch(issuerStat) {
case OIS_BadSig:
foundBadIssuer = true;
case OIS_No:
if(responderInfo != NULL) {
delete responderInfo;
}
break;
case OIS_Good:
assert(responderInfo != NULL);
signerInfo = signerInfoTBD = responderInfo;
foundLocalResponder = true;
tpOcspDebug("tpVerifyOcspResp: signer := LocalResponderCert");
break;
}
}
if((signerInfo == NULL) && (numSignerCerts != 0)) {
TPCertInfo *respCert = NULL;
for(unsigned dex=0; dex<numSignerCerts; dex++) {
const CSSM_DATA *certData = ocspResp.signerCert(dex);
if(signerInfo == NULL) {
issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
certData, NULL,
OCT_Provided, issuer, &respCert);
switch(issuerStat) {
case OIS_No:
break;
case OIS_Good:
assert(respCert != NULL);
signerInfo = signerInfoTBD = respCert;
tpOcspDebug("tpVerifyOcspResp: signer := signerCert[%u]", dex);
break;
case OIS_BadSig:
foundBadIssuer = true;
break;
}
}
else {
try {
respCert = new TPCertInfo(vfyCtx.clHand, vfyCtx.cspHand, certData,
TIC_CopyData, vfyCtx.verifyTime);
}
catch(...) {
tpOcspDebug("tpVerifyOcspResp: BAD signerCert[%u]", dex);
}
}
if((respCert != NULL) && (respCert != signerInfo)) {
gatheredCerts.appendCert(respCert);
}
}
}
if((signerInfo == NULL) && (issuer != NULL)) {
issuerStat = tpIsOcspIssuer(vfyCtx, ocspResp,
NULL, issuer,
OCT_Issuer, issuer, NULL);
switch(issuerStat) {
case OIS_BadSig:
ourRtn = ORS_Unknown;
cssmErr = CSSMERR_APPLETP_OCSP_SIG_ERROR;
goto errOut;
case OIS_No:
break;
case OIS_Good:
signerInfo = issuer;
tpOcspDebug("tpVerifyOcspResp: signer := issuer");
break;
}
}
if(signerInfo == NULL) {
if((issuer != NULL) && !issuer->isStatusFatal(CSSMERR_APPLETP_OCSP_NO_SIGNER)) {
tpOcspDebug("tpVerifyOcspResp: no signer found, user allows!");
ourRtn = ORS_Good;
}
else {
tpOcspDebug("tpVerifyOcspResp: no signer found");
ourRtn = ORS_Unknown;
cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
}
goto errOut;
}
if(signerInfo != NULL && !foundLocalResponder) {
if (signerInfo->isExpired() || signerInfo->isNotValidYet()) {
tpOcspDebug("tpVerifyOcspResp ocspSigner expired or not yet valid");
ourRtn = ORS_Unknown;
cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
goto errOut;
}
else {
tpOcspDebug("tpVerifyOcspResp SUCCESS");
ourRtn = ORS_Good;
goto errOut;
}
}
gatheredCerts.setAllUnused();
vfyCtx.signerCerts->setAllUnused();
crtn = ocspCerts.buildCertGroup(
*signerInfo, vfyCtx.signerCerts, vfyCtx.dbList, vfyCtx.clHand,
vfyCtx.cspHand,
vfyCtx.verifyTime,
vfyCtx.numAnchorCerts,
vfyCtx.anchorCerts,
certsToBeFreed, &gatheredCerts, CSSM_FALSE, vfyCtx.actionFlags,
vfyCtx.policyOid,
vfyCtx.policyStr,
vfyCtx.policyStrLen,
kSecTrustSettingsKeyUseSignRevocation,
verifiedToRoot,
verifiedToAnchor,
verifiedViaTrustSetting);
if(crtn) {
tpOcspDebug("tpVerifyOcspResp buildCertGroup failure");
cssmErr = crtn;
ourRtn = ORS_Unknown;
goto errOut;
}
crtn = ocspCerts.getReturnCode(crtn, NULL, vfyCtx.actionFlags);
if ((crtn == CSSMERR_TP_CERT_EXPIRED) || (crtn == CSSMERR_TP_CERT_NOT_VALID_YET)) {
tpOcspDebug("tpVerifyOcspResp ocspSigner expired or not yet valid");
ourRtn = ORS_Unknown;
cssmErr = CSSMERR_APPLETP_OCSP_NO_SIGNER;
goto errOut;
}
if(!verifiedToAnchor && !verifiedViaTrustSetting) {
ourRtn = ORS_Unknown;
if(verifiedToRoot) {
tpOcspDebug("tpVerifyOcspResp root, no anchor");
cssmErr = CSSMERR_APPLETP_OCSP_INVALID_ANCHOR_CERT;
}
else {
tpOcspDebug("tpVerifyOcspResp no root, no anchor");
cssmErr = CSSMERR_APPLETP_OCSP_NOT_TRUSTED;
}
if((issuer != NULL) && !issuer->isStatusFatal(cssmErr)) {
tpOcspDebug("...ignoring last error per trust setting");
ourRtn = ORS_Good;
}
else {
ourRtn = ORS_Unknown;
}
}
else {
tpOcspDebug("tpVerifyOcspResp SUCCESS; chain verified");
ourRtn = ORS_Good;
}
errOut:
delete signerInfoTBD;
if (ocspResp_genTime && ocspResp_cssmTime) vfyCtx.verifyTime = cssmTimeStr;
if (ocspResp_genTime) free(ocspResp_genTime);
if (ocspResp_cssmTime) free(ocspResp_cssmTime);
return ourRtn;
}