#include "StaticCode.h"
#include "Code.h"
#include "reqmaker.h"
#if TARGET_OS_OSX
#include "drmaker.h"
#include "notarization.h"
#endif
#include "reqdumper.h"
#include "reqparser.h"
#include "sigblob.h"
#include "resources.h"
#include "detachedrep.h"
#include "signerutils.h"
#if TARGET_OS_OSX
#include "csdatabase.h"
#endif
#include "dirscanner.h"
#include <CoreFoundation/CFURLAccess.h>
#include <Security/SecPolicyPriv.h>
#include <Security/SecTrustPriv.h>
#include <Security/SecCertificatePriv.h>
#if TARGET_OS_OSX
#include <Security/CMSPrivate.h>
#endif
#import <Security/SecCMS.h>
#include <Security/SecCmsContentInfo.h>
#include <Security/SecCmsSignerInfo.h>
#include <Security/SecCmsSignedData.h>
#if TARGET_OS_OSX
#include <Security/cssmapplePriv.h>
#endif
#include <security_utilities/unix++.h>
#include <security_utilities/cfmunge.h>
#include <security_utilities/casts.h>
#include <Security/CMSDecoder.h>
#include <security_utilities/logging.h>
#include <dirent.h>
#include <sys/xattr.h>
#include <sstream>
#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
#include <dispatch/private.h>
#include <os/assumes.h>
#include <regex.h>
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, uint32_t flags)
: mCheckfix30814861builder1(NULL),
mRep(rep),
mValidated(false), mExecutableValidated(false), mResourcesValidated(false), mResourcesValidContext(NULL),
mProgressQueue("com.apple.security.validation-progress", false, QOS_CLASS_UNSPECIFIED),
mOuterScope(NULL), mResourceScope(NULL),
mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL),
mFlags(flags), mNotarizationChecked(false), mStaplingChecked(false), mNotarizationDate(NAN)
#if TARGET_OS_OSX
, mEvalDetails(NULL)
#else
, mTrustedSigningCertChain(false)
#endif
{
CODESIGN_STATIC_CREATE(this, rep);
#if TARGET_OS_OSX
checkForSystemSignature();
#endif
}
SecStaticCode::~SecStaticCode() throw()
try {
::free(const_cast<Requirement *>(mDesignatedReq));
delete mResourcesValidContext;
delete mLimitedAsync;
delete mCheckfix30814861builder1;
} catch (...) {
return;
}
void SecStaticCode::initializeFromParent(const SecStaticCode& parent) {
mOuterScope = &parent;
setMonitor(parent.monitor());
if (parent.mLimitedAsync)
mLimitedAsync = new LimitedAsync(*parent.mLimitedAsync);
}
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)
{
dispatch_sync(mProgressQueue, ^{
mCancelPending = false; });
if (mValidationFlags & kSecCSReportProgress) {
mCurrentWork = 0; mTotalWork = workload; }
}
void SecStaticCode::reportProgress(unsigned amount )
{
if (mMonitor && (mValidationFlags & kSecCSReportProgress)) {
__block bool cancel = false;
dispatch_sync(mProgressQueue, ^{
if (mCancelPending)
cancel = true;
mCurrentWork += amount;
mMonitor(this->handle(false), CFSTR("progress"), CFTemp<CFDictionaryRef>("{current=%d,total=%d}", mCurrentWork, mTotalWork));
});
if (cancel)
MacOSError::throwMe(errSecCSCancelled);
}
}
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()
{
if (!(mValidationFlags & kSecCSReportProgress)) MacOSError::throwMe(errSecCSInvalidFlags);
dispatch_assert_queue(mProgressQueue);
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 TARGET_OS_OSX
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 (...) {
}
}
#else
MacOSError::throwMe(errSecUnimplemented);
#endif
}
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;
mCodeDirectories.clear();
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;
#if TARGET_OS_OSX
mEvalDetails = NULL;
#endif
mRep->flush();
#if TARGET_OS_OSX
checkForSystemSignature();
#endif
}
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()->slotIsPresent(-slot))
return NULL;
if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false))
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 SecStaticCode::CodeDirectoryMap *
SecStaticCode::codeDirectories(bool check ) const
{
if (mCodeDirectories.empty()) {
try {
loadCodeDirectories(mCodeDirectories);
} catch (...) {
if (check)
throw;
if (!mCodeDirectories.empty()) {
assert(false);
Syslog::warning("code signing internal problem: mCodeDirectories set despite exception exit");
MacOSError::throwMe(errSecCSInternalError);
}
}
} else {
return &mCodeDirectories;
}
if (!mCodeDirectories.empty()) {
return &mCodeDirectories;
}
if (check) {
MacOSError::throwMe(errSecCSUnsigned);
}
return NULL;
}
const CodeDirectory *SecStaticCode::codeDirectory(bool check ) const
{
if (!mDir) {
try {
CodeDirectoryMap const *candidates = codeDirectories(check);
if (candidates != NULL) {
CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
mDir = candidates->at(type); }
} catch (...) {
if (check)
throw;
if (mDir) {
assert(false);
Syslog::warning("code signing internal problem: mDir set despite exception exit");
MacOSError::throwMe(errSecCSInternalError);
}
}
}
if (mDir)
return reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(mDir));
if (check)
MacOSError::throwMe(errSecCSUnsigned);
return NULL;
}
bool SecStaticCode::loadCodeDirectories(CodeDirectoryMap& cdMap) const
{
__block CodeDirectoryMap candidates;
__block CodeDirectory::HashAlgorithms hashAlgorithms;
__block CFRef<CFDataRef> baseDir;
auto add = ^bool (CodeDirectory::SpecialSlot slot){
CFRef<CFDataRef> cdData = diskRep()->component(slot);
if (!cdData)
return false;
const CodeDirectory* cd = reinterpret_cast<const CodeDirectory*>(CFDataGetBytePtr(cdData));
if (!cd->validateBlob(CFDataGetLength(cdData)))
MacOSError::throwMe(errSecCSSignatureFailed); cd->checkIntegrity();
auto result = candidates.insert(make_pair(cd->hashType, cdData.get()));
if (!result.second)
MacOSError::throwMe(errSecCSSignatureInvalid); hashAlgorithms.insert(cd->hashType);
if (slot == cdCodeDirectorySlot)
baseDir = cdData;
return true;
};
if (!add(cdCodeDirectorySlot))
return false; for (CodeDirectory::SpecialSlot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; slot++)
if (!add(slot)) break;
if (candidates.empty())
MacOSError::throwMe(errSecCSSignatureFailed); cdMap.swap(candidates);
mHashAlgorithms.swap(hashAlgorithms);
mBaseDir = baseDir;
return true;
}
CFDataRef SecStaticCode::cdHash()
{
if (!mCDHash) {
if (const CodeDirectory *cd = codeDirectory(false)) {
mCDHash.take(cd->cdhash());
CODESIGN_STATIC_CDHASH(this, CFDataGetBytePtr(mCDHash), (unsigned int)CFDataGetLength(mCDHash));
}
}
return mCDHash;
}
CFArrayRef SecStaticCode::cdHashes()
{
if (!mCDHashes) {
CFRef<CFMutableArrayRef> cdList = makeCFMutableArray(0);
for (auto it = mCodeDirectories.begin(); it != mCodeDirectories.end(); ++it) {
const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it->second);
if (CFRef<CFDataRef> hash = cd->cdhash())
CFArrayAppendValue(cdList, hash);
}
mCDHashes = cdList.get();
}
return mCDHashes;
}
CFDictionaryRef SecStaticCode::cdHashesFull()
{
if (!mCDHashFullDict) {
CFRef<CFMutableDictionaryRef> cdDict = makeCFMutableDictionary();
for (auto const &it : mCodeDirectories) {
CodeDirectory::HashAlgorithm alg = it.first;
const CodeDirectory *cd = (const CodeDirectory *)CFDataGetBytePtr(it.second);
CFRef<CFDataRef> hash = cd->cdhash(false);
if (hash) {
CFDictionaryAddValue(cdDict, CFTempNumber(alg), hash);
}
}
mCDHashFullDict = cdDict.get();
}
return mCDHashFullDict;
}
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 (...) {
secinfo("staticCode", "%p validation threw non-common exception", this);
mValidated = true;
Syslog::notice("code signing internal problem: unknown exception thrown by validation");
mValidationResult = errSecCSInternalError;
throw;
}
assert(validated());
#if TARGET_OS_OSX
if (mValidationResult == errSecSuccess) {
if (mValidationExpired)
if ((mValidationFlags & kSecCSConsiderExpiration)
|| (codeDirectory()->flags & kSecCodeSignatureForceExpiration))
MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED);
} else
MacOSError::throwMe(mValidationResult);
#endif
}
void SecStaticCode::validateNonResourceComponents()
{
this->validateDirectory();
for (CodeDirectory::SpecialSlot slot = codeDirectory()->maxSpecialSlot(); slot >= 1; --slot)
switch (slot) {
case cdResourceDirSlot: break;
default:
this->component(slot); break;
}
}
void SecStaticCode::validateTopDirectory()
{
assert(mDir); if (CFDataRef topDirectory = component(cdTopDirectorySlot)) {
const auto topData = (const Endian<uint32_t> *)CFDataGetBytePtr(topDirectory);
const auto topDataEnd = topData + CFDataGetLength(topDirectory) / sizeof(*topData);
std::vector<uint32_t> signedVector(topData, topDataEnd);
std::vector<uint32_t> foundVector;
foundVector.push_back(cdCodeDirectorySlot); for (CodeDirectory::Slot slot = 1; slot <= cdSlotMax; ++slot)
if (component(slot))
foundVector.push_back(slot);
int alternateCount = int(mCodeDirectories.size() - 1); for (int n = 0; n < alternateCount; n++)
foundVector.push_back(cdAlternateCodeDirectorySlots + n);
foundVector.push_back(cdSignatureSlot);
if (signedVector != foundVector)
MacOSError::throwMe(errSecCSSignatureFailed);
}
}
CFAbsoluteTime SecStaticCode::signingTime()
{
validateDirectory();
return mSigningTime;
}
CFAbsoluteTime SecStaticCode::signingTimestamp()
{
validateDirectory();
return mSigningTimestamp;
}
#if TARGET_OS_OSX
#define kSecSHA256HashSize 32
static const unsigned char ASI_CS_12[] = {
0x77,0x82,0x9C,0x64,0x33,0x45,0x2E,0x4A,0xD3,0xA8,0xE4,0x6F,0x00,0x6C,0x27,0xEA,
0xFB,0xD3,0xF2,0x6D,0x50,0xF3,0x6F,0xE0,0xE9,0x6D,0x06,0x59,0x19,0xB5,0x46,0xFF
};
bool SecStaticCode::checkfix41082220(OSStatus cssmTrustResult)
{
if (cssmTrustResult != CSSMERR_TP_CERT_REVOKED) {
return false;
}
if (CFArrayGetCount(mCertChain) == 0) {
return false;
}
CFRef<CFDataRef> leafHash(SecCertificateCopySHA256Digest((SecCertificateRef)CFArrayGetValueAtIndex(mCertChain, 0)));
if (memcmp(ASI_CS_12, CFDataGetBytePtr(leafHash), kSecSHA256HashSize) != 0) {
return false;
}
if (!isDetached() || format() != std::string("disk image")) {
return false;
}
if (hashAlgorithms().size() != 1 || hashAlgorithm() != kSecCodeSignatureHashSHA1) {
return false;
}
if (component(cdEntitlementSlot) || teamID()) {
return false;
}
if (codeDirectory()->version != 0x20100 || codeDirectory()->flags != 0) {
return false;
}
Security::Syslog::warning("CodeSigning: Check-fix enabled for dmg '%s' with identifier '%s' signed with revoked certificates",
mainExecutablePath().c_str(), identifier().c_str());
return true;
}
#endif // TARGET_OS_OSX
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());
#if TARGET_OS_OSX
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, mBaseDir));
MacOSError::check(CMSDecoderFinalizeMessage(cms));
MacOSError::check(CMSDecoderSetSearchKeychain(cms, cfEmptyArray()));
CFRef<CFArrayRef> vf_policies(createVerificationPolicies());
CFRef<CFArrayRef> ts_policies(createTimeStampingAndRevocationPolicies());
CMSSignerStatus status;
MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies,
false, &status, &mTrust.aref(), NULL));
if (status != kCMSSignerValid) {
const char *reason;
switch (status) {
case kCMSSignerUnsigned: reason="kCMSSignerUnsigned"; break;
case kCMSSignerNeedsDetachedContent: reason="kCMSSignerNeedsDetachedContent"; break;
case kCMSSignerInvalidSignature: reason="kCMSSignerInvalidSignature"; break;
case kCMSSignerInvalidCert: reason="kCMSSignerInvalidCert"; break;
case kCMSSignerInvalidIndex: reason="kCMSSignerInvalidIndex"; break;
default: reason="unknown"; break;
}
Security::Syslog::error("CMSDecoderCopySignerStatus failed with %s error (%d)",
reason, (int)status);
MacOSError::throwMe(errSecCSSignatureFailed);
}
CFRef<CFDataRef> hashAgilityV1;
switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) {
case noErr:
if (hashAgilityV1) {
CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashAgilityV1);
CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes")));
CFArrayRef myCdList = this->cdHashes();
if (cdList == NULL || !CFEqual(cdList, myCdList))
MacOSError::throwMe(errSecCSSignatureFailed);
}
break;
case -1:
break;
default:
MacOSError::throwMe(rc);
}
CFRef<CFDictionaryRef> hashAgilityV2;
switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) {
case noErr:
if (hashAgilityV2) {
if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) {
MacOSError::throwMe(errSecCSSignatureFailed);
}
bool foundOurs = false;
for (auto& entry : mCodeDirectories) {
SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first);
if (tag == SEC_OID_UNKNOWN) {
continue;
}
CFRef<CFNumberRef> key = makeCFNumber(int(tag));
CFRef<CFDataRef> entryCdhash;
entryCdhash = (CFDataRef)CFDictionaryGetValue(hashAgilityV2, (void*)key.get());
CodeDirectory const *cd = (CodeDirectory const*)CFDataGetBytePtr(entry.second);
CFRef<CFDataRef> ourCdhash = cd->cdhash(false); if (!CFEqual(entryCdhash, ourCdhash)) {
MacOSError::throwMe(errSecCSSignatureFailed);
}
if (entry.first == this->hashAlgorithm()) {
foundOurs = true;
}
}
if (!foundOurs) {
MacOSError::throwMe(errSecCSSignatureFailed);
}
}
break;
case -1:
break;
default:
MacOSError::throwMe(rc);
}
mSigningTime = 0; switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) {
case errSecSuccess:
case errSecSigningTimeMissing:
break;
default:
Security::Syslog::error("Could not get signing time (error %d)", (int)rc);
MacOSError::throwMe(rc);
}
mSigningTimestamp = 0;
switch (OSStatus rc = CMSDecoderCopySignerTimestampWithPolicy(cms, ts_policies, 0, &mSigningTimestamp)) {
case errSecSuccess:
case errSecTimestampMissing:
break;
default:
Security::Syslog::error("Could not get timestamp (error %d)", (int)rc);
MacOSError::throwMe(rc);
}
if (mValidationFlags & kSecCSNoNetworkAccess) {
MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); }
MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false));
CSSM_APPLE_TP_ACTION_DATA actionData = {
CSSM_APPLE_TP_ACTION_VERSION, 0 };
if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) {
MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); actionData.ActionFlags |= 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) {
Security::Syslog::error("Could not get team identifier (%s)", teamID());
MacOSError::throwMe(errSecCSInvalidTeamIdentifier);
}
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(errSecCSBadTeamIdentifier);
}
}
}
}
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; }
}
if (checkfix41082220(result)) {
break; }
Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result);
MacOSError::throwMe(result);
}
}
if (mSigningTimestamp) {
CFIndex rootix = CFArrayGetCount(mCertChain);
if (SecCertificateRef mainRoot = SecCertificateRef(CFArrayGetValueAtIndex(mCertChain, rootix-1)))
if (isAppleCA(mainRoot)) {
CFRef<CFArrayRef> tsCerts;
OSStatus result = CMSDecoderCopySignerTimestampCertificates(cms, 0, &tsCerts.aref());
if (result) {
Security::Syslog::error("SecStaticCode: could not get timestamp certificates (error %d)", (int)result);
MacOSError::check(result);
}
CFIndex tsn = CFArrayGetCount(tsCerts);
bool good = tsn > 0 && isAppleCA(SecCertificateRef(CFArrayGetValueAtIndex(tsCerts, tsn-1)));
if (!good) {
result = CSSMERR_TP_NOT_TRUSTED;
Security::Syslog::error("SecStaticCode: timestamp policy verification failed (error %d)", (int)result);
MacOSError::throwMe(result);
}
}
}
return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED;
}
#else
CFDataRef sig = this->signature();
this->codeDirectory(); mSigningTime = 0;
CFRef<CFDictionaryRef> attrs;
CFRef<CFArrayRef> vf_policies(createVerificationPolicies());
MacOSError::check(SecCMSVerifyCopyDataAndAttributes(sig, mBaseDir, vf_policies, &mTrust.aref(), NULL, &attrs.aref()));
mSigningTime = SecTrustGetVerifyTime(mTrust);
SecTrustResultType trustResult;
MacOSError::check(SecTrustEvaluate(mTrust, &trustResult));
CFRef<CFDataRef> hashBag;
hashBag = CFDataRef(CFDictionaryGetValue(attrs, kSecCMSHashAgility));
if (hashBag) {
CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashBag);
CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes")));
CFArrayRef myCdList = this->cdHashes();
if (cdList == NULL || !CFEqual(cdList, myCdList))
MacOSError::throwMe(errSecCSSignatureFailed);
}
SecCertificateRef leafCert = SecTrustGetCertificateAtIndex(mTrust, 0);
if (leafCert != NULL) {
CFIndex count = SecTrustGetCertificateCount(mTrust);
CFMutableArrayRef certs = CFArrayCreateMutable(kCFAllocatorDefault, count,
&kCFTypeArrayCallBacks);
CFArrayAppendValue(certs, leafCert);
for (CFIndex i = 1; i < count; ++i) {
CFArrayAppendValue(certs, SecTrustGetCertificateAtIndex(mTrust, i));
}
mCertChain.take((CFArrayRef)certs);
}
mTrustedSigningCertChain = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed);
return false; #endif
}
#if TARGET_OS_OSX
static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags)
{
CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(flags));
return policy.yield();
}
#endif
CFArrayRef SecStaticCode::createVerificationPolicies()
{
if (mValidationFlags & kSecCSUseSoftwareSigningCert) {
CFRef<SecPolicyRef> ssRef = SecPolicyCreateAppleSoftwareSigning();
return makeCFArray(1, ssRef.get());
}
#if TARGET_OS_OSX
CFRef<SecPolicyRef> core;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
&CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref()));
if (mValidationFlags & kSecCSNoNetworkAccess) {
CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
return makeCFArray(2, core.get(), no_revoc.get());
}
else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod);
return makeCFArray(2, core.get(), revoc.get());
} else {
return makeCFArray(1, core.get());
}
#elif TARGET_OS_TV
CFRef<SecPolicyRef> tvOSRef = SecPolicyCreateAppleTVOSApplicationSigning();
return makeCFArray(1, tvOSRef.get());
#else
CFRef<SecPolicyRef> iOSRef = SecPolicyCreateiPhoneApplicationSigning();
return makeCFArray(1, iOSRef.get());
#endif
}
CFArrayRef SecStaticCode::createTimeStampingAndRevocationPolicies()
{
CFRef<SecPolicyRef> tsPolicy = SecPolicyCreateAppleTimeStamping();
#if TARGET_OS_OSX
if (mValidationFlags & kSecCSNoNetworkAccess) {
CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
return makeCFArray(2, tsPolicy.get(), no_revoc.get());
}
else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod);
return makeCFArray(2, tsPolicy.get(), revoc.get());
}
else {
return makeCFArray(1, tsPolicy.get());
}
#else
return makeCFArray(1, tsPolicy.get());
#endif
}
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, false))
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->signingLimit();
for (uint32_t slot = 0; slot < cd->nCodeSlots; ++slot) {
size_t thisPage = remaining;
if (pageSize)
thisPage = min(thisPage, pageSize);
__block bool good = true;
CodeDirectory::multipleHashFileData(fd, thisPage, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) {
const CodeDirectory* cd = (const CodeDirectory*)CFDataGetBytePtr(mCodeDirectories[type]);
if (!hasher->verify(cd->getSlot(slot,
mValidationFlags & kSecCSValidatePEH)))
good = false;
});
if (!good) {
CODESIGN_EVAL_STATIC_EXECUTABLE_FAIL(this, (int)slot);
MacOSError::throwMe(errSecCSSignatureFailed);
}
remaining -= thisPage;
}
assert(remaining == 0);
mExecutableValidated = true;
mExecutableValidResult = errSecSuccess;
} catch (const CommonError &err) {
mExecutableValidated = true;
mExecutableValidResult = err.osStatus();
throw;
} catch (...) {
secinfo("staticCode", "%p executable validation threw non-common exception", this);
mExecutableValidated = true;
mExecutableValidResult = errSecCSInternalError;
Syslog::notice("code signing internal problem: unknown exception thrown by validation");
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) {
if (mLimitedAsync == NULL) {
bool runMultiThreaded = ((flags & kSecCSSingleThreaded) == kSecCSSingleThreaded) ? false :
(diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey);
mLimitedAsync = new LimitedAsync(runMultiThreaded);
}
try {
CFDictionaryRef rules;
CFDictionaryRef files;
uint32_t version;
if (!loadResources(rules, files, version))
return;
DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
(char*)this->mainExecutablePath().c_str(), 0);
mResourcesValidContext = new CollectingContext(*this);
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);
}
Dispatch::Group group;
Dispatch::Group &groupRef = group;
__block CFRef<CFMutableDictionaryRef> resourceMap = makeCFMutableDictionary(files);
string base = cfString(this->resourceBase());
ResourceBuilder resources(base, base, rules, strict, mTolerateErrors);
this->mResourceScope = &resources;
diskRep()->adjustResources(resources);
resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const string relpath, ResourceBuilder::Rule *rule) {
CFDictionaryRemoveValue(resourceMap, CFTempString(relpath));
bool isSymlink = (ent->fts_info == FTS_SL);
void (^validate)() = ^{
validateResource(files, relpath, isSymlink, *mResourcesValidContext, flags, version);
reportProgress();
};
mLimitedAsync->perform(groupRef, validate);
});
group.wait();
unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap));
if (leftovers > 0) {
secinfo("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 (...) {
secinfo("staticCode", "%p executable validation threw non-common exception", this);
mResourcesValidated = true;
mResourcesDeep = flags & kSecCSCheckNestedCode;
mResourcesValidResult = errSecCSInternalError;
Syslog::notice("code signing internal problem: unknown exception thrown by validation");
throw;
}
}
assert(validatedResources());
if (mResourcesValidResult)
MacOSError::throwMe(mResourcesValidResult);
if (mResourcesValidContext->osStatus() != errSecSuccess)
mResourcesValidContext->throwMe();
}
bool SecStaticCode::loadResources(CFDictionaryRef& rules, CFDictionaryRef& files, uint32_t& version)
{
CFDictionaryRef sealedResources = resourceDictionary();
if (this->resourceBase()) { if (sealedResources)
;
else
MacOSError::throwMe(errSecCSResourcesNotFound);
} else { if (sealedResources)
MacOSError::throwMe(errSecCSResourcesNotFound);
else
return false; }
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);
return true;
}
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) {
Syslog::notice("code signing internal problem: diskRep returned no allowedResourceOmissions");
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;
CFArrayRef allowedRef = allowed.get(); 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(allowedRef, range, key);
});
return !coversAll || forbiddenOmission;
}
CFDictionaryRef SecStaticCode::infoDictionary()
{
if (!mInfoDict) {
mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed));
secinfo("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());
secinfo("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}")) {
secinfo("staticCode", "%p loaded ResourceDict %p",
this, mResourceDict.get());
return mResourceDict = dict;
}
return NULL;
}
CFDataRef SecStaticCode::copyComponent(CodeDirectory::SpecialSlot slot, CFDataRef hash)
{
const CodeDirectory* cd = this->codeDirectory();
if (CFCopyRef<CFDataRef> component = this->component(slot)) {
if (hash) {
const void *slotHash = cd->getSlot(slot, false);
if (cd->hashSize != CFDataGetLength(hash) || 0 != memcmp(slotHash, CFDataGetBytePtr(hash), cd->hashSize)) {
Syslog::notice("copyComponent hash mismatch slot %d length %d", slot, int(CFDataGetLength(hash)));
return NULL; }
}
return component.yield();
}
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;
}
CFDictionaryRef SecStaticCode::diskRepInformation()
{
return mRep->diskRepInformation();
}
bool SecStaticCode::checkfix30814861(string path, bool addition) {
CFRef<CFDictionaryRef> inf = diskRepInformation();
try {
CFDictionary info(diskRepInformation(), errSecCSNotSupported);
uint32_t platform =
cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0);
uint32_t sdkVersion =
cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0);
if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) {
return false;
}
} catch (const MacOSError &error) {
return false;
}
static regex_t pathre_sinf;
static regex_t pathre_supp_supf;
static dispatch_once_t once;
dispatch_once(&once, ^{
os_assert_zero(regcomp(&pathre_sinf,
"^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.sinf$",
REG_EXTENDED | REG_NOSUB));
os_assert_zero(regcomp(&pathre_supp_supf,
"^(Frameworks/[^/]+\\.framework/|PlugIns/[^/]+\\.appex/|())SC_Info/[^/]+\\.(supf|supp)$",
REG_EXTENDED | REG_NOSUB));
});
const regex_t &pathre = addition ? pathre_sinf : pathre_supp_supf;
const int result = regexec(&pathre, path.c_str(), 0, NULL, 0);
if (result == REG_NOMATCH) {
return false;
} else if (result != 0) {
secerror("unexpected regexec result %d for path '%s'", result, path.c_str());
return false;
}
dispatch_once(&mCheckfix30814861builder1_once, ^{
CFDictionaryRef rules1 = cfget<CFDictionaryRef>(resourceDictionary(), "rules");
const string base = cfString(resourceBase());
mCheckfix30814861builder1 = new ResourceBuilder(base, base, rules1, false, mTolerateErrors);
});
ResourceBuilder::Rule const * const matchingRule = mCheckfix30814861builder1->findRule(path);
if (matchingRule == NULL || !(matchingRule->flags & ResourceBuilder::omitted)) {
return false;
}
return true;
}
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 (version > 1 && ((flags & (kSecCSStrictValidate|kSecCSRestrictSidebandData)) == (kSecCSStrictValidate|kSecCSRestrictSidebandData))) {
AutoFileDesc fd(cfString(fullpath));
if (fd.hasExtendedAttribute(XATTR_RESOURCEFORK_NAME) || fd.hasExtendedAttribute(XATTR_FINDERINFO_NAME))
ctx.reportProblem(errSecCSInvalidAssociatedFileData, kSecCFErrorResourceSideband, fullpath);
}
if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
ResourceSeal seal(file);
const ResourceSeal& rseal = seal;
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()) {
if (!isSymlink)
return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); validateSymlinkResource(cfString(fullpath), cfString(seal.link()), ctx, flags);
} else if (seal.hash(hashAlgorithm())) { if (isSymlink)
return ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); AutoFileDesc fd(cfString(fullpath), O_RDONLY, FileDesc::modeMissingOk); if (fd) {
__block bool good = true;
CodeDirectory::multipleHashFileData(fd, 0, hashAlgorithms(), ^(CodeDirectory::HashAlgorithm type, Security::DynamicHash *hasher) {
if (!hasher->verify(rseal.hash(type)))
good = false;
});
if (!good) {
if (version == 2 && checkfix30814861(path, false)) {
secinfo("validateResource", "%s check-fixed (altered).", path.c_str());
} 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;
}
if (version == 2 && checkfix30814861(path, true)) {
secinfo("validateResource", "%s check-fixed (added).", path.c_str());
} else {
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
}
}
void SecStaticCode::validatePlainMemoryResource(string path, CFDataRef fileData, SecCSFlags flags)
{
CFDictionaryRef rules;
CFDictionaryRef files;
uint32_t version;
if (!loadResources(rules, files, version))
MacOSError::throwMe(errSecCSResourcesNotFound); if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
ResourceSeal seal(file);
const Byte *sealHash = seal.hash(hashAlgorithm());
if (sealHash) {
if (codeDirectory()->verifyMemoryContent(fileData, sealHash))
return; }
}
MacOSError::throwMe(errSecCSBadResource);
}
void SecStaticCode::validateSymlinkResource(std::string fullpath, std::string seal, ValidationContext &ctx, SecCSFlags flags)
{
static const char* const allowedDestinations[] = {
"/System/",
"/Library/",
NULL
};
char target[PATH_MAX];
ssize_t len = ::readlink(fullpath.c_str(), target, sizeof(target)-1);
if (len < 0)
UnixError::check(-1);
target[len] = '\0';
std::string fulltarget = target;
if (target[0] != '/') {
size_t lastSlash = fullpath.rfind('/');
fulltarget = fullpath.substr(0, lastSlash) + '/' + target;
}
if (seal != target) {
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, CFTempString(fullpath));
return;
}
if ((mValidationFlags & (kSecCSStrictValidate|kSecCSRestrictSymlinks)) == (kSecCSStrictValidate|kSecCSRestrictSymlinks)) {
char resolved[PATH_MAX];
if (realpath(fulltarget.c_str(), resolved)) {
assert(resolved[0] == '/');
size_t rlen = strlen(resolved);
if (target[0] == '/') {
for (const char* const* pathp = allowedDestinations; *pathp; pathp++) {
size_t dlen = strlen(*pathp);
if (rlen > dlen && strncmp(resolved, *pathp, dlen) == 0)
return; }
} else {
for (const SecStaticCode* code = this; code; code = code->mOuterScope) {
string root = code->mResourceScope->root();
if (strncmp(resolved, root.c_str(), root.size()) == 0) {
if (code->mResourceScope->includes(resolved + root.length() + 1))
return; else
break; }
}
}
}
if (mTolerateErrors.find(errSecCSInvalidSymlink) == mTolerateErrors.end())
ctx.reportProblem(errSecCSInvalidSymlink, kSecCFErrorResourceAltered, CFTempString(fullpath));
}
}
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 | kSecCSQuickCheck;
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path)));
code->initializeFromParent(*this);
code->staticValidate(flags & (~kSecCSRestrictToAppLike), SecRequirement::required(req));
if (isFramework && (flags & kSecCSStrictValidate))
try {
validateOtherVersions(path, flags & (~kSecCSRestrictToAppLike), 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, "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->initializeFromParent(*this);
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);
__block CFRef<CFMutableArrayRef> allHashes = CFArrayCreateMutableCopy(NULL, 0, this->cdHashes());
handleOtherArchitectures(^(SecStaticCode *other) {
CFArrayRef hashes = other->cdHashes();
CFArrayAppendArray(allHashes, hashes, CFRangeMake(0, CFArrayGetCount(hashes)));
});
CFIndex count = CFArrayGetCount(allHashes);
for (CFIndex n = 0; n < count; ++n) {
chain.add();
maker.cdhash(CFDataRef(CFArrayGetValueAtIndex(allHashes, n)));
}
return maker.make();
} else {
#if TARGET_OS_OSX
validateDirectory(); Requirement::Context context(this->certificates(),
this->infoDictionary(),
this->entitlements(),
this->identifier(),
this->codeDirectory(),
NULL,
kSecCodeSignatureNoHash,
false
);
return DRMaker(context).make();
#else
MacOSError::throwMe(errSecCSUnimplemented);
#endif
}
}
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)
{
bool result = false;
assert(req);
validateDirectory();
result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory(), NULL, kSecCodeSignatureNoHash, mRep->appleInternalForcePlatform()), failure);
return result;
}
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, kSecCodeInfoCdHashes, this->cdHashes());
CFDictionaryAddValue(dict, kSecCodeInfoCdHashesFull, this->cdHashesFull());
const CodeDirectory* cd = this->codeDirectory(false);
CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithm, CFTempNumber(cd->hashType));
CFRef<CFArrayRef> digests = makeCFArrayFrom(^CFTypeRef(CodeDirectory::HashAlgorithm type) { return CFTempNumber(type); }, hashAlgorithms());
CFDictionaryAddValue(dict, kSecCodeInfoDigestAlgorithms, digests);
if (cd->platform)
CFDictionaryAddValue(dict, kSecCodeInfoPlatformIdentifier, CFTempNumber(cd->platform));
if (cd->runtimeVersion()) {
CFDictionaryAddValue(dict, kSecCodeInfoRuntimeVersion, CFTempNumber(cd->runtimeVersion()));
}
try {
if (CFDictionaryRef info = this->infoDictionary())
CFDictionaryAddValue(dict, kSecCodeInfoPList, info);
} catch (...) { }
if (flags & kSecCSSigningInformation)
try {
if (CFDataRef sig = this->signature())
CFDictionaryAddValue(dict, kSecCodeInfoCMS, sig);
if (const char *teamID = this->teamID())
CFDictionaryAddValue(dict, kSecCodeInfoTeamIdentifier, CFTempString(teamID));
if (mTrust)
CFDictionaryAddValue(dict, kSecCodeInfoTrust, mTrust);
if (CFArrayRef certs = this->certificates())
CFDictionaryAddValue(dict, kSecCodeInfoCertificates, certs);
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);
} catch (...) { }
if (flags & kSecCSRequirementInformation)
#if TARGET_OS_OSX
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 (...) { }
#endif
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 (!(flags & kSecCSSkipResourceDirectory)) {
if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict);
}
if (CFRef<CFDictionaryRef> ddict = diskRepInformation())
CFDictionaryAddValue(dict, kSecCodeInfoDiskRepInfo, ddict);
} catch (...) { }
if (mNotarizationChecked && !isnan(mNotarizationDate)) {
CFRef<CFDateRef> date = CFDateCreate(NULL, mNotarizationDate);
if (date) {
CFDictionaryAddValue(dict, kSecCodeInfoNotarizationDate, date.get());
} else {
secerror("Error creating date from timestamp: %f", mNotarizationDate);
}
}
}
if (flags & kSecCSCalculateCMSDigest) {
try {
CFDictionaryAddValue(dict, kSecCodeInfoCMSDigestHashType, CFTempNumber(cmsDigestHashType()));
CFRef<CFDataRef> cmsDigest = createCmsDigest();
if (cmsDigest) {
CFDictionaryAddValue(dict, kSecCodeInfoCMSDigest, cmsDigest.get());
}
} catch (...) { }
}
if (flags & kSecCSContentInformation && !(flags & kSecCSSkipResourceDirectory))
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)
{
StLock<Mutex> _(mLock);
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);
#if TARGET_OS_OSX
if (!mStaplingChecked) {
mRep->registerStapledTicket();
mStaplingChecked = true;
}
if (mFlags & kSecCSForceOnlineNotarizationCheck) {
if (!mNotarizationChecked) {
if (this->cdHash()) {
bool is_revoked = checkNotarizationServiceForRevocation(this->cdHash(), (SecCSDigestAlgorithm)this->hashAlgorithm(), &mNotarizationDate);
if (is_revoked) {
MacOSError::throwMe(errSecCSRevokedNotarization);
}
}
mNotarizationChecked = true;
}
}
#endif // TARGET_OS_OSX
if (flags & kSecCSReportProgress)
prepareProgress(estimateResourceWorkload() + 2);
this->staticValidateCore(flags, req);
if (flags & kSecCSCheckAllArchitectures)
handleOtherArchitectures(^(SecStaticCode* subcode) {
if (flags & kSecCSCheckGatekeeperArchitectures) {
Universal *fat = subcode->diskRep()->mainExecutableImage();
assert(fat && fat->narrowed()); Architecture arch = fat->bestNativeArch(); if ((arch.cpuType() & ~CPU_ARCH_MASK) == CPU_TYPE_POWERPC)
return; }
subcode->detachedSignature(this->mDetachedSig); subcode->staticValidateCore(flags, req);
});
reportProgress();
reportEvent(CFSTR("prepared"), NULL);
if (!(flags & kSecCSDoNotValidateResources))
this->validateResources(flags);
if (flags & kSecCSStrictValidate)
mRep->strictValidate(codeDirectory(), mTolerateErrors, mValidationFlags);
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(); this->validateTopDirectory();
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;
off_t activeOffset = fat->archOffset();
for (Universal::Architectures::const_iterator arch = architectures.begin(); arch != architectures.end(); ++arch) {
try {
ctx.offset = int_cast<size_t, off_t>(fat->archOffset(*arch));
ctx.size = fat->lengthOfSlice(int_cast<off_t,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);
}
} catch(std::out_of_range e) {
MacOSError::throwMe(errSecCSBadObjectFormat);
}
}
}
}
}
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, NULL, kSecCodeSignatureNoHash, false);
return req->requirement()->validates(ctx);
}
CFDataRef SecStaticCode::createCmsDigest()
{
auto it = codeDirectories()->begin();
if (it == codeDirectories()->end()) {
return NULL;
}
CodeDirectory const * const cd = reinterpret_cast<CodeDirectory const*>(CFDataGetBytePtr(it->second));
RefPointer<DynamicHash> hash = cd->hashFor(mCMSDigestHashType);
CFMutableDataRef data = CFDataCreateMutable(NULL, hash->digestLength());
CFDataSetLength(data, hash->digestLength());
hash->update(cd, cd->length());
hash->finish(CFDataGetMutableBytePtr(data));
return data;
}
} }