#include <security_keychain/Trust.h>
#include <security_keychain/TrustSettingsSchema.h>
#include <security_cdsa_utilities/cssmdates.h>
#include <security_utilities/cfutilities.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecCertificate.h>
#include <Security/SecTrust.h>
#include "SecBridge.h"
#include "TrustAdditions.h"
#include "TrustKeychains.h"
using namespace Security;
using namespace KeychainCore;
static inline CssmData cfData(CFDataRef data)
{
return CssmData(const_cast<UInt8 *>(CFDataGetBytePtr(data)),
CFDataGetLength(data));
}
static SecCertificateRef
convert(const SecPointer<Certificate> &certificate)
{
return *certificate;
}
ModuleNexus<TrustStore> Trust::gStore;
#pragma mark -- TrustKeychains --
static const CSSM_DL_DB_HANDLE nullCSSMDLDBHandle = {0,};
class TrustKeychains
{
public:
TrustKeychains();
~TrustKeychains() {}
CSSM_DL_DB_HANDLE rootStoreHandle() { return mRootStore ? mRootStore->database()->handle() : nullCSSMDLDBHandle; }
CSSM_DL_DB_HANDLE systemKcHandle() { return mSystem ? mSystem->database()->handle() : nullCSSMDLDBHandle; }
Keychain &rootStore() { return mRootStore; }
Keychain &systemKc() { return mSystem; }
private:
Keychain mRootStore;
Keychain mSystem;
};
static ModuleNexus<TrustKeychains> trustKeychains;
static ModuleNexus<RecursiveMutex> trustKeychainsMutex;
TrustKeychains::TrustKeychains() :
mRootStore(globals().storageManager.make(SYSTEM_ROOT_STORE_PATH, false)),
mSystem(globals().storageManager.make(ADMIN_CERT_STORE_PATH, false))
{
}
RecursiveMutex& SecTrustKeychainsGetMutex()
{
return trustKeychainsMutex();
}
#pragma mark -- Trust --
Trust::Trust(CFTypeRef certificates, CFTypeRef policies)
: mTP(gGuidAppleX509TP), mAction(CSSM_TP_ACTION_DEFAULT),
mCerts(cfArrayize(certificates)), mPolicies(cfArrayize(policies)),
mResult(kSecTrustResultInvalid), mUsingTrustSettings(false),
mAnchorPolicy(useAnchorsDefault), mSearchLibsSet(false),
mSearchLibs(NULL), mMutex(Mutex::recursive)
{
}
Trust::~Trust()
{
clearResults();
if (mSearchLibs) {
delete mSearchLibs;
}
}
StorageManager::KeychainList& Trust::searchLibs(bool init)
{
if (!mSearchLibs) {
mSearchLibs = new StorageManager::KeychainList;
if (init) {
globals().storageManager.getSearchList(*mSearchLibs);
}
}
return *mSearchLibs;
}
void Trust::searchLibs(StorageManager::KeychainList &libs)
{
searchLibs(false) = libs;
mSearchLibsSet = true;
}
CSSM_TP_VERIFY_CONTEXT_RESULT_PTR Trust::cssmResult()
{
if (mResult == kSecTrustResultInvalid)
MacOSError::throwMe(errSecTrustNotAvailable);
return &mTpResult;
}
CssmData cfCertificateData(SecCertificateRef certificate)
{
return Certificate::required(certificate)->data();
}
CssmField cfField(SecPolicyRef item)
{
SecPointer<Policy> policy = Policy::required(SecPolicyRef(item));
return CssmField(policy->oid(), policy->value());
}
CSSM_DL_DB_HANDLE cfKeychain(SecKeychainRef ref)
{
Keychain keychain = KeychainImpl::required(ref);
return keychain->database()->handle();
}
#if !defined(NDEBUG)
void showCertSKID(const void *value, void *context);
#endif
void Trust::evaluate(bool disableEV)
{
bool isEVCandidate=false;
{
StLock<Mutex>_(mMutex);
clearResults();
CFArrayRef allowedAnchors = allowedEVRootsForLeafCertificate(mCerts);
CFArrayRef filteredCerts = NULL;
isEVCandidate = (allowedAnchors && !disableEV) ? true : false;
if (isEVCandidate) {
secdebug("evTrust", "Trust::evaluate() certificate is EV candidate");
filteredCerts = potentialEVChainWithCertificates(mCerts);
mCerts = filteredCerts;
} else {
secdebug("evTrust", "Trust::evaluate() performing standard evaluation");
if (mCerts) {
filteredCerts = CFArrayCreateMutableCopy(NULL, 0, mCerts);
}
if (mAnchors) {
allowedAnchors = CFArrayCreateMutableCopy(NULL, 0, mAnchors);
}
}
mAllowedAnchors = allowedAnchors;
mFilteredCerts = filteredCerts;
if (allowedAnchors)
CFRelease(allowedAnchors);
if (filteredCerts)
CFRelease(filteredCerts);
if (mAllowedAnchors)
{
secdebug("trusteval", "Trust::evaluate: anchors: %ld", CFArrayGetCount(mAllowedAnchors));
#if !defined(NDEBUG)
CFArrayApplyFunction(mAllowedAnchors, CFRangeMake(0, CFArrayGetCount(mAllowedAnchors)), showCertSKID, NULL);
#endif
}
if(!mSearchLibsSet) {
globals().storageManager.getSearchList(searchLibs());
mSearchLibsSet = true;
}
CFToVector<CssmData, SecCertificateRef, cfCertificateData> subjects(mFilteredCerts);
CertGroup subjectCertGroup(CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_BER, CSSM_CERTGROUP_DATA);
subjectCertGroup.count() = subjects;
subjectCertGroup.blobCerts() = subjects;
TPBuildVerifyContext context(mAction);
CSSM_APPLE_TP_ACTION_DATA localActionData;
memset(&localActionData, 0, sizeof(localActionData));
CssmData localActionCData((uint8 *)&localActionData, sizeof(localActionData));
CSSM_APPLE_TP_ACTION_DATA *actionDataP = &localActionData;
if (mActionData) {
context.actionData() = cfData(mActionData);
actionDataP = (CSSM_APPLE_TP_ACTION_DATA *)context.actionData().data();
}
else {
context.actionData() = localActionCData;
}
if (!mAnchors) {
actionDataP->ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS;
}
if (policySpecified(mPolicies, CSSMOID_APPLE_TP_SSL)) {
actionDataP->ActionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET;
}
CFMutableArrayRef allPolicies = NULL;
uint32 numSpecAdded = 0;
uint32 numPrefAdded = 0;
bool requirePerCert = (actionDataP->ActionFlags & CSSM_TP_ACTION_REQUIRE_REV_PER_CERT);
if (isEVCandidate || requirePerCert) {
secdebug("evTrust", "Trust::evaluate() forcing OCSP/CRL revocation checking");
allPolicies = forceRevocationPolicies(numPrefAdded, context.allocator, requirePerCert);
}
else if (mAnchors && (CFArrayGetCount(mAnchors)==0) && (searchLibs().size()==0)) {
allPolicies = NULL; }
else if(!(revocationPolicySpecified(mPolicies))) {
allPolicies = addSpecifiedRevocationPolicies(numSpecAdded, context.allocator);
if(allPolicies == NULL) {
allPolicies = Trust::addPreferenceRevocationPolicies(numPrefAdded,
context.allocator);
}
}
if(allPolicies == NULL) {
allPolicies = CFMutableArrayRef(CFArrayRef(mPolicies));
}
orderRevocationPolicies(allPolicies);
CFToVector<CssmField, SecPolicyRef, cfField> policies(allPolicies);
if (policies.empty())
MacOSError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
context.setPolicies(policies, policies);
CFCopyRef<CFArrayRef> anchors(mAllowedAnchors);
CFToVector<CssmData, SecCertificateRef, cfCertificateData> roots(anchors);
if (!anchors) {
mUsingTrustSettings = (mAnchorPolicy < useAnchorsOnly);
secdebug("userTrust", "Trust::evaluate() %s",
(mUsingTrustSettings) ? "using UserTrust" : "has no trusted anchors!");
}
else {
mUsingTrustSettings = (mAnchorPolicy == useAnchorsAndBuiltIns);
secdebug("userTrust", "Trust::evaluate() using %s %s anchors",
(mUsingTrustSettings) ? "UserTrust AND" : "only",
(isEVCandidate) ? "EV" : "caller");
context.anchors(roots, roots);
}
vector<CSSM_DL_DB_HANDLE> dlDbList;
{
StLock<Mutex> _(SecTrustKeychainsGetMutex());
StorageManager::KeychainList& list = searchLibs();
for (StorageManager::KeychainList::const_iterator it = list.begin();
it != list.end(); it++)
{
try
{
CSSM_DL_DB_HANDLE dldbHandle = (*it)->database()->handle();
if (dldbHandle.DLHandle) {
CSSM_GUID guid = {};
CSSM_RETURN crtn = CSSM_GetModuleGUIDFromHandle(dldbHandle.DLHandle, &guid);
if (crtn == CSSM_OK) {
if ((memcmp(&guid, &gGuidAppleLDAPDL, sizeof(CSSM_GUID))==0) ||
(memcmp(&guid, &gGuidAppleDotMacDL, sizeof(CSSM_GUID))==0)) {
continue; }
}
}
dlDbList.push_back(dldbHandle);
}
catch (...)
{
}
}
if(mUsingTrustSettings) {
try {
CSSM_DL_DB_HANDLE rootStoreHandle = trustKeychains().rootStoreHandle();
if (rootStoreHandle.DBHandle)
dlDbList.push_back(rootStoreHandle);
actionDataP->ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS;
}
catch (...) {
mUsingTrustSettings = false;
}
try {
CSSM_DL_DB_HANDLE systemKcHandle = trustKeychains().systemKcHandle();
if (systemKcHandle.DBHandle)
dlDbList.push_back(systemKcHandle);
}
catch(...) {
}
}
context.setDlDbList(dlDbList.size(), &dlDbList[0]);
}
char timeString[15];
if (mVerifyTime) {
CssmUniformDate(static_cast<CFDateRef>(mVerifyTime)).convertTo(
timeString, sizeof(timeString));
context.time(timeString);
}
StorageManager::KeychainList *holdSearchList = NULL;
if (searchLibs().size() > 0) {
holdSearchList = new StorageManager::KeychainList;
globals().storageManager.getSearchList(*holdSearchList);
}
try {
mTP->certGroupVerify(subjectCertGroup, context, &mTpResult);
mTpReturn = noErr;
} catch (CommonError &err) {
mTpReturn = err.osStatus();
secdebug("trusteval", "certGroupVerify exception: %d", (int)mTpReturn);
}
mResult = diagnoseOutcome();
if (mTpResult.count() > 0
&& mTpResult[0].form() == CSSM_EVIDENCE_FORM_APPLE_HEADER
&& mTpResult[0].as<CSSM_TP_APPLE_EVIDENCE_HEADER>()->Version == CSSM_TP_APPLE_EVIDENCE_VERSION
&& mTpResult.count() == 3
&& mTpResult[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP
&& mTpResult[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO) {
evaluateUserTrust(*mTpResult[1].as<CertGroup>(),
mTpResult[2].as<CSSM_TP_APPLE_EVIDENCE_INFO>(), anchors);
} else {
secdebug("trusteval", "unexpected evidence ignored");
}
CFArrayRef fullChain = makeCFArray(convert, mCertChain);
CFDictionaryRef etResult = extendedTrustResults(fullChain, mResult, mTpReturn, isEVCandidate);
mExtendedResult = etResult; if (etResult) {
CFRelease(etResult);
}
if (fullChain) {
CFRelease(fullChain);
}
if(numSpecAdded) {
freeSpecifiedRevocationPolicies(allPolicies, numSpecAdded, context.allocator);
}
if(numPrefAdded) {
Trust::freePreferenceRevocationPolicies(allPolicies, numPrefAdded, context.allocator);
}
if (holdSearchList) {
delete holdSearchList;
holdSearchList = NULL;
}
}
if (isEVCandidate && mResult == kSecTrustResultRecoverableTrustFailure &&
isRevocationServerMetaError(mTpReturn)) {
evaluate(true);
}
}
static const CSSM_RETURN recoverableErrors[] =
{
CSSMERR_TP_INVALID_ANCHOR_CERT,
CSSMERR_TP_NOT_TRUSTED,
CSSMERR_TP_VERIFICATION_FAILURE,
CSSMERR_TP_VERIFY_ACTION_FAILED,
CSSMERR_TP_INVALID_CERTIFICATE,
CSSMERR_TP_INVALID_REQUEST_INPUTS,
CSSMERR_TP_CERT_EXPIRED,
CSSMERR_TP_CERT_NOT_VALID_YET,
CSSMERR_TP_CERTIFICATE_CANT_OPERATE,
CSSMERR_TP_INVALID_CERT_AUTHORITY,
CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK,
CSSMERR_APPLETP_HOSTNAME_MISMATCH,
CSSMERR_TP_VERIFY_ACTION_FAILED,
CSSMERR_APPLETP_SMIME_EMAIL_ADDRS_NOT_FOUND,
CSSMERR_APPLETP_SMIME_NO_EMAIL_ADDRS,
CSSMERR_APPLETP_SMIME_BAD_EXT_KEY_USE,
CSSMERR_APPLETP_CS_BAD_CERT_CHAIN_LENGTH,
CSSMERR_APPLETP_CS_NO_BASIC_CONSTRAINTS,
CSSMERR_APPLETP_CS_BAD_PATH_LENGTH,
CSSMERR_APPLETP_CS_NO_EXTENDED_KEY_USAGE,
CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE,
CSSMERR_APPLETP_CODE_SIGN_DEVELOPMENT,
CSSMERR_APPLETP_RS_BAD_CERT_CHAIN_LENGTH,
CSSMERR_APPLETP_UNKNOWN_CRITICAL_EXTEN,
CSSMERR_APPLETP_CRL_NOT_FOUND,
CSSMERR_APPLETP_CRL_SERVER_DOWN,
CSSMERR_APPLETP_CRL_NOT_VALID_YET,
CSSMERR_APPLETP_OCSP_UNAVAILABLE,
CSSMERR_APPLETP_INCOMPLETE_REVOCATION_CHECK,
CSSMERR_APPLETP_NETWORK_FAILURE,
CSSMERR_APPLETP_OCSP_RESP_TRY_LATER,
};
#define NUM_RECOVERABLE_ERRORS (sizeof(recoverableErrors) / sizeof(CSSM_RETURN))
SecTrustResultType Trust::diagnoseOutcome()
{
StLock<Mutex>_(mMutex);
uint32 chainLength = 0;
if (mTpResult.count() == 3 &&
mTpResult[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP &&
mTpResult[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO)
{
const CertGroup &chain = *mTpResult[1].as<CertGroup>();
chainLength = chain.count();
}
switch (mTpReturn) {
case noErr: if (mUsingTrustSettings)
{
if (chainLength)
{
const CSSM_TP_APPLE_EVIDENCE_INFO *infoList = mTpResult[2].as<CSSM_TP_APPLE_EVIDENCE_INFO>();
const TPEvidenceInfo &info = TPEvidenceInfo::overlay(infoList[chainLength-1]);
const CSSM_TP_APPLE_CERT_STATUS resultCertStatus = info.status();
bool hasUserDomainTrust = ((resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST) &&
(resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_USER));
bool hasAdminDomainTrust = ((resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_TRUST) &&
(resultCertStatus & CSSM_CERT_STATUS_TRUST_SETTINGS_FOUND_ADMIN));
if (hasUserDomainTrust || hasAdminDomainTrust)
{
return kSecTrustResultProceed; }
}
}
return kSecTrustResultUnspecified; case CSSMERR_TP_INVALID_CERTIFICATE: return kSecTrustResultFatalTrustFailure;
case CSSMERR_APPLETP_TRUST_SETTING_DENY: return kSecTrustResultDeny;
default:
break;
}
const CSSM_RETURN *errp=recoverableErrors;
for(unsigned dex=0; dex<NUM_RECOVERABLE_ERRORS; dex++, errp++) {
if(*errp == mTpReturn) {
return kSecTrustResultRecoverableTrustFailure;
}
}
return kSecTrustResultOtherError; }
void Trust::evaluateUserTrust(const CertGroup &chain,
const CSSM_TP_APPLE_EVIDENCE_INFO *infoList, CFCopyRef<CFArrayRef> anchors)
{
StLock<Mutex>_(mMutex);
mCertChain.resize(chain.count());
for (uint32 n = 0; n < mCertChain.size(); n++) {
const TPEvidenceInfo &info = TPEvidenceInfo::overlay(infoList[n]);
if (info.recordId()) {
Keychain keychain = keychainByDLDb(info.DlDbHandle);
DbUniqueRecord uniqueId(keychain->database()->newDbUniqueRecord());
secdebug("trusteval", "evidence %lu from keychain \"%s\"", (unsigned long)n, keychain->name());
*static_cast<CSSM_DB_UNIQUE_RECORD_PTR *>(uniqueId) = info.UniqueRecord;
uniqueId->activate(); Item ii = keychain->item(CSSM_DL_DB_RECORD_X509_CERTIFICATE, uniqueId);
Certificate* cert = dynamic_cast<Certificate*>(ii.get());
if (cert == NULL) {
CssmError::throwMe(CSSMERR_CSSM_INVALID_POINTER);
}
mCertChain[n] = cert;
} else if (info.status(CSSM_CERT_STATUS_IS_IN_INPUT_CERTS)) {
secdebug("trusteval", "evidence %lu from input cert %lu", (unsigned long)n, (unsigned long)info.index());
assert(info.index() < uint32(CFArrayGetCount(mCerts)));
SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(mCerts,
info.index()));
mCertChain[n] = Certificate::required(cert);
} else if (info.status(CSSM_CERT_STATUS_IS_IN_ANCHORS)) {
secdebug("trusteval", "evidence %lu from anchor cert %lu", (unsigned long)n, (unsigned long)info.index());
assert(info.index() < uint32(CFArrayGetCount(anchors)));
SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(anchors,
info.index()));
mCertChain[n] = Certificate::required(cert);
} else {
secdebug("trusteval", "evidence %lu from unknown source", (unsigned long)n);
mCertChain[n] =
new Certificate(chain.blobCerts()[n],
CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_BER);
}
}
TrustStore &store = gStore();
SecPointer<Policy> policy =
Policy::required(SecPolicyRef(CFArrayGetValueAtIndex(mPolicies, 0)));
for (mResultIndex = 0;
mResult == kSecTrustResultUnspecified && mResultIndex < mCertChain.size();
mResultIndex++) {
if (!mCertChain[mResultIndex]) {
assert(false);
continue;
}
mResult = store.find(mCertChain[mResultIndex], policy, searchLibs());
secdebug("trusteval", "trustResult=%lu from cert %lu", mResult, (unsigned long)mResultIndex);
}
}
void Trust::releaseTPEvidence(TPVerifyResult &result, Allocator &allocator)
{
if (result.count() > 0) { if (result[0].form() == CSSM_EVIDENCE_FORM_APPLE_HEADER) {
if (result[0].as<CSSM_TP_APPLE_EVIDENCE_HEADER>()->Version == CSSM_TP_APPLE_EVIDENCE_VERSION
&& result.count() == 3
&& result[1].form() == CSSM_EVIDENCE_FORM_APPLE_CERTGROUP
&& result[2].form() == CSSM_EVIDENCE_FORM_APPLE_CERT_INFO) {
CertGroup& certs = *result[1].as<CertGroup>();
CSSM_TP_APPLE_EVIDENCE_INFO *evidence = result[2].as<CSSM_TP_APPLE_EVIDENCE_INFO>();
uint32 count = certs.count();
allocator.free(result[0].data()); certs.destroy(allocator); allocator.free(result[1].data()); for (uint32 n = 0; n < count; n++)
allocator.free(evidence[n].StatusCodes);
allocator.free(result[2].data()); } else {
secdebug("trusteval", "unrecognized Apple TP evidence format");
}
} else {
secdebug("trusteval", "destroying unknown TP evidence format");
for (uint32 n = 0; n < result.count(); n++)
{
allocator.free(result[n].data());
}
}
allocator.free (result.Evidence);
}
}
void Trust::clearResults()
{
StLock<Mutex>_(mMutex);
if (mResult != kSecTrustResultInvalid) {
releaseTPEvidence(mTpResult, mTP.allocator());
mResult = kSecTrustResultInvalid;
}
}
void Trust::buildEvidence(CFArrayRef &certChain, TPEvidenceInfo * &statusChain)
{
StLock<Mutex>_(mMutex);
if (mResult == kSecTrustResultInvalid)
MacOSError::throwMe(errSecTrustNotAvailable);
certChain = mEvidenceReturned =
makeCFArray(convert, mCertChain);
if(mTpResult.count() >= 3) {
statusChain = mTpResult[2].as<TPEvidenceInfo>();
}
else {
statusChain = NULL;
}
}
void Trust::extendedResult(CFDictionaryRef &result)
{
if (mResult == kSecTrustResultInvalid)
MacOSError::throwMe(errSecTrustNotAvailable);
if (mExtendedResult)
CFRetain(mExtendedResult); result = mExtendedResult;
}
CFArrayRef Trust::properties()
{
StLock<Mutex>_(mMutex);
CFMutableArrayRef properties = CFArrayCreateMutable(NULL, 0,
&kCFTypeArrayCallBacks);
if (mResult == kSecTrustResultInvalid) return properties;
for (uint32 idx=0; idx < mCertChain.size(); idx++) {
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(NULL, 0,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (dict) {
CFStringRef title = NULL;
mCertChain[idx]->inferLabel(false, &title);
if (title) {
CFDictionarySetValue(dict, (const void *)kSecPropertyTypeTitle, (const void *)title);
CFRelease(title);
}
if (idx == 0 && mTpReturn != noErr) {
CFStringRef error = SecCopyErrorMessageString(mTpReturn, NULL);
if (error) {
CFDictionarySetValue(dict, (const void *)kSecPropertyTypeError, (const void *)error);
CFRelease(error);
}
}
CFArrayAppendValue(properties, (const void *)dict);
CFRelease(dict);
}
}
return properties;
}
bool Compare_CSSM_DL_DB_HANDLE(const CSSM_DL_DB_HANDLE &h1, const CSSM_DL_DB_HANDLE &h2)
{
return (h1.DLHandle == h2.DLHandle && h1.DBHandle == h2.DBHandle);
}
Keychain Trust::keychainByDLDb(const CSSM_DL_DB_HANDLE &handle)
{
StLock<Mutex>_(mMutex);
StorageManager::KeychainList& list = searchLibs();
for (StorageManager::KeychainList::const_iterator it = list.begin();
it != list.end(); it++)
{
try
{
if (Compare_CSSM_DL_DB_HANDLE((*it)->database()->handle(), handle))
return *it;
}
catch (...)
{
}
}
if(mUsingTrustSettings) {
try {
if(Compare_CSSM_DL_DB_HANDLE(trustKeychains().rootStoreHandle(), handle)) {
return trustKeychains().rootStore();
}
if(Compare_CSSM_DL_DB_HANDLE(trustKeychains().systemKcHandle(), handle)) {
return trustKeychains().systemKc();
}
}
catch(...) {
}
}
MacOSError::throwMe(errSecInternal);
}