#include "TPNetwork.h"
#include "tpdebugging.h"
#include <Security/cssmtype.h>
#include <Security/cssmapple.h>
#include <Security/oidscert.h>
#include <Security/logging.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#define CRL_FETCH_TOOL "/usr/bin/crlrefresh"
#define CRL_FETCH_ENV "TP_CRLREFRESH"
#define CRL_RBUF_SIZE 1024
typedef enum {
LT_Crl = 1,
LT_Cert
} LF_Type;
static CSSM_RETURN tpFetchViaNet(
const CSSM_DATA &url,
LF_Type lfType,
CssmAllocator &alloc,
CSSM_DATA &attrBlob) {
char *arg1;
int status;
switch(lfType) {
case LT_Crl:
arg1 = "f";
break;
case LT_Cert:
arg1 = "F";
break;
default:
return CSSMERR_TP_INTERNAL_ERROR;
}
int pipeFds[2];
status = pipe(pipeFds);
if(status) {
tpErrorLog("tpFetchViaNet: pipe error %d\n", errno);
return CSSMERR_TP_REQUEST_LOST;
}
pid_t pid = fork();
if(pid < 0) {
tpErrorLog("tpFetchViaNet: fork error %d\n", errno);
return CSSMERR_TP_REQUEST_LOST;
}
if(pid == 0) {
char *urlStr;
if(url.Data[url.Length - 1] == '\0') {
urlStr = (char *)url.Data;
}
else {
urlStr = (char *)alloc.malloc(url.Length + 1);
memmove(urlStr, url.Data, url.Length);
urlStr[url.Length] = '\0';
}
status = dup2(pipeFds[1], STDOUT_FILENO);
if(status < 0) {
tpErrorLog("tpFetchViaNet: dup2 error %d\n", errno);
_exit(1);
}
close(pipeFds[0]);
close(pipeFds[1]);
char *crlFetchTool = CRL_FETCH_TOOL;
#ifndef NDEBUG
char *cft = getenv(CRL_FETCH_ENV);
if(cft) {
crlFetchTool = cft;
}
#endif
execl(crlFetchTool, CRL_FETCH_TOOL, arg1, urlStr, NULL);
Syslog::error("TPNetwork: exec returned %d errno %d", status, errno);
_exit(1);
}
close(pipeFds[1]);
int thisRead = 0;
int totalRead = 0;
char inBuf[CRL_RBUF_SIZE];
attrBlob.Data = NULL;
attrBlob.Length = 0; CSSM_RETURN crtn = CSSM_OK;
do {
thisRead = read(pipeFds[0], inBuf, CRL_RBUF_SIZE);
if(thisRead < 0) {
switch(errno) {
case EINTR:
continue;
default:
tpErrorLog("tpFetchViaNet: read from child error %d\n", errno);
crtn = CSSMERR_TP_REQUEST_LOST;
break;
}
if(crtn) {
break;
}
}
if(thisRead == 0) {
attrBlob.Length = totalRead;
break;
}
if(attrBlob.Length < (unsigned)(totalRead + thisRead)) {
uint32 newLen = attrBlob.Length + CRL_RBUF_SIZE;
attrBlob.Data = (uint8 *)alloc.realloc(attrBlob.Data, newLen);
attrBlob.Length = newLen;
}
memmove(attrBlob.Data + totalRead, inBuf, thisRead);
totalRead += thisRead;
} while(1);
close(pipeFds[0]);
pid_t rtnPid;
do {
rtnPid = wait4(pid, &status, 0 , NULL );
if(rtnPid == pid) {
if(!WIFEXITED(status) || (WEXITSTATUS(status) != 0)) {
tpErrorLog("tpFetchViaNet: bad exit status from child\n");
crtn = CSSMERR_TP_REQUEST_LOST;
}
break;
}
else if(rtnPid < 0) {
if(errno == EINTR) {
continue;
}
tpErrorLog("tpFetchViaNet: wait4 error %d\n", errno);
crtn = CSSMERR_TP_REQUEST_LOST;
break;
}
else {
tpErrorLog("tpFetchViaNet: wait4 returned %d\n", rtnPid);
crtn = CSSMERR_TP_REQUEST_LOST;
}
} while(1);
return crtn;
}
static CSSM_RETURN tpCrlViaNet(
const CSSM_DATA &url,
TPCrlVerifyContext &vfyCtx,
TPCertInfo &forCert, TPCrlInfo *&rtnCrl)
{
TPCrlInfo *crl = NULL;
CSSM_DATA crlData;
CSSM_RETURN crtn;
CssmAllocator &alloc = CssmAllocator::standard();
crtn = tpFetchViaNet(url, LT_Crl, alloc, crlData);
if(crtn) {
return crtn;
}
try {
crl = new TPCrlInfo(vfyCtx.clHand,
vfyCtx.cspHand,
&crlData,
TIC_CopyData,
vfyCtx.verifyTime); }
catch(...) {
alloc.free(crlData.Data);
rtnCrl = NULL;
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
alloc.free(crlData.Data);
crtn = crl->verifyWithContext(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;
CssmAllocator &alloc = CssmAllocator::standard();
crtn = tpFetchViaNet(url, LT_Cert, 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,
TPCrlVerifyContext *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,
*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,
TPCrlVerifyContext &vfyCtx,
TPCrlInfo *&crl) {
CSSM_DATA_PTR fieldValue;
if(vfyCtx.verifyTime != NULL) {
tpErrorLog("***tpFetchCrlFromNet: don't know how to time travel\n");
return CSSMERR_APPLETP_CRL_NOT_FOUND;
}
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->fullName;
crtn = tpFetchViaGeneralNames(names,
cert,
&vfyCtx,
0, 0, NULL, NULL,
&rtnCrl);
break;
}
default:
tpErrorLog("tpFetchCrlFromNet: "
"unknown distPointName->nameType (%u)\n",
(unsigned)dp->distPointName->nameType);
break;
}
if(crtn) {
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_DATA_PTR fieldValue;
if(verifyTime != NULL) {
tpErrorLog("***tpFetchIssuerFromNet: don't know how to time travel\n");
return CSSMERR_TP_CERTGROUP_INCOMPLETE;
}
CSSM_RETURN crtn = subject.fetchField(&CSSMOID_IssuerAltName,
&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;
crtn = tpFetchViaGeneralNames(names,
subject,
NULL, clHand,
cspHand,
verifyTime,
&rtnCert,
NULL);
subject.freeField(&CSSMOID_IssuerAltName, fieldValue);
switch(crtn) {
case CSSM_OK:
case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE:
issuer = rtnCert;
break;
default:
break;
}
return crtn;
}