#include "AppleTPSession.h"
#include "certGroupUtils.h"
#include "TPCertInfo.h"
#include "TPCrlInfo.h"
#include "tpPolicies.h"
#include "tpdebugging.h"
#include "tpCrlVerify.h"
#include <Security/oidsalg.h>
#include <Security/cssmapple.h>
#define TP_PKINIT_SERVER_HACK 1
#if TP_PKINIT_SERVER_HACK
#include <Security/SecKeychain.h>
#include <Security/SecKeychainSearch.h>
#include <Security/SecCertificate.h>
#include <Security/oidscert.h>
#include <sys/types.h>
#include <pwd.h>
#define CFRELEASE(cf) if(cf) { CFRelease(cf); }
static bool tpCheckPkinitServerCert(
TPCertGroup &certGroup)
{
unsigned numCerts = certGroup.numCerts();
if(numCerts != 1) {
tpDebug("tpCheckPkinitServerCert: too many certs");
return false;
}
TPCertInfo *theCert = certGroup.certAtIndex(numCerts - 1);
if(!theCert->isSelfSigned()) {
tpDebug("tpCheckPkinitServerCert: 1 cert, not self-signed");
return false;
}
const CSSM_DATA *subjectName = theCert->subjectName();
OSStatus ortn;
SecKeychainRef kcRef = NULL;
string fullPathName;
const char *homeDir = getenv("HOME");
if (homeDir == NULL)
{
uid_t uid = geteuid();
if (!uid) uid = getuid();
struct passwd *pw = getpwuid(uid);
if (!pw) {
return false;
}
homeDir = pw->pw_dir;
}
fullPathName = homeDir;
fullPathName += "/Library/Application Support/PKINIT/TrustedServers.keychain";
ortn = SecKeychainOpen(fullPathName.c_str(), &kcRef);
if(ortn) {
tpDebug("tpCheckPkinitServerCert: keychain not found (1)");
return false;
}
bool ourRtn = false;
SecKeychainStatus kcStatus;
CSSM_DATA_PTR subjSerial = NULL;
CSSM_RETURN crtn;
SecKeychainSearchRef srchRef = NULL;
SecKeychainAttributeList attrList;
SecKeychainAttribute attrs[2];
SecKeychainItemRef foundItem = NULL;
ortn = SecKeychainGetStatus(kcRef, &kcStatus);
if(ortn) {
tpDebug("tpCheckPkinitServerCert: keychain not found (2)");
goto errOut;
}
crtn = theCert->fetchField(&CSSMOID_X509V1SerialNumber, &subjSerial);
if(crtn) {
tpDebug("tpCheckPkinitServerCert: error fetching serial number");
goto errOut;
}
attrs[0].tag = kSecSubjectItemAttr;
attrs[0].length = subjectName->Length;
attrs[0].data = subjectName->Data;
attrs[1].tag = kSecSerialNumberItemAttr;
attrs[1].length = subjSerial->Length;
attrs[1].data = subjSerial->Data;
attrList.count = 2;
attrList.attr = attrs;
ortn = SecKeychainSearchCreateFromAttributes(kcRef,
kSecCertificateItemClass,
&attrList,
&srchRef);
if(ortn) {
tpDebug("tpCheckPkinitServerCert: search failure");
goto errOut;
}
for(;;) {
ortn = SecKeychainSearchCopyNext(srchRef, &foundItem);
if(ortn) {
tpDebug("tpCheckPkinitServerCert: end search");
break;
}
CSSM_DATA certData;
ortn = SecCertificateGetData((SecCertificateRef)foundItem, &certData);
if(ortn) {
tpDebug("tpCheckPkinitServerCert: SecCertificateGetData failure");
continue;
}
if(tpCompareCssmData(&certData, theCert->itemData())){
tpDebug("tpCheckPkinitServerCert: FOUND CERT");
ourRtn = true;
break;
}
tpDebug("tpCheckPkinitServerCert: skipping matching cert");
CFRelease(foundItem);
foundItem = NULL;
}
errOut:
CFRELEASE(kcRef);
CFRELEASE(srchRef);
CFRELEASE(foundItem);
if(subjSerial != NULL) {
theCert->freeField(&CSSMOID_X509V1SerialNumber, subjSerial);
}
return ourRtn;
}
#endif
void AppleTPSession::CertGroupConstruct(CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
const CSSM_DL_DB_LIST &DBList,
const void *ConstructParams,
const CSSM_CERTGROUP &CertGroupFrag,
CSSM_CERTGROUP_PTR &CertGroup)
{
TPCertGroup outCertGroup(*this, TGO_Caller);
TPCertGroup inCertGroup(CertGroupFrag,
clHand,
cspHand,
*this,
NULL, true, TGO_Group);
TPCertGroup gatheredCerts(*this, TGO_Group);
CSSM_RETURN constructReturn = CSSM_OK;
CSSM_APPLE_TP_ACTION_FLAGS actionFlags = 0;
CSSM_BOOL verifiedToRoot; CSSM_BOOL verifiedToAnchor; CSSM_BOOL verifiedViaTrustSetting;
try {
CertGroupConstructPriv(clHand,
cspHand,
inCertGroup,
&DBList,
NULL,
0, NULL,
actionFlags,
NULL, NULL, 0, 0,
gatheredCerts,
verifiedToRoot,
verifiedToAnchor,
verifiedViaTrustSetting,
outCertGroup);
}
catch(const CssmError &cerr) {
constructReturn = cerr.error;
if(outCertGroup.numCerts() == 0) {
CssmError::throwMe(constructReturn);
}
}
CertGroup = outCertGroup.buildCssmCertGroup();
outCertGroup.freeDbRecords();
if(constructReturn) {
CssmError::throwMe(constructReturn);
}
}
void AppleTPSession::CertGroupConstructPriv(CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
TPCertGroup &inCertGroup,
const CSSM_DL_DB_LIST *DBList, const char *cssmTimeStr,
uint32 numAnchorCerts,
const CSSM_DATA *anchorCerts,
CSSM_APPLE_TP_ACTION_FLAGS actionFlags,
const CSSM_OID *policyOid,
const char *policyStr,
uint32 policyStrLen,
SecTrustSettingsKeyUsage keyUse,
TPCertGroup &certsToBeFreed,
CSSM_BOOL &verifiedToRoot, CSSM_BOOL &verifiedToAnchor, CSSM_BOOL &verifiedViaTrustSetting, TPCertGroup &outCertGroup) {
TPCertInfo *subjectCert; CSSM_RETURN outErr = CSSM_OK;
subjectCert = inCertGroup.certAtIndex(0);
outCertGroup.appendCert(subjectCert);
subjectCert->isLeaf(true);
subjectCert->isFromInputCerts(true);
outCertGroup.setAllUnused();
subjectCert->used(true);
outErr = outCertGroup.buildCertGroup(
*subjectCert,
&inCertGroup,
DBList,
clHand,
cspHand,
cssmTimeStr,
numAnchorCerts,
anchorCerts,
certsToBeFreed,
&certsToBeFreed, CSSM_TRUE, actionFlags,
policyOid,
policyStr,
policyStrLen,
keyUse,
verifiedToRoot,
verifiedToAnchor,
verifiedViaTrustSetting);
if(outErr) {
CssmError::throwMe(outErr);
}
}
static bool checkPolicyOid(
const CSSM_OID &oid,
TPPolicy &tpPolicy)
{
if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_SSL)) {
tpPolicy = kTP_SSL;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_X509_BASIC)) {
tpPolicy = kTPx509Basic;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_SMIME)) {
tpPolicy = kTP_SMIME;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_EAP)) {
tpPolicy = kTP_EAP;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_SW_UPDATE_SIGNING)) {
tpPolicy = kTP_SWUpdateSign;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_RESOURCE_SIGN)) {
tpPolicy = kTP_ResourceSign;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_IP_SEC)) {
tpPolicy = kTP_IPSec;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_ICHAT)) {
tpPolicy = kTP_iChat;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_ISIGN)) {
tpPolicy = kTPiSign;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_PKINIT_CLIENT)) {
tpPolicy = kTP_PKINIT_Client;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_PKINIT_SERVER)) {
tpPolicy = kTP_PKINIT_Server;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_CODE_SIGNING)) {
tpPolicy = kTP_CodeSigning;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_PACKAGE_SIGNING)) {
tpPolicy = kTP_PackageSigning;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_MACAPPSTORE_RECEIPT)) {
tpPolicy = kTP_MacAppStoreRec;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_APPLEID_SHARING)) {
tpPolicy = kTP_AppleIDSharing;
return true;
}
else if(tpCompareOids(&oid, &CSSMOID_APPLE_TP_TIMESTAMPING)) {
tpPolicy = kTP_TimeStamping;
return true;
}
return false;
}
void AppleTPSession::CertGroupVerify(CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
const CSSM_CERTGROUP &CertGroupToBeVerified,
const CSSM_TP_VERIFY_CONTEXT *VerifyContext,
CSSM_TP_VERIFY_CONTEXT_RESULT_PTR VerifyContextResult)
{
CSSM_BOOL verifiedToRoot = CSSM_FALSE;
CSSM_BOOL verifiedToAnchor = CSSM_FALSE;
CSSM_BOOL verifiedViaTrustSetting = CSSM_FALSE;
CSSM_RETURN constructReturn = CSSM_OK;
CSSM_RETURN policyReturn = CSSM_OK;
const CSSM_TP_CALLERAUTH_CONTEXT *cred;
const CSSM_APPLE_TP_ACTION_DATA * volatile actionData = NULL;
CSSM_TIMESTRING cssmTimeStr;
CSSM_APPLE_TP_ACTION_FLAGS actionFlags = 0;
CSSM_TP_STOP_ON tpStopOn = 0;
bool didCertPolicy = false;
bool didRevokePolicy = false;
CSSM_OID utNullPolicy = {0, NULL};
const CSSM_OID *utPolicyOid = NULL;
const char *utPolicyStr = NULL;
uint32 utPolicyStrLen = 0;
SecTrustSettingsKeyUsage utKeyUse = 0;
bool utTrustSettingEnabled = false;
if(VerifyContextResult) {
memset(VerifyContextResult, 0, sizeof(*VerifyContextResult));
}
if((VerifyContext == NULL) || (VerifyContext->Cred == NULL)) {
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
cred = VerifyContext->Cred;
actionData = (CSSM_APPLE_TP_ACTION_DATA * volatile)VerifyContext->ActionData.Data;
if(actionData != NULL) {
switch(actionData->Version) {
case CSSM_APPLE_TP_ACTION_VERSION:
if(VerifyContext->ActionData.Length !=
sizeof(CSSM_APPLE_TP_ACTION_DATA)) {
CssmError::throwMe(CSSMERR_TP_INVALID_ACTION_DATA);
}
break;
default:
CssmError::throwMe(CSSMERR_TP_INVALID_ACTION_DATA);
}
actionFlags = actionData->ActionFlags;
if(actionFlags & CSSM_TP_ACTION_TRUST_SETTINGS) {
utTrustSettingEnabled = true;
}
}
cssmTimeStr = cred->VerifyTime;
tpStopOn = cred->VerificationAbortOn;
switch(tpStopOn) {
case CSSM_TP_STOP_ON_NONE:
case CSSM_TP_STOP_ON_FIRST_FAIL:
break;
case CSSM_TP_STOP_ON_POLICY:
tpStopOn = CSSM_TP_STOP_ON_FIRST_FAIL;
break;
default:
CssmError::throwMe(CSSMERR_TP_INVALID_STOP_ON_POLICY);
}
if(cred->CallerCredentials != NULL) {
CssmError::throwMe(CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER);
}
if(utTrustSettingEnabled) {
const CSSM_TP_POLICYINFO *pinfo = &cred->Policy;
TPPolicy utPolicy = kTPx509Basic;
utPolicyOid = &utNullPolicy;
if(pinfo->NumberOfPolicyIds == 0) {
tpTrustSettingsDbg("CertGroupVerify: User trust enabled but no policies (1)");
}
else {
CSSM_FIELD_PTR utPolicyField = &pinfo->PolicyIds[0];
utPolicyOid = &utPolicyField->FieldOid;
bool foundPolicy = checkPolicyOid(*utPolicyOid, utPolicy);
if(!foundPolicy) {
tpTrustSettingsDbg("CertGroupVerify: User trust enabled but no policies");
}
else {
tp_policyTrustSettingParams(utPolicy, &utPolicyField->FieldValue,
&utPolicyStr, &utPolicyStrLen, &utKeyUse);
}
}
}
TPCertGroup outCertGroup(*this,
TGO_Caller); TPCertGroup inCertGroup(CertGroupToBeVerified, clHand, cspHand, *this,
cssmTimeStr, true, TGO_Group);
TPCertGroup gatheredCerts(*this, TGO_Group);
try {
CertGroupConstructPriv(
clHand,
cspHand,
inCertGroup,
cred->DBList,
cssmTimeStr,
cred->NumberOfAnchorCerts,
cred->AnchorCerts,
actionFlags,
utPolicyOid,
utPolicyStr,
utPolicyStrLen,
utKeyUse,
gatheredCerts,
verifiedToRoot,
verifiedToAnchor,
verifiedViaTrustSetting,
outCertGroup);
}
catch(const CssmError &cerr) {
constructReturn = cerr.error;
if(outCertGroup.numCerts() == 0) {
CssmError::throwMe(constructReturn);
}
}
assert(outCertGroup.numCerts() >= 1);
switch(constructReturn) {
case CSSMERR_TP_CERTIFICATE_CANT_OPERATE:
case CSSMERR_TP_INVALID_CERT_AUTHORITY:
case CSSMERR_APPLETP_TRUST_SETTING_DENY:
case errSecInvalidTrustSettings:
break;
default:
if(verifiedToAnchor || verifiedViaTrustSetting) {
constructReturn = CSSM_OK;
}
else if(verifiedToRoot) {
if(actionFlags & CSSM_TP_ACTION_IMPLICIT_ANCHORS) {
constructReturn = CSSM_OK;
}
else {
constructReturn = CSSMERR_TP_INVALID_ANCHOR_CERT;
}
}
else {
constructReturn = CSSMERR_TP_NOT_TRUSTED;
}
if((constructReturn != CSSM_OK) &&
outCertGroup.isAllowedError(constructReturn)) {
constructReturn = CSSM_OK;
}
break;
}
TPPolicy tpPolicy;
const CSSM_APPLE_TP_SSL_OPTIONS *sslOpts;
CSSM_RETURN thisPolicyRtn = CSSM_OK;
TPCrlGroup *crlGroup = NULL;
try {
crlGroup = new TPCrlGroup(&VerifyContext->Crls,
clHand, cspHand,
*this, NULL, TGO_Group);
}
catch(const CssmError &cerr) {
CSSM_RETURN cr = cerr.error;
outCertGroup.certAtIndex(0)->addStatusCode(cr);
tpDebug("CertGroupVerify: error constructing CrlGroup; continuing\n");
}
TPVerifyContext revokeVfyContext(*this,
clHand,
cspHand,
cssmTimeStr,
cred->NumberOfAnchorCerts,
cred->AnchorCerts,
&inCertGroup,
crlGroup,
gatheredCerts,
cred->DBList,
kRevokeNone, actionFlags,
NULL, NULL, utPolicyOid,
utPolicyStr,
utPolicyStrLen,
utKeyUse);
bool doPolicyVerify;
bool doRevocationPolicy;
for(uint32 polDex=0; polDex<cred->Policy.NumberOfPolicyIds; polDex++) {
if(cred->Policy.PolicyIds == NULL) {
policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
CSSM_FIELD_PTR policyId = &cred->Policy.PolicyIds[polDex];
const CSSM_DATA *fieldVal = &policyId->FieldValue;
const CSSM_OID *oid = &policyId->FieldOid;
thisPolicyRtn = CSSM_OK;
doPolicyVerify = false;
doRevocationPolicy = false;
sslOpts = NULL;
doPolicyVerify = checkPolicyOid(*oid, tpPolicy);
if(doPolicyVerify) {
bool policyAbort = false;
switch(tpPolicy) {
case kTPx509Basic:
case kTPiSign:
case kTP_PKINIT_Client:
case kTP_PKINIT_Server:
if(fieldVal->Data != NULL) {
policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
policyAbort = true;
break;
}
break;
default:
break;
}
if(policyAbort) {
break;
}
#if TP_PKINIT_SERVER_HACK
if(tpPolicy == kTP_PKINIT_Server) {
if(constructReturn == CSSMERR_TP_INVALID_ANCHOR_CERT) {
if(tpCheckPkinitServerCert(outCertGroup)) {
constructReturn = CSSM_OK;
}
}
}
#endif
}
else if(tpCompareOids(oid, &CSSMOID_APPLE_TP_REVOCATION_CRL)) {
const CSSM_APPLE_TP_CRL_OPTIONS *crlOpts;
crlOpts = (CSSM_APPLE_TP_CRL_OPTIONS *)fieldVal->Data;
thisPolicyRtn = CSSM_OK;
if(crlOpts != NULL) {
switch(crlOpts->Version) {
case CSSM_APPLE_TP_CRL_OPTS_VERSION:
if(fieldVal->Length !=
sizeof(CSSM_APPLE_TP_CRL_OPTIONS)) {
thisPolicyRtn =
CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
break;
default:
thisPolicyRtn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
if(thisPolicyRtn != CSSM_OK) {
policyReturn = thisPolicyRtn;
break;
}
}
revokeVfyContext.policy = kRevokeCrlBasic;
revokeVfyContext.crlOpts = crlOpts;
doRevocationPolicy = true;
}
else if(tpCompareOids(oid, &CSSMOID_APPLE_TP_REVOCATION_OCSP)) {
const CSSM_APPLE_TP_OCSP_OPTIONS *ocspOpts;
ocspOpts = (CSSM_APPLE_TP_OCSP_OPTIONS *)fieldVal->Data;
thisPolicyRtn = CSSM_OK;
if(ocspOpts != NULL) {
switch(ocspOpts->Version) {
case CSSM_APPLE_TP_OCSP_OPTS_VERSION:
if(fieldVal->Length !=
sizeof(CSSM_APPLE_TP_OCSP_OPTIONS)) {
thisPolicyRtn =
CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
break;
default:
thisPolicyRtn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
if(thisPolicyRtn != CSSM_OK) {
policyReturn = thisPolicyRtn;
break;
}
}
revokeVfyContext.policy = kRevokeOcsp;
revokeVfyContext.ocspOpts = ocspOpts;
doRevocationPolicy = true;
}
else {
policyReturn = CSSMERR_TP_INVALID_POLICY_IDENTIFIERS;
break;
}
if(doPolicyVerify) {
assert(!doRevocationPolicy); thisPolicyRtn = tp_policyVerify(tpPolicy,
*this,
clHand,
cspHand,
&outCertGroup,
verifiedToRoot,
verifiedViaTrustSetting,
actionFlags,
fieldVal,
cred->Policy.PolicyControl); didCertPolicy = true;
}
if(doRevocationPolicy) {
assert(!doPolicyVerify); thisPolicyRtn = tpRevocationPolicyVerify(revokeVfyContext, outCertGroup);
didRevokePolicy = true;
}
if((thisPolicyRtn != CSSM_OK) &&
outCertGroup.isAllowedError(thisPolicyRtn)) {
thisPolicyRtn = CSSM_OK;
}
if(thisPolicyRtn) {
if(policyReturn == CSSM_OK) {
policyReturn = thisPolicyRtn;
}
if(tpStopOn == CSSM_TP_STOP_ON_FIRST_FAIL) {
break;
}
}
}
if((policyReturn == CSSM_OK) || (tpStopOn == CSSM_TP_STOP_ON_NONE)) {
if(!didCertPolicy) {
policyReturn = tp_policyVerify(kTPDefault,
*this,
clHand,
cspHand,
&outCertGroup,
verifiedToRoot,
verifiedViaTrustSetting,
actionFlags,
NULL, cred->Policy.PolicyControl);
if((policyReturn != CSSM_OK) &&
outCertGroup.isAllowedError(policyReturn)) {
policyReturn = CSSM_OK;
}
}
if( !didRevokePolicy && ( (policyReturn == CSSM_OK || (tpStopOn == CSSM_TP_STOP_ON_NONE)) )
) {
revokeVfyContext.policy = TP_CRL_POLICY_DEFAULT;
CSSM_RETURN thisPolicyRtn = tpRevocationPolicyVerify(revokeVfyContext,
outCertGroup);
if((thisPolicyRtn != CSSM_OK) &&
outCertGroup.isAllowedError(thisPolicyRtn)) {
thisPolicyRtn = CSSM_OK;
}
if((thisPolicyRtn != CSSM_OK) && (policyReturn == CSSM_OK)) {
policyReturn = thisPolicyRtn;
}
}
}
delete crlGroup;
if(VerifyContextResult != NULL) {
VerifyContextResult->NumberOfEvidences = 3;
VerifyContextResult->Evidence =
(CSSM_EVIDENCE_PTR)calloc(3, sizeof(CSSM_EVIDENCE));
CSSM_TP_APPLE_EVIDENCE_HEADER *hdr =
(CSSM_TP_APPLE_EVIDENCE_HEADER *)malloc(
sizeof(CSSM_TP_APPLE_EVIDENCE_HEADER));
hdr->Version = CSSM_TP_APPLE_EVIDENCE_VERSION;
CSSM_EVIDENCE_PTR ev = &VerifyContextResult->Evidence[0];
ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_HEADER;
ev->Evidence = hdr;
ev = &VerifyContextResult->Evidence[1];
ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_CERTGROUP;
ev->Evidence = outCertGroup.buildCssmCertGroup();
ev = &VerifyContextResult->Evidence[2];
ev->EvidenceForm = CSSM_EVIDENCE_FORM_APPLE_CERT_INFO;
ev->Evidence = outCertGroup.buildCssmEvidenceInfo();
}
else {
outCertGroup.freeDbRecords();
}
CSSM_RETURN outErr = outCertGroup.getReturnCode(constructReturn, policyReturn,
actionFlags);
if(outErr) {
CssmError::throwMe(outErr);
}
}