#include "StaticCode.h"
#include "Code.h"
#include "reqmaker.h"
#include "drmaker.h"
#include "reqdumper.h"
#include "reqparser.h"
#include "sigblob.h"
#include "resources.h"
#include "detachedrep.h"
#include "csdatabase.h"
#include "csutilities.h"
#include "dirscanner.h"
#include <CoreFoundation/CFURLAccess.h>
#include <Security/SecPolicyPriv.h>
#include <Security/SecTrustPriv.h>
#include <Security/SecCertificatePriv.h>
#include <Security/CMSPrivate.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsSignerInfo.h>
#include <Security/SecCmsSignedData.h>
#include <Security/cssmapplePriv.h>
#include <security_utilities/unix++.h>
#include <security_utilities/cfmunge.h>
#include <Security/CMSDecoder.h>
#include <security_utilities/logging.h>
#include <dirent.h>
#include <sstream>
namespace Security {
namespace CodeSigning {
using namespace UnixPlusPlus;
static const char WWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.2] exists";
static const char MACWWDRRequirement[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.12] exists";
static const char developerID[] = "anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists"
" and certificate leaf[field.1.2.840.113635.100.6.1.13] exists";
static const char distributionCertificate[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.7] exists";
static const char iPhoneDistributionCert[] = "anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.4] exists";
static inline OSStatus errorForSlot(CodeDirectory::SpecialSlot slot)
{
switch (slot) {
case cdInfoSlot:
return errSecCSInfoPlistFailed;
case cdResourceDirSlot:
return errSecCSResourceDirectoryFailed;
default:
return errSecCSSignatureFailed;
}
}
SecStaticCode::SecStaticCode(DiskRep *rep)
: mRep(rep),
mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL),
mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mEvalDetails(NULL)
{
CODESIGN_STATIC_CREATE(this, rep);
CFRef<CFDataRef> codeDirectory = rep->codeDirectory();
if (codeDirectory && CFDataGetLength(codeDirectory) <= 0)
MacOSError::throwMe(errSecCSSignatureInvalid);
checkForSystemSignature();
}
SecStaticCode::~SecStaticCode() throw()
try {
::free(const_cast<Requirement *>(mDesignatedReq));
if (mResourcesValidContext)
delete mResourcesValidContext;
} catch (...) {
return;
}
bool SecStaticCode::equal(SecCFObject &secOther)
{
SecStaticCode *other = static_cast<SecStaticCode *>(&secOther);
CFDataRef mine = this->cdHash();
CFDataRef his = other->cdHash();
if (mine || his)
return mine && his && CFEqual(mine, his);
else
return CFEqual(CFRef<CFURLRef>(this->copyCanonicalPath()), CFRef<CFURLRef>(other->copyCanonicalPath()));
}
CFHashCode SecStaticCode::hash()
{
if (CFDataRef h = this->cdHash())
return CFHash(h);
else
return CFHash(CFRef<CFURLRef>(this->copyCanonicalPath()));
}
CFTypeRef SecStaticCode::reportEvent(CFStringRef stage, CFDictionaryRef info)
{
if (mMonitor)
return mMonitor(this->handle(false), stage, info);
else
return NULL;
}
void SecStaticCode::prepareProgress(unsigned int workload)
{
{
StLock<Mutex> _(mCancelLock);
mCancelPending = false; }
if (mValidationFlags & kSecCSReportProgress) {
mCurrentWork = 0; mTotalWork = workload; }
}
void SecStaticCode::reportProgress(unsigned amount )
{
if (mMonitor && (mValidationFlags & kSecCSReportProgress)) {
{
StLock<Mutex> _(mCancelLock);
if (mCancelPending)
MacOSError::throwMe(errSecCSCancelled);
}
mCurrentWork += amount;
mMonitor(this->handle(false), CFSTR("progress"), CFTemp<CFDictionaryRef>("{current=%d,total=%d}", mCurrentWork, mTotalWork));
}
}
static void addError(CFTypeRef cfError, void* context)
{
if (CFGetTypeID(cfError) == CFNumberGetTypeID()) {
int64_t error;
CFNumberGetValue(CFNumberRef(cfError), kCFNumberSInt64Type, (void*)&error);
MacOSErrorSet* errors = (MacOSErrorSet*)context;
errors->insert(OSStatus(error));
}
}
void SecStaticCode::setValidationModifiers(CFDictionaryRef conditions)
{
if (conditions) {
CFDictionary source(conditions, errSecCSDbCorrupt);
mAllowOmissions = source.get<CFArrayRef>("omissions");
if (CFArrayRef errors = source.get<CFArrayRef>("errors"))
CFArrayApplyFunction(errors, CFRangeMake(0, CFArrayGetCount(errors)), addError, &this->mTolerateErrors);
}
}
void SecStaticCode::cancelValidation()
{
StLock<Mutex> _(mCancelLock);
if (!(mValidationFlags & kSecCSReportProgress)) MacOSError::throwMe(errSecCSInvalidFlags);
mCancelPending = true;
}
void SecStaticCode::detachedSignature(CFDataRef sigData)
{
if (sigData) {
mDetachedSig = sigData;
mRep = new DetachedRep(sigData, mRep->base(), "explicit detached");
CODESIGN_STATIC_ATTACH_EXPLICIT(this, mRep);
} else {
mDetachedSig = NULL;
mRep = mRep->base();
CODESIGN_STATIC_ATTACH_EXPLICIT(this, NULL);
}
}
void SecStaticCode::checkForSystemSignature()
{
if (!this->isSigned()) {
SignatureDatabase db;
if (db.isOpen())
try {
if (RefPointer<DiskRep> dsig = db.findCode(mRep)) {
CODESIGN_STATIC_ATTACH_SYSTEM(this, dsig);
mRep = dsig;
}
} catch (...) {
}
}
}
string SecStaticCode::signatureSource()
{
if (!isSigned())
return "unsigned";
if (DetachedRep *rep = dynamic_cast<DetachedRep *>(mRep.get()))
return rep->source();
return "embedded";
}
SecStaticCode *SecStaticCode::requiredStatic(SecStaticCodeRef ref)
{
SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef);
if (SecStaticCode *scode = dynamic_cast<SecStaticCode *>(object))
return scode;
else if (SecCode *code = dynamic_cast<SecCode *>(object))
return code->staticCode();
else MacOSError::throwMe(errSecCSInvalidObjectRef);
}
SecCode *SecStaticCode::optionalDynamic(SecStaticCodeRef ref)
{
SecCFObject *object = SecCFObject::required(ref, errSecCSInvalidObjectRef);
if (dynamic_cast<SecStaticCode *>(object))
return NULL;
else if (SecCode *code = dynamic_cast<SecCode *>(object))
return code;
else MacOSError::throwMe(errSecCSInvalidObjectRef);
}
void SecStaticCode::resetValidity()
{
CODESIGN_EVAL_STATIC_RESET(this);
mValidated = false;
mExecutableValidated = mResourcesValidated = false;
if (mResourcesValidContext) {
delete mResourcesValidContext;
mResourcesValidContext = NULL;
}
mDir = NULL;
mSignature = NULL;
for (unsigned n = 0; n < cdSlotCount; n++)
mCache[n] = NULL;
mInfoDict = NULL;
mEntitlements = NULL;
mResourceDict = NULL;
mDesignatedReq = NULL;
mCDHash = NULL;
mGotResourceBase = false;
mTrust = NULL;
mCertChain = NULL;
mEvalDetails = NULL;
mRep->flush();
checkForSystemSignature();
}
CFDataRef SecStaticCode::component(CodeDirectory::SpecialSlot slot, OSStatus fail )
{
assert(slot <= cdSlotMax);
CFRef<CFDataRef> &cache = mCache[slot];
if (!cache) {
if (CFRef<CFDataRef> data = mRep->component(slot)) {
if (validated()) if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot))
MacOSError::throwMe(errorForSlot(slot)); cache = data; } else { if (validated()) if (codeDirectory()->slotIsPresent(-slot)) MacOSError::throwMe(errorForSlot(slot)); cache = CFDataRef(kCFNull); }
}
return (cache == CFDataRef(kCFNull)) ? NULL : cache.get();
}
const CodeDirectory *SecStaticCode::codeDirectory(bool check )
{
if (!mDir) {
if (mDir.take(mRep->codeDirectory())) {
const CodeDirectory *dir = reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir));
dir->checkIntegrity();
}
}
if (mDir)
return reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir));
if (check)
MacOSError::throwMe(errSecCSUnsigned);
return NULL;
}
CFDataRef SecStaticCode::cdHash()
{
if (!mCDHash) {
if (const CodeDirectory *cd = codeDirectory(false)) {
SHA1 hash;
hash(cd, cd->length());
SHA1::Digest digest;
hash.finish(digest);
mCDHash.take(makeCFData(digest, sizeof(digest)));
CODESIGN_STATIC_CDHASH(this, digest, sizeof(digest));
}
}
return mCDHash;
}
CFDataRef SecStaticCode::signature()
{
if (!mSignature)
mSignature.take(mRep->signature());
if (mSignature)
return mSignature;
MacOSError::throwMe(errSecCSUnsigned);
}
void SecStaticCode::validateDirectory()
{
if (!validated() || ((mValidationFlags & kSecCSEnforceRevocationChecks) && !revocationChecked()))
try {
CODESIGN_EVAL_STATIC_DIRECTORY(this);
mValidationExpired = verifySignature();
if (mValidationFlags & kSecCSEnforceRevocationChecks)
mRevocationChecked = true;
for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
if (mCache[slot]) validateComponent(slot, errorForSlot(slot)); mValidated = true; mValidationResult = errSecSuccess; } catch (const CommonError &err) {
mValidated = true;
mValidationResult = err.osStatus();
throw;
} catch (...) {
secdebug("staticCode", "%p validation threw non-common exception", this);
mValidated = true;
mValidationResult = errSecCSInternalError;
throw;
}
assert(validated());
if (mValidationResult == errSecSuccess) {
if (mValidationExpired)
if ((mValidationFlags & kSecCSConsiderExpiration)
|| (codeDirectory()->flags & kSecCodeSignatureForceExpiration))
MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED);
} else
MacOSError::throwMe(mValidationResult);
}
void SecStaticCode::validateNonResourceComponents()
{
this->validateDirectory();
for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
switch (slot) {
case cdResourceDirSlot: break;
default:
this->component(slot); break;
}
}
CFAbsoluteTime SecStaticCode::signingTime()
{
validateDirectory();
return mSigningTime;
}
CFAbsoluteTime SecStaticCode::signingTimestamp()
{
validateDirectory();
return mSigningTimestamp;
}
bool SecStaticCode::verifySignature()
{
if (flag(kSecCodeSignatureAdhoc)) {
CODESIGN_EVAL_STATIC_SIGNATURE_ADHOC(this);
return false;
}
DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str());
CFRef<CMSDecoderRef> cms;
MacOSError::check(CMSDecoderCreate(&cms.aref())); CFDataRef sig = this->signature();
MacOSError::check(CMSDecoderUpdateMessage(cms, CFDataGetBytePtr(sig), CFDataGetLength(sig)));
this->codeDirectory(); MacOSError::check(CMSDecoderSetDetachedContent(cms, mDir));
MacOSError::check(CMSDecoderFinalizeMessage(cms));
MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray()));
CFRef<CFArrayRef> vf_policies = verificationPolicies();
CFRef<CFArrayRef> ts_policies = SecPolicyCreateAppleTimeStampingAndRevocationPolicies(vf_policies);
CMSSignerStatus status;
MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies,
false, &status, &mTrust.aref(), NULL));
if (status != kCMSSignerValid)
MacOSError::throwMe(errSecCSSignatureFailed);
mSigningTime = 0; switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) {
case errSecSuccess:
case errSecSigningTimeMissing:
break;
default:
MacOSError::throwMe(rc);
}
mSigningTimestamp = 0;
switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) {
case errSecSuccess:
case errSecTimestampMissing:
break;
default:
MacOSError::throwMe(rc);
}
if (mValidationFlags & kSecCSNoNetworkAccess) {
MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); }
MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); MacOSError::check(SecTrustSetKeychains(mTrust, cfEmptyArray())); CSSM_APPLE_TP_ACTION_DATA actionData = {
CSSM_APPLE_TP_ACTION_VERSION, CSSM_TP_ACTION_IMPLICIT_ANCHORS };
for (;;) { MacOSError::check(SecTrustSetParameters(mTrust,
CSSM_TP_ACTION_DEFAULT, CFTempData(&actionData, sizeof(actionData))));
SecTrustResultType trustResult;
MacOSError::check(SecTrustEvaluate(mTrust, &trustResult));
MacOSError::check(SecTrustGetResult(mTrust, &trustResult, &mCertChain.aref(), &mEvalDetails));
if (teamID() && SecStaticCode::isAppleDeveloperCert(mCertChain)) {
CFRef<CFStringRef> teamIDFromCert;
if (CFArrayGetCount(mCertChain) > 0) {
MacOSError::check(SecCertificateCopySubjectComponent((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, Requirement::leafCert),
&CSSMOID_OrganizationalUnitName,
&teamIDFromCert.aref()));
if (teamIDFromCert) {
CFRef<CFStringRef> teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8);
if (!teamIDFromCD) {
MacOSError::throwMe(errSecCSInternalError);
}
if (CFStringCompare(teamIDFromCert, teamIDFromCD, 0) != kCFCompareEqualTo) {
Security::Syslog::error("Team identifier in the signing certificate (%s) does not match the team identifier (%s) in the code directory", cfString(teamIDFromCert).c_str(), teamID());
MacOSError::throwMe(errSecCSSignatureInvalid);
}
}
}
}
CODESIGN_EVAL_STATIC_SIGNATURE_RESULT(this, trustResult, mCertChain ? (int)CFArrayGetCount(mCertChain) : 0);
switch (trustResult) {
case kSecTrustResultProceed:
case kSecTrustResultUnspecified:
break; case kSecTrustResultDeny:
MacOSError::throwMe(CSSMERR_APPLETP_TRUST_SETTING_DENY); case kSecTrustResultInvalid:
assert(false); MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED);
default:
{
OSStatus result;
MacOSError::check(SecTrustGetCssmResultCode(mTrust, &result));
if (mSigningTimestamp == 0) if (((result == CSSMERR_TP_CERT_EXPIRED) || (result == CSSMERR_TP_CERT_NOT_VALID_YET))
&& !(actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED)) {
CODESIGN_EVAL_STATIC_SIGNATURE_EXPIRED(this);
actionData.ActionFlags |= CSSM_TP_ACTION_ALLOW_EXPIRED; continue; }
MacOSError::throwMe(result);
}
}
if (mSigningTimestamp) {
CFIndex rootix = CFArrayGetCount(mCertChain);
if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1)))
if (isAppleCA(mainRoot)) {
CFRef<CFArrayRef> tsCerts;
MacOSError::check(CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref()));
CFIndex tsn = CFArrayGetCount(tsCerts);
bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1)));
if (!good)
MacOSError::throwMe(CSSMERR_TP_NOT_TRUSTED);
}
}
return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED;
}
}
static SecPolicyRef makeCRLPolicy()
{
CFRef<SecPolicyRef> policy;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_CRL, &policy.aref()));
CSSM_APPLE_TP_CRL_OPTIONS options;
memset(&options, 0, sizeof(options));
options.Version = CSSM_APPLE_TP_CRL_OPTS_VERSION;
options.CrlFlags = CSSM_TP_ACTION_FETCH_CRL_FROM_NET | CSSM_TP_ACTION_CRL_SUFFICIENT;
CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
MacOSError::check(SecPolicySetValue(policy, &optData));
return policy.yield();
}
static SecPolicyRef makeOCSPPolicy()
{
CFRef<SecPolicyRef> policy;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_REVOCATION_OCSP, &policy.aref()));
CSSM_APPLE_TP_OCSP_OPTIONS options;
memset(&options, 0, sizeof(options));
options.Version = CSSM_APPLE_TP_OCSP_OPTS_VERSION;
options.Flags = CSSM_TP_ACTION_OCSP_SUFFICIENT;
CSSM_DATA optData = { sizeof(options), (uint8 *)&options };
MacOSError::check(SecPolicySetValue(policy, &optData));
return policy.yield();
}
CFArrayRef SecStaticCode::verificationPolicies()
{
CFRef<SecPolicyRef> core;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
&CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref()));
if (mValidationFlags & kSecCSNoNetworkAccess) {
CFRef<SecPolicyRef> no_revoc = SecPolicyCreateRevocation(kSecRevocationNetworkAccessDisabled);
return makeCFArray(2, core.get(), no_revoc.get());
}
else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
CFRef<SecPolicyRef> crl = makeCRLPolicy();
CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
return makeCFArray(3, core.get(), crl.get(), ocsp.get());
} else {
return makeCFArray(1, core.get());
}
}
void SecStaticCode::validateComponent(CodeDirectory::SpecialSlot slot, OSStatus fail )
{
assert(slot <= cdSlotMax);
CFDataRef data = mCache[slot];
assert(data); if (data == CFDataRef(kCFNull)) {
if (codeDirectory()->slotIsPresent(-slot)) MacOSError::throwMe(fail); } else {
if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot))
MacOSError::throwMe(fail);
}
}
void SecStaticCode::validateExecutable()
{
if (!validatedExecutable()) {
try {
DTRACK(CODESIGN_EVAL_STATIC_EXECUTABLE, this,
(char*)this->mainExecutablePath().c_str(), codeDirectory()->nCodeSlots);
const CodeDirectory *cd = this->codeDirectory();
if (!cd)
MacOSError::throwMe(errSecCSUnsigned);
AutoFileDesc fd(mainExecutablePath(), O_RDONLY);
fd.fcntl(F_NOCACHE, true); if (Universal *fat = mRep->mainExecutableImage())
fd.seek(fat->archOffset());
size_t pageSize = cd->pageSize ? (1 << cd->pageSize) : 0;
size_t remaining = cd->codeLimit;
for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) {
size_t size = min(remaining, pageSize);
if (!cd->validateSlot(fd, size, slot)) {
CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot);
MacOSError::throwMe(errSecCSSignatureFailed);
}
remaining -= size;
}
mExecutableValidated = true;
mExecutableValidResult = errSecSuccess;
} catch (const CommonError &err) {
mExecutableValidated = true;
mExecutableValidResult = err.osStatus();
throw;
} catch (...) {
secdebug("staticCode", "%p executable validation threw non-common exception", this);
mExecutableValidated = true;
mExecutableValidResult = errSecCSInternalError;
throw;
}
}
assert(validatedExecutable());
if (mExecutableValidResult != errSecSuccess)
MacOSError::throwMe(mExecutableValidResult);
}
unsigned SecStaticCode::estimateResourceWorkload()
{
CFDictionaryRef sealedResources = resourceDictionary();
CFDictionaryRef files = cfget<CFDictionaryRef>(sealedResources, "files2");
if (files == NULL)
files = cfget<CFDictionaryRef>(sealedResources, "files");
return files ? unsigned(CFDictionaryGetCount(files)) : 0;
}
void SecStaticCode::validateResources(SecCSFlags flags)
{
bool doit = true;
if (mResourcesValidated) { if (!(flags & kSecCSCheckNestedCode) || mResourcesDeep) doit = false;
}
if (doit) {
try {
CFDictionaryRef sealedResources = resourceDictionary();
if (this->resourceBase()) if (sealedResources)
;
else
MacOSError::throwMe(errSecCSResourcesNotFound);
else if (sealedResources)
MacOSError::throwMe(errSecCSResourcesNotFound);
else
return;
DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
(char*)this->mainExecutablePath().c_str(), 0);
if (mValidationFlags & kSecCSFullReport)
mResourcesValidContext = new CollectingContext(*this); else
mResourcesValidContext = new ValidationContext(*this);
CFDictionaryRef rules;
CFDictionaryRef files;
uint32_t version;
if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { rules = cfget<CFDictionaryRef>(sealedResources, "rules2");
files = cfget<CFDictionaryRef>(sealedResources, "files2");
version = 2;
} else { rules = cfget<CFDictionaryRef>(sealedResources, "rules");
files = cfget<CFDictionaryRef>(sealedResources, "files");
version = 1;
}
if (!rules || !files)
MacOSError::throwMe(errSecCSResourcesInvalid);
bool strict = flags & kSecCSStrictValidate;
if (strict) {
if (hasWeakResourceRules(rules, version, mAllowOmissions))
if (mTolerateErrors.find(errSecCSWeakResourceRules) == mTolerateErrors.end())
MacOSError::throwMe(errSecCSWeakResourceRules);
if (version == 1)
if (mTolerateErrors.find(errSecCSWeakResourceEnvelope) == mTolerateErrors.end())
MacOSError::throwMe(errSecCSWeakResourceEnvelope);
}
__block CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files);
string base = cfString(this->resourceBase());
ResourceBuilder resources(base, base, rules, codeDirectory()->hashType, strict, mTolerateErrors);
diskRep()->adjustResources(resources);
resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const char *relpath, ResourceBuilder::Rule *rule) {
validateResource(files, relpath, ent->fts_info == FTS_SL, *mResourcesValidContext, flags, version);
reportProgress();
CFDictionaryRemoveValue(resourceMap, CFTempString(relpath));
});
unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap));
if (leftovers > 0) {
secdebug("staticCode", "%d sealed resource(s) not found in code", int(leftovers));
CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext);
}
mResourcesValidated = true;
mResourcesDeep = flags & kSecCSCheckNestedCode;
if (mResourcesValidContext->osStatus() != errSecSuccess)
mResourcesValidContext->throwMe();
} catch (const CommonError &err) {
mResourcesValidated = true;
mResourcesDeep = flags & kSecCSCheckNestedCode;
mResourcesValidResult = err.osStatus();
throw;
} catch (...) {
secdebug("staticCode", "%p executable validation threw non-common exception", this);
mResourcesValidated = true;
mResourcesDeep = flags & kSecCSCheckNestedCode;
mResourcesValidResult = errSecCSInternalError;
throw;
}
}
assert(validatedResources());
if (mResourcesValidResult)
MacOSError::throwMe(mResourcesValidResult);
if (mResourcesValidContext->osStatus() != errSecSuccess)
mResourcesValidContext->throwMe();
}
void SecStaticCode::checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context)
{
ValidationContext *ctx = static_cast<ValidationContext *>(context);
ResourceSeal seal(value);
if (!seal.optional()) {
if (key && CFGetTypeID(key) == CFStringGetTypeID()) {
CFTempURL tempURL(CFStringRef(key), false, ctx->code.resourceBase());
if (!tempURL.get()) {
ctx->reportProblem(errSecCSBadDictionaryFormat, kSecCFErrorResourceSeal, key);
} else {
ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, tempURL);
}
} else {
ctx->reportProblem(errSecCSBadResource, kSecCFErrorResourceSeal, key);
}
}
}
static bool isOmitRule(CFTypeRef value)
{
if (CFGetTypeID(value) == CFBooleanGetTypeID())
return value == kCFBooleanFalse;
CFDictionary rule(value, errSecCSResourceRulesInvalid);
return rule.get<CFBooleanRef>("omit") == kCFBooleanTrue;
}
bool SecStaticCode::hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t version, CFArrayRef allowedOmissions)
{
CFRef<CFArrayRef> defaultOmissions = this->diskRep()->allowedResourceOmissions();
if (!defaultOmissions)
MacOSError::throwMe(errSecCSInternalError);
CFRef<CFMutableArrayRef> allowed = CFArrayCreateMutableCopy(NULL, 0, defaultOmissions);
if (allowedOmissions)
CFArrayAppendArray(allowed, allowedOmissions, CFRangeMake(0, CFArrayGetCount(allowedOmissions)));
CFRange range = CFRangeMake(0, CFArrayGetCount(allowed));
string catchAllRule = (version == 1) ? "^Resources/" : "^.*";
__block bool coversAll = false;
__block bool forbiddenOmission = false;
CFDictionary rules(rulesDict, errSecCSResourceRulesInvalid);
rules.apply(^(CFStringRef key, CFTypeRef value) {
string pattern = cfString(key, errSecCSResourceRulesInvalid);
if (pattern == catchAllRule && value == kCFBooleanTrue) {
coversAll = true;
return;
}
if (isOmitRule(value))
forbiddenOmission |= !CFArrayContainsValue(allowed, range, key);
});
return !coversAll || forbiddenOmission;
}
CFDictionaryRef SecStaticCode::infoDictionary()
{
if (!mInfoDict) {
mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed));
secdebug("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get());
}
return mInfoDict;
}
CFDictionaryRef SecStaticCode::entitlements()
{
if (!mEntitlements) {
validateDirectory();
if (CFDataRef entitlementData = component(cdEntitlementSlot)) {
validateComponent(cdEntitlementSlot);
const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlementData));
if (blob->validateBlob()) {
mEntitlements.take(blob->entitlements());
secdebug("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get());
}
}
}
return mEntitlements;
}
CFDictionaryRef SecStaticCode::resourceDictionary(bool check )
{
if (mResourceDict) return mResourceDict;
if (CFRef<CFDictionaryRef> dict = getDictionary(cdResourceDirSlot, check))
if (cfscan(dict, "{rules=%Dn,files=%Dn}")) {
secdebug("staticCode", "%p loaded ResourceDict %p",
this, mResourceDict.get());
return mResourceDict = dict;
}
return NULL;
}
CFURLRef SecStaticCode::resourceBase()
{
if (!mGotResourceBase) {
string base = mRep->resourcesRootPath();
if (!base.empty())
mResourceBase.take(makeCFURL(base, true));
mGotResourceBase = true;
}
return mResourceBase;
}
CFDictionaryRef SecStaticCode::getDictionary(CodeDirectory::SpecialSlot slot, bool check )
{
if (check)
validateDirectory();
if (CFDataRef infoData = component(slot)) {
validateComponent(slot);
if (CFDictionaryRef dict = makeCFDictionaryFrom(infoData))
return dict;
else
MacOSError::throwMe(errSecCSBadDictionaryFormat);
}
return NULL;
}
CFDataRef SecStaticCode::resource(string path, ValidationContext &ctx)
{
if (CFDictionaryRef rdict = resourceDictionary()) {
if (CFTypeRef file = cfget(rdict, "files.%s", path.c_str())) {
ResourceSeal seal = file;
if (!resourceBase()) MacOSError::throwMe(errSecCSResourcesNotFound);
if (seal.nested())
MacOSError::throwMe(errSecCSResourcesNotSealed); CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase());
if (CFRef<CFDataRef> data = cfLoadFile(fullpath)) {
MakeHash<CodeDirectory> hasher(this->codeDirectory());
hasher->update(CFDataGetBytePtr(data), CFDataGetLength(data));
if (hasher->verify(seal.hash()))
return data.yield(); else
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); } else {
if (!seal.optional())
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); else
return NULL; }
} else
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
return NULL;
} else
MacOSError::throwMe(errSecCSResourcesNotSealed);
}
CFDataRef SecStaticCode::resource(string path)
{
ValidationContext ctx(*this);
return resource(path, ctx);
}
void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version)
{
if (!resourceBase()) MacOSError::throwMe(errSecCSResourcesNotFound);
CFRef<CFURLRef> fullpath = makeCFURL(path, false, resourceBase());
if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
ResourceSeal seal = file;
if (seal.nested()) {
if (isSymlink)
return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); string suffix = ".framework";
bool isFramework = (path.length() > suffix.length())
&& (path.compare(path.length()-suffix.length(), suffix.length(), suffix) == 0);
validateNestedCode(fullpath, seal, flags, isFramework);
} else if (seal.link()) {
char target[PATH_MAX];
ssize_t len = ::readlink(cfString(fullpath).c_str(), target, sizeof(target)-1);
if (len < 0)
UnixError::check(-1);
target[len] = '\0';
if (cfString(seal.link()) != target)
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath);
} else if (seal.hash()) { AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); if (fd) {
MakeHash<CodeDirectory> hasher(this->codeDirectory());
hashFileData(fd, hasher.get());
if (hasher->verify(seal.hash()))
return; else
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); } else {
if (!seal.optional())
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); else
return; }
} else
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); return;
}
if (version == 1) { char target[PATH_MAX];
if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0)
return;
}
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
}
void SecStaticCode::validateNestedCode(CFURLRef path, const ResourceSeal &seal, SecCSFlags flags, bool isFramework)
{
CFRef<SecRequirementRef> req;
if (SecRequirementCreateWithString(seal.requirement(), kSecCSDefaultFlags, &req.aref()))
MacOSError::throwMe(errSecCSResourcesInvalid);
try {
if (!(flags & kSecCSCheckNestedCode))
flags |= kSecCSBasicValidateOnly;
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path)));
code->setMonitor(this->monitor());
code->staticValidate(flags, SecRequirement::required(req));
if (isFramework && (flags & kSecCSStrictValidate))
try {
validateOtherVersions(path, flags, req, code);
} catch (const CSError &err) {
MacOSError::throwMe(errSecCSBadFrameworkVersion);
} catch (const MacOSError &err) {
MacOSError::throwMe(errSecCSBadFrameworkVersion);
}
} catch (CSError &err) {
if (err.error == errSecCSReqFailed) {
mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path);
return;
}
err.augment(kSecCFErrorPath, path);
throw;
} catch (const MacOSError &err) {
if (err.error == errSecCSReqFailed) {
mResourcesValidContext->reportProblem(errSecCSBadNestedCode, kSecCFErrorResourceAltered, path);
return;
}
CSError::throwMe(err.error, kSecCFErrorPath, path);
}
}
void SecStaticCode::validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code)
{
std::string mainPath = cfStringRelease(code->diskRep()->copyCanonicalPath());
char main_path[PATH_MAX];
bool foundTarget = false;
if (realpath(mainPath.c_str(), main_path) != NULL)
foundTarget = true;
std::ostringstream versionsPath;
versionsPath << cfString(path) << "/Versions/";
DirScanner scanner(versionsPath.str());
if (scanner.initialized()) {
struct dirent *entry = NULL;
while ((entry = scanner.getNext()) != NULL) {
std::ostringstream fullPath;
if (entry->d_type != DT_DIR ||
strcmp(entry->d_name, ".") == 0 ||
strcmp(entry->d_name, "..") == 0 ||
strcmp(entry->d_name, "Current") == 0)
continue;
fullPath << versionsPath.str() << entry->d_name;
char real_full_path[PATH_MAX];
if (realpath(fullPath.str().c_str(), real_full_path) == NULL)
UnixError::check(-1);
if (foundTarget && strcmp(main_path, real_full_path) == 0)
continue;
SecPointer<SecStaticCode> frameworkVersion = new SecStaticCode(DiskRep::bestGuess(real_full_path));
frameworkVersion->setMonitor(this->monitor());
frameworkVersion->staticValidate(flags, SecRequirement::required(req));
}
}
}
bool SecStaticCode::flag(uint32_t tested)
{
if (const CodeDirectory *cd = this->codeDirectory(false))
return cd->flags & tested;
else
return false;
}
const Requirements *SecStaticCode::internalRequirements()
{
if (CFDataRef reqData = component(cdRequirementsSlot)) {
const Requirements *req = (const Requirements *)CFDataGetBytePtr(reqData);
if (!req->validateBlob())
MacOSError::throwMe(errSecCSReqInvalid);
return req;
} else
return NULL;
}
const Requirement *SecStaticCode::internalRequirement(SecRequirementType type)
{
if (const Requirements *reqs = internalRequirements())
return reqs->find<Requirement>(type);
else
return NULL;
}
const Requirement *SecStaticCode::designatedRequirement()
{
if (const Requirement *req = internalRequirement(kSecDesignatedRequirementType)) {
return req; } else {
if (!mDesignatedReq)
mDesignatedReq = defaultDesignatedRequirement();
return mDesignatedReq;
}
}
const Requirement *SecStaticCode::defaultDesignatedRequirement()
{
if (flag(kSecCodeSignatureAdhoc)) {
__block Requirement::Maker maker;
Requirement::Maker::Chain chain(maker, opOr);
chain.add();
maker.cdhash(this->cdHash());
handleOtherArchitectures(^(SecStaticCode *subcode) {
if (CFDataRef cdhash = subcode->cdHash()) {
chain.add();
maker.cdhash(cdhash);
}
});
return maker.make();
} else {
validateDirectory(); Requirement::Context context(this->certificates(),
this->infoDictionary(),
this->entitlements(),
this->identifier(),
this->codeDirectory()
);
return DRMaker(context).make();
}
}
void SecStaticCode::validateRequirements(SecRequirementType type, SecStaticCode *target,
OSStatus nullError )
{
DTRACK(CODESIGN_EVAL_STATIC_INTREQ, this, type, target, nullError);
if (const Requirement *req = internalRequirement(type))
target->validateRequirement(req, nullError ? nullError : errSecCSReqFailed);
else if (nullError)
MacOSError::throwMe(nullError);
else
;
}
bool SecStaticCode::satisfiesRequirement(const Requirement *req, OSStatus failure)
{
assert(req);
validateDirectory();
return req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure);
}
void SecStaticCode::validateRequirement(const Requirement *req, OSStatus failure)
{
if (!this->satisfiesRequirement(req, failure))
MacOSError::throwMe(failure);
}
SecCertificateRef SecStaticCode::cert(int ix)
{
validateDirectory(); if (mCertChain) {
CFIndex length = CFArrayGetCount(mCertChain);
if (ix < 0)
ix += length;
if (ix >= 0 && ix < length)
return SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, ix));
}
return NULL;
}
CFArrayRef SecStaticCode::certificates()
{
validateDirectory(); return mCertChain;
}
CFDictionaryRef SecStaticCode::signingInformation(SecCSFlags flags)
{
CFRef<CFMutableDictionaryRef> dict = makeCFMutableDictionary(1,
kSecCodeInfoMainExecutable, CFTempURL(this->mainExecutablePath()).get()
);
if (!this->isSigned())
return dict.yield();
CFDictionaryAddValue(dict, kSecCodeInfoIdentifier, CFTempString(this->identifier()));
CFDictionaryAddValue(dict, kSecCodeInfoFlags, CFTempNumber(this->codeDirectory(false)->flags.get()));
CFDictionaryAddValue(dict, kSecCodeInfoFormat, CFTempString(this->format()));
CFDictionaryAddValue(dict, kSecCodeInfoSource, CFTempString(this->signatureSource()));
CFDictionaryAddValue(dict, kSecCodeInfoUnique, this->cdHash());
CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(this->codeDirectory(false)->hashType));
try {
if (CFDictionaryRef info = this->infoDictionary())
CFDictionaryAddValue(dict, kSecCodeInfoPList, info);
} catch (...) { }
if (flags & kSecCSSigningInformation)
try {
if (CFArrayRef certs = this->certificates())
CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs);
if (CFDataRef sig = this->signature())
CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig);
if (mTrust)
CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust);
if (CFAbsoluteTime time = this->signingTime())
if (CFRef<CFDateRef> date = CFDateCreate(NULL, time))
CFDictionaryAddValue(dict, kSecCodeInfoTime, date);
if (CFAbsoluteTime time = this->signingTimestamp())
if (CFRef<CFDateRef> date = CFDateCreate(NULL, time))
CFDictionaryAddValue(dict, kSecCodeInfoTimestamp, date);
if (const char *teamID = this->teamID())
CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID));
} catch (...) { }
if (flags & kSecCSRequirementInformation)
try {
if (const Requirements *reqs = this->internalRequirements()) {
CFDictionaryAddValue(dict, kSecCodeInfoRequirements,
CFTempString(Dumper::dump(reqs)));
CFDictionaryAddValue(dict, kSecCodeInfoRequirementData, CFTempData(*reqs));
}
const Requirement *dreq = this->designatedRequirement();
CFRef<SecRequirementRef> dreqRef = (new SecRequirement(dreq))->handle();
CFDictionaryAddValue(dict, kSecCodeInfoDesignatedRequirement, dreqRef);
if (this->internalRequirement(kSecDesignatedRequirementType)) { CFRef<SecRequirementRef> ddreqRef = (new SecRequirement(this->defaultDesignatedRequirement(), true))->handle();
CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, ddreqRef);
} else { CFDictionaryAddValue(dict, kSecCodeInfoImplicitDesignatedRequirement, dreqRef);
}
} catch (...) { }
try {
if (CFDataRef ent = this->component(cdEntitlementSlot)) {
CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent);
if (CFDictionaryRef entdict = this->entitlements())
CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict);
}
} catch (...) { }
if (flags & kSecCSInternalInformation)
try {
if (mDir)
CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir);
CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase()));
if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict);
} catch (...) { }
if (flags & kSecCSContentInformation)
if (CFRef<CFArrayRef> files = mRep->modifiedFiles())
CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files);
return dict.yield();
}
SecStaticCode::ValidationContext::~ValidationContext()
{ }
void SecStaticCode::ValidationContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value)
{
CSError::throwMe(rc, type, value);
}
void SecStaticCode::CollectingContext::reportProblem(OSStatus rc, CFStringRef type, CFTypeRef value)
{
if (mStatus == errSecSuccess)
mStatus = rc; if (type) {
if (!mCollection)
mCollection.take(makeCFMutableDictionary());
CFMutableArrayRef element = CFMutableArrayRef(CFDictionaryGetValue(mCollection, type));
if (!element) {
element = makeCFMutableArray(0);
if (!element)
CFError::throwMe();
CFDictionaryAddValue(mCollection, type, element);
CFRelease(element);
}
CFArrayAppendValue(element, value);
}
}
void SecStaticCode::CollectingContext::throwMe()
{
assert(mStatus != errSecSuccess);
throw CSError(mStatus, mCollection.retain());
}
void SecStaticCode::staticValidate(SecCSFlags flags, const SecRequirement *req)
{
setValidationFlags(flags);
prepareProgress(estimateResourceWorkload() + 2);
this->staticValidateCore(flags, req);
if (flags & kSecCSCheckAllArchitectures)
handleOtherArchitectures(^(SecStaticCode* subcode) {
subcode->detachedSignature(this->mDetachedSig); subcode->staticValidateCore(flags, req);
});
reportProgress();
reportEvent(CFSTR("prepared"), NULL);
if (!(flags & kSecCSDoNotValidateResources))
this->validateResources(flags);
if (flags & kSecCSStrictValidate)
mRep->strictValidate(mTolerateErrors);
reportProgress();
if (CFRef<CFTypeRef> veto = reportEvent(CFSTR("validated"), NULL)) {
if (CFGetTypeID(veto) == CFNumberGetTypeID())
MacOSError::throwMe(cfNumber<OSStatus>(veto.as<CFNumberRef>()));
else
MacOSError::throwMe(errSecCSBadCallbackValue);
}
}
void SecStaticCode::staticValidateCore(SecCSFlags flags, const SecRequirement *req)
{
try {
this->validateNonResourceComponents(); if (!(flags & kSecCSDoNotValidateExecutable))
this->validateExecutable();
if (req)
this->validateRequirement(req->requirement(), errSecCSReqFailed);
} catch (CSError &err) {
if (Universal *fat = this->diskRep()->mainExecutableImage()) if (MachO *mach = fat->architecture()) {
err.augment(kSecCFErrorArchitecture, CFTempString(mach->architecture().displayName()));
delete mach;
}
throw;
} catch (const MacOSError &err) {
if (Universal *fat = this->diskRep()->mainExecutableImage())
if (MachO *mach = fat->architecture()) {
CFTempString arch(mach->architecture().displayName());
delete mach;
CSError::throwMe(err.error, kSecCFErrorArchitecture, arch);
}
throw;
}
}
void SecStaticCode::handleOtherArchitectures(void (^handle)(SecStaticCode* other))
{
if (Universal *fat = this->diskRep()->mainExecutableImage()) {
Universal::Architectures architectures;
fat->architectures(architectures);
if (architectures.size() > 1) {
DiskRep::Context ctx;
size_t activeOffset = fat->archOffset();
for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) {
ctx.offset = fat->archOffset(*arch);
if (ctx.offset > SIZE_MAX)
MacOSError::throwMe(errSecCSInternalError);
ctx.size = fat->lengthOfSlice((size_t)ctx.offset);
if (ctx.offset != activeOffset) { SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx));
subcode->detachedSignature(this->mDetachedSig); if (this->teamID() == NULL || subcode->teamID() == NULL) {
if (this->teamID() != subcode->teamID())
MacOSError::throwMe(errSecCSSignatureInvalid);
} else if (strcmp(this->teamID(), subcode->teamID()) != 0)
MacOSError::throwMe(errSecCSSignatureInvalid);
handle(subcode);
}
}
}
}
}
bool SecStaticCode::isAppleDeveloperCert(CFArrayRef certs)
{
static const std::string appleDeveloperRequirement = "(" + std::string(WWDRRequirement) + ") or (" + MACWWDRRequirement + ") or (" + developerID + ") or (" + distributionCertificate + ") or (" + iPhoneDistributionCert + ")";
SecPointer<SecRequirement> req = new SecRequirement(parseRequirement(appleDeveloperRequirement), true);
Requirement::Context ctx(certs, NULL, NULL, "", NULL);
return req->requirement()->validates(ctx);
}
} }