#include "TPNetwork.h"
#include "tpdebugging.h"
#include "tpTime.h"
#include "cuEnc64.h"
#include <Security/cssmtype.h>
#include <Security/cssmapple.h>
#include <Security/oidscert.h>
#include <security_utilities/logging.h>
#include <security_ocspd/ocspdClient.h>
#define CA_ISSUERS_OID OID_PKIX, 0x30, 0x02
#define CA_ISSUERS_OID_LEN OID_PKIX_LENGTH + 2
static const uint8 OID_CA_ISSUERS[] = {CA_ISSUERS_OID};
const CSSM_OID CSSMOID_CA_ISSUERS = {CA_ISSUERS_OID_LEN, (uint8 *)OID_CA_ISSUERS};
typedef enum {
LT_Crl = 1,
LT_Cert
} LF_Type;
static CSSM_RETURN tpDecodeCert(
Allocator &alloc,
CSSM_DATA &rtnBlob) {
const unsigned char *inbuf = (const unsigned char *)rtnBlob.Data;
unsigned inlen = (unsigned)rtnBlob.Length;
unsigned char *outbuf = NULL;
unsigned outlen = 0;
CSSM_RETURN ortn = cuConvertPem(inbuf, inlen, &outbuf, &outlen);
if(ortn == 0 && outbuf != NULL) {
unsigned char *rtnP = (unsigned char *) alloc.malloc(outlen);
if(rtnP != NULL) {
memcpy(rtnP, outbuf, outlen);
rtnBlob.Data = rtnP;
rtnBlob.Length = outlen;
}
free(outbuf);
alloc.free((void *)inbuf);
}
return ortn;
}
static CSSM_RETURN tpFetchViaNet(
const CSSM_DATA &url,
const CSSM_DATA *issuer, LF_Type lfType,
CSSM_TIMESTRING verifyTime, Allocator &alloc,
CSSM_DATA &rtnBlob) {
if(lfType == LT_Crl) {
return ocspdCRLFetch(alloc, url, issuer,
true, true, verifyTime, rtnBlob);
}
else {
CSSM_RETURN result = ocspdCertFetch(alloc, url, rtnBlob);
if(result == CSSM_OK) {
(void)tpDecodeCert(alloc, rtnBlob);
}
return result;
}
}
static CSSM_RETURN tpCrlViaNet(
const CSSM_DATA &url,
const CSSM_DATA *issuer, TPVerifyContext &vfyCtx,
TPCertInfo &forCert, TPCrlInfo *&rtnCrl)
{
TPCrlInfo *crl = NULL;
CSSM_DATA crlData;
CSSM_RETURN crtn;
Allocator &alloc = Allocator::standard();
char cssmTime[CSSM_TIME_STRLEN+1];
rtnCrl = NULL;
{
StLock<Mutex> _(tpTimeLock());
timeAtNowPlus(0, TIME_CSSM, cssmTime);
}
crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
if(crtn) {
return crtn;
}
try {
crl = new TPCrlInfo(vfyCtx.clHand,
vfyCtx.cspHand,
&crlData,
TIC_CopyData,
NULL); }
catch(...) {
alloc.free(crlData.Data);
tpDebug(" bad CRL; flushing from cache and retrying");
ocspdCRLFlush(url);
crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData);
if(crtn == CSSM_OK) {
try {
crl = new TPCrlInfo(vfyCtx.clHand,
vfyCtx.cspHand,
&crlData,
TIC_CopyData,
NULL);
tpDebug(" RECOVERY: good CRL obtained from net");
}
catch(...) {
alloc.free(crlData.Data);
tpDebug(" bad CRL; recovery FAILED (1)");
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
}
else {
tpDebug(" bad CRL; recovery FAILED (2)");
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
}
alloc.free(crlData.Data);
crtn = crl->verifyWithContextNow(vfyCtx, &forCert);
if(crtn == CSSM_OK) {
crl->uri(url);
}
else {
delete crl;
crl = NULL;
}
rtnCrl = crl;
return crtn;
}
static CSSM_RETURN tpIssuerCertViaNet(
const CSSM_DATA &url,
CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
const char *verifyTime,
TPCertInfo &subject,
TPCertInfo *&rtnCert)
{
TPCertInfo *issuer = NULL;
CSSM_DATA certData;
CSSM_RETURN crtn;
Allocator &alloc = Allocator::standard();
crtn = tpFetchViaNet(url, NULL, LT_Cert, NULL, alloc, certData);
if(crtn) {
tpErrorLog("tpIssuerCertViaNet: net fetch failed\n");
return CSSMERR_APPLETP_CERT_NOT_FOUND_FROM_ISSUER;
}
try {
issuer = new TPCertInfo(clHand,
cspHand,
&certData,
TIC_CopyData,
verifyTime);
}
catch(...) {
tpErrorLog("tpIssuerCertViaNet: bad cert via net fetch\n");
alloc.free(certData.Data);
rtnCert = NULL;
return CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
}
alloc.free(certData.Data);
if(!issuer->isIssuerOf(subject)) {
tpErrorLog("tpIssuerCertViaNet: wrong issuer cert via net fetch\n");
crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
}
else {
crtn = subject.verifyWithIssuer(issuer);
if(crtn) {
tpErrorLog("tpIssuerCertViaNet: sig verify fail for cert via net "
"fetch\n");
crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER;
}
}
if(crtn) {
assert(issuer != NULL);
delete issuer;
issuer = NULL;
}
rtnCert = issuer;
return crtn;
}
static CSSM_RETURN tpFetchViaGeneralNames(
const CE_GeneralNames *names,
TPCertInfo &forCert,
const CSSM_DATA *issuer, TPVerifyContext *verifyContext, CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand, const char *verifyTime,
TPCertInfo **certInfo,
TPCrlInfo **crlInfo)
{
assert(certInfo || crlInfo);
assert(!certInfo || !crlInfo);
CSSM_RETURN crtn;
for(unsigned nameDex=0; nameDex<names->numNames; nameDex++) {
CE_GeneralName *name = &names->generalName[nameDex];
switch(name->nameType) {
case GNT_URI:
if(name->name.Length < 5) {
continue;
}
if(strncmp((char *)name->name.Data, "ldap:", 5) &&
strncmp((char *)name->name.Data, "http:", 5) &&
strncmp((char *)name->name.Data, "https:", 6)) {
continue;
}
if(certInfo) {
tpDebug(" fetching cert via net");
crtn = tpIssuerCertViaNet(name->name,
clHand,
cspHand,
verifyTime,
forCert,
*certInfo);
}
else {
tpDebug(" fetching CRL via net");
assert(verifyContext != NULL);
crtn = tpCrlViaNet(name->name,
issuer,
*verifyContext,
forCert,
*crlInfo);
}
switch(crtn) {
case CSSM_OK:
case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: return crtn;
default:
break;
}
break;
default:
tpCrlDebug(" tpFetchCrlFromNet: unknown"
"nameType (%u)", (unsigned)name->nameType);
break;
}
}
if(certInfo) {
return CSSMERR_TP_CERTGROUP_INCOMPLETE;
}
else {
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
}
CSSM_RETURN tpFetchCrlFromNet(
TPCertInfo &cert,
TPVerifyContext &vfyCtx,
TPCrlInfo *&crl) {
CSSM_DATA_PTR fieldValue;
CSSM_RETURN crtn = cert.fetchField(&CSSMOID_CrlDistributionPoints,
&fieldValue);
switch(crtn) {
case CSSM_OK:
break;
case CSSMERR_CL_NO_FIELD_VALUES:
return CSSMERR_APPLETP_CRL_NOT_FOUND;
default:
return crtn;
}
if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
tpErrorLog("tpFetchCrlFromNet: malformed CSSM_FIELD");
return CSSMERR_TP_UNKNOWN_FORMAT;
}
CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
CE_CRLDistPointsSyntax *dps =
(CE_CRLDistPointsSyntax *)cssmExt->value.parsedValue;
TPCrlInfo *rtnCrl = NULL;
crtn = CSSMERR_APPLETP_CRL_NOT_FOUND;
for(unsigned dex=0; dex<dps->numDistPoints; dex++) {
CE_CRLDistributionPoint *dp = &dps->distPoints[dex];
if(dp->distPointName == NULL) {
continue;
}
switch(dp->distPointName->nameType) {
case CE_CDNT_NameRelativeToCrlIssuer:
tpErrorLog("tpFetchCrlFromNet: "
"CE_CDNT_NameRelativeToCrlIssuer not implemented\n");
break;
case CE_CDNT_FullName:
{
CE_GeneralNames *names = dp->distPointName->dpn.fullName;
crtn = tpFetchViaGeneralNames(names,
cert,
cert.issuerName(),
&vfyCtx,
0, 0, vfyCtx.verifyTime,
NULL,
&rtnCrl);
break;
}
default:
tpErrorLog("tpFetchCrlFromNet: "
"unknown distPointName->nameType (%u)\n",
(unsigned)dp->distPointName->nameType);
break;
}
if(crtn == CSSM_OK) {
break;
}
}
cert.freeField(&CSSMOID_CrlDistributionPoints, fieldValue);
if(crtn == CSSM_OK) {
assert(rtnCrl != NULL);
crl = rtnCrl;
}
return crtn;
}
CSSM_RETURN tpFetchIssuerFromNet(
TPCertInfo &subject,
CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
const char *verifyTime,
TPCertInfo *&issuer) {
CSSM_OID_PTR fieldOid = NULL;
CSSM_DATA_PTR fieldValue = NULL; CSSM_RETURN crtn;
bool hasAIA = false;
fieldOid = (CSSM_OID_PTR)&CSSMOID_AuthorityInfoAccess;
crtn = subject.fetchField(fieldOid,
&fieldValue);
hasAIA = (crtn == CSSM_OK);
if (!hasAIA) {
fieldOid = (CSSM_OID_PTR)&CSSMOID_IssuerAltName;
crtn = subject.fetchField(fieldOid,
&fieldValue);
}
switch(crtn) {
case CSSM_OK:
break;
case CSSMERR_CL_NO_FIELD_VALUES:
return CSSMERR_TP_CERTGROUP_INCOMPLETE;
default:
return crtn;
}
if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) {
tpPolicyError("tpFetchIssuerFromNet: malformed CSSM_FIELD");
return CSSMERR_TP_UNKNOWN_FORMAT;
}
CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data;
CE_GeneralNames *names = (CE_GeneralNames *)cssmExt->value.parsedValue;
TPCertInfo *rtnCert = NULL;
if (hasAIA) {
CE_AuthorityInfoAccess *access = (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue;
for (uint32 index = 0; access && index < access->numAccessDescriptions; index++) {
CE_AccessDescription *accessDesc = &access->accessDescriptions[index];
CSSM_OID_PTR methodOid = (CSSM_OID_PTR)&accessDesc->accessMethod;
if(methodOid->Data != NULL && methodOid->Length == CSSMOID_CA_ISSUERS.Length &&
!memcmp(methodOid->Data, CSSMOID_CA_ISSUERS.Data, methodOid->Length)) {
CE_GeneralNames aiaNames = { 1, &accessDesc->accessLocation };
crtn = tpFetchViaGeneralNames(&aiaNames,
subject,
NULL, NULL, clHand,
cspHand,
verifyTime,
&rtnCert,
NULL);
if (crtn == CSSM_OK ||
crtn == CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE) {
break; }
}
}
subject.freeField(fieldOid, fieldValue);
}
else {
crtn = tpFetchViaGeneralNames(names,
subject,
NULL, NULL, clHand,
cspHand,
verifyTime,
&rtnCert,
NULL);
subject.freeField(fieldOid, fieldValue);
}
switch(crtn) {
case CSSM_OK:
case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
issuer = rtnCert;
break;
default:
break;
}
return crtn;
}