#include <security_keychain/Trust.h>
#include <security_cdsa_utilities/cssmdates.h>
#include <security_utilities/cfutilities.h>
#include <CoreFoundation/CFData.h>
#include <Security/SecCertificate.h>
#include "SecBridge.h"
using namespace KeychainCore;
ModuleNexus<TrustStore> Trust::gStore;
static inline CssmData cfData(CFDataRef data)
{
return CssmData(const_cast<UInt8 *>(CFDataGetBytePtr(data)),
CFDataGetLength(data));
}
Trust::Trust(CFTypeRef certificates, CFTypeRef policies)
: mTP(gGuidAppleX509TP), mAction(CSSM_TP_ACTION_DEFAULT),
mCerts(cfArrayize(certificates)), mPolicies(cfArrayize(policies)),
mResult(kSecTrustResultInvalid)
{
globals().storageManager.getSearchList(mSearchLibs);
}
Trust::~Trust() throw()
{
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()
{
clearResults();
CFToVector<CssmData, SecCertificateRef, cfCertificateData> subjects(mCerts);
CertGroup subjectCertGroup(CSSM_CERT_X_509v3,
CSSM_CERT_ENCODING_BER, CSSM_CERTGROUP_DATA);
subjectCertGroup.count() = subjects;
subjectCertGroup.blobCerts() = subjects;
TPBuildVerifyContext context(mAction);
if (mActionData)
context.actionData() = cfData(mActionData);
CFMutableArrayRef allPolicies = NULL;
uint32 numSpecAdded = 0;
uint32 numPrefAdded = 0;
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(mAnchors);
if (!anchors)
anchors = gStore().copyRootCertificates(); CFToVector<CssmData, SecCertificateRef, cfCertificateData> roots(anchors);
context.anchors(roots, roots);
vector<CSSM_DL_DB_HANDLE> dlDbList;
for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin();
it != mSearchLibs.end(); it++)
{
try
{
dlDbList.push_back((*it)->database()->handle());
}
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();
}
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(numSpecAdded) {
freeSpecifiedRevocationPolicies(allPolicies, numSpecAdded, context.allocator);
}
if(numPrefAdded) {
Trust::freePreferenceRevocationPolicies(allPolicies, numPrefAdded, context.allocator);
}
}
SecTrustResultType Trust::diagnoseOutcome()
{
switch (mTpReturn) {
case noErr: return kSecTrustResultUnspecified;
case CSSMERR_TP_CERT_EXPIRED: case CSSMERR_TP_CERT_NOT_VALID_YET: case CSSMERR_TP_NOT_TRUSTED: case CSSMERR_TP_VERIFICATION_FAILURE: case CSSMERR_TP_INVALID_ANCHOR_CERT: case CSSMERR_TP_VERIFY_ACTION_FAILED: return kSecTrustResultRecoverableTrustFailure;
case CSSMERR_TP_INVALID_CERTIFICATE: return kSecTrustResultFatalTrustFailure;
default:
return kSecTrustResultOtherError; }
}
void Trust::evaluateUserTrust(const CertGroup &chain,
const CSSM_TP_APPLE_EVIDENCE_INFO *infoList, CFCopyRef<CFArrayRef> anchors)
{
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 #%ld from keychain \"%s\"", n, keychain->name());
*static_cast<CSSM_DB_UNIQUE_RECORD_PTR *>(uniqueId) = info.UniqueRecord;
uniqueId->activate(); mCertChain[n] = safe_cast<Certificate *>(keychain->item(CSSM_DL_DB_RECORD_X509_CERTIFICATE, uniqueId).get());
} else if (info.status(CSSM_CERT_STATUS_IS_IN_INPUT_CERTS)) {
secdebug("trusteval", "evidence %ld from input cert %ld", n, 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 %ld from anchor cert %ld", n, info.index());
assert(info.index() < uint32(CFArrayGetCount(anchors)));
SecCertificateRef cert = SecCertificateRef(CFArrayGetValueAtIndex(anchors,
info.index()));
mCertChain[n] = Certificate::required(cert);
} else {
secdebug("trusteval", "evidence %ld from unknown source", 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);
}
}
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()
{
if (mResult != kSecTrustResultInvalid) {
releaseTPEvidence(mTpResult, mTP.allocator());
mResult = kSecTrustResultInvalid;
}
}
static SecCertificateRef
convert(const SecPointer<Certificate> &certificate)
{
return *certificate;
}
void Trust::buildEvidence(CFArrayRef &certChain, TPEvidenceInfo * &statusChain)
{
if (mResult == kSecTrustResultInvalid)
MacOSError::throwMe(errSecTrustNotAvailable);
certChain = mEvidenceReturned =
makeCFArray(convert, mCertChain);
if(mTpResult.count() >= 3) {
statusChain = mTpResult[2].as<TPEvidenceInfo>();
}
else {
statusChain = NULL;
}
}
Keychain Trust::keychainByDLDb(const CSSM_DL_DB_HANDLE &handle) const
{
for (StorageManager::KeychainList::const_iterator it = mSearchLibs.begin();
it != mSearchLibs.end(); it++)
{
try
{
if ((*it)->database()->handle() == handle)
return *it;
}
catch (...)
{
}
}
assert(false);
return Keychain();
}