#include <security_keychain/Trust.h>
#include <security_keychain/TrustSettingsSchema.h>
#include <security_cdsa_utilities/cssmdates.h>
#include <security_utilities/cfutilities.h>
#include <CoreFoundation/CFData.h>
#include <Security/SecCertificate.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 --
class TrustKeychains
{
public:
TrustKeychains();
~TrustKeychains() {}
CSSM_DL_DB_HANDLE rootStoreHandle() { return mRootStore->database()->handle(); }
CSSM_DL_DB_HANDLE systemKcHandle() { return mSystem->database()->handle(); }
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),
mMutex(Mutex::recursive)
{
globals().storageManager.getSearchList(mSearchLibs);
}
Trust::~Trust()
{
clearResults();
}
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();
}
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);
} else {
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);
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 (policySpecified(mPolicies, CSSMOID_APPLE_TP_SSL)) {
actionDataP->ActionFlags |= CSSM_TP_ACTION_FETCH_CERT_FROM_NET;
}
CFMutableArrayRef allPolicies = NULL;
uint32 numSpecAdded = 0;
uint32 numPrefAdded = 0;
if (isEVCandidate) {
secdebug("evTrust", "Trust::evaluate() forcing OCSP revocation checking");
allPolicies = forceOCSPRevocationPolicy(numPrefAdded, context.allocator);
}
else if (mAnchors && (CFArrayGetCount(mAnchors)==0) && (mSearchLibs.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));
}
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 = true;
secdebug("userTrust", "Trust::evaluate() using UserTrust");
}
else {
mUsingTrustSettings = false;
secdebug("userTrust", "Trust::evaluate() !UserTrust; using %s anchors",
(isEVCandidate) ? "EV" : "caller");
context.anchors(roots, roots);
}
vector<CSSM_DL_DB_HANDLE> dlDbList;
{
StLock<Mutex> _(SecTrustKeychainsGetMutex());
for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin();
it != mSearchLibs.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 {
dlDbList.push_back(trustKeychains().rootStoreHandle());
actionDataP->ActionFlags |= CSSM_TP_ACTION_TRUST_SETTINGS;
}
catch (...) {
mUsingTrustSettings = false;
}
try {
dlDbList.push_back(trustKeychains().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;
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");
}
if (isEVCandidate) {
CFArrayRef fullChain = makeCFArray(convert, mCertChain);
CFDictionaryRef evResult = extendedValidationResults(fullChain, mResult, mTpReturn);
mExtendedResult = evResult; if (evResult)
CFRelease(evResult);
CFRelease(fullChain);
secdebug("evTrust", "Trust::evaluate() post-processing complete");
} else {
mExtendedResult = NULL;
}
if(numSpecAdded) {
freeSpecifiedRevocationPolicies(allPolicies, numSpecAdded, context.allocator);
}
if(numPrefAdded) {
Trust::freePreferenceRevocationPolicies(allPolicies, numPrefAdded, context.allocator);
}
}
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);
switch (mTpReturn) {
case noErr: if (mUsingTrustSettings)
{
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();
}
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, mSearchLibs);
}
}
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;
}
Keychain Trust::keychainByDLDb(const CSSM_DL_DB_HANDLE &handle)
{
StLock<Mutex>_(mMutex);
for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin();
it != mSearchLibs.end(); it++)
{
try
{
if ((*it)->database()->handle() == handle)
return *it;
}
catch (...)
{
}
}
if(mUsingTrustSettings) {
try {
if(trustKeychains().rootStoreHandle() == handle) {
return trustKeychains().rootStore();
}
if(trustKeychains().systemKcHandle() == handle) {
return trustKeychains().systemKc();
}
}
catch(...) {
}
}
MacOSError::throwMe(errSecInternal);
}