#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 {
//
// Construct a SecStaticCode object given a disk representation object
//
-SecStaticCode::SecStaticCode(DiskRep *rep)
- : mRep(rep),
+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, DISPATCH_QUEUE_PRIORITY_DEFAULT),
+ mProgressQueue("com.apple.security.validation-progress", false, QOS_CLASS_UNSPECIFIED),
mOuterScope(NULL), mResourceScope(NULL),
- mDesignatedReq(NULL), mGotResourceBase(false), mMonitor(NULL), mLimitedAsync(NULL), mEvalDetails(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
}
::free(const_cast<Requirement *>(mDesignatedReq));
delete mResourcesValidContext;
delete mLimitedAsync;
+ delete mCheckfix30814861builder1;
} catch (...) {
return;
}
void SecStaticCode::prepareProgress(unsigned int workload)
{
dispatch_sync(mProgressQueue, ^{
- mCancelPending = false; // not cancelled
+ mCancelPending = false; // not canceled
});
if (mValidationFlags & kSecCSReportProgress) {
mCurrentWork = 0; // nothing done yet
//
void SecStaticCode::checkForSystemSignature()
{
+#if TARGET_OS_OSX
if (!this->isSigned()) {
SignatureDatabase db;
if (db.isOpen())
} catch (...) {
}
}
+#else
+ MacOSError::throwMe(errSecUnimplemented);
+#endif
}
mResourcesValidContext = NULL;
}
mDir = NULL;
+ mCodeDirectories.clear();
mSignature = NULL;
for (unsigned n = 0; n < cdSlotCount; n++)
mCache[n] = NULL;
mGotResourceBase = false;
mTrust = NULL;
mCertChain = NULL;
+ mNotarizationChecked = false;
+ mStaplingChecked = false;
+ mNotarizationDate = NAN;
+#if TARGET_OS_OSX
mEvalDetails = NULL;
+#endif
mRep->flush();
+#if TARGET_OS_OSX
// we may just have updated the system database, so check again
checkForSystemSignature();
+#endif
}
return NULL;
if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), // ... and it's no good
- CFDataGetLength(data), -slot))
+ CFDataGetLength(data), -slot, false))
MacOSError::throwMe(errorForSlot(slot)); // ... then bail
}
cache = data; // it's okay, cache it
}
+//
+// Get the CodeDirectories.
+// Throws (if check==true) or returns NULL (check==false) if there are none.
+// Always throws if the CodeDirectories exist but are invalid.
+// NEVER validates against the signature.
+//
+const SecStaticCode::CodeDirectoryMap *
+SecStaticCode::codeDirectories(bool check /* = true */) const
+{
+ if (mCodeDirectories.empty()) {
+ try {
+ loadCodeDirectories(mCodeDirectories);
+ } catch (...) {
+ if (check)
+ throw;
+ // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectories.
+ // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory.
+ 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;
+}
+
//
// Get the CodeDirectory.
// Throws (if check==true) or returns NULL (check==false) if there is none.
{
if (!mDir) {
// pick our favorite CodeDirectory from the choices we've got
- CodeDirectoryMap candidates;
- if (loadCodeDirectories(candidates)) {
- CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
- mDir = candidates[type]; // and the winner is...
- candidates.swap(mCodeDirectories);
+ try {
+ CodeDirectoryMap const *candidates = codeDirectories(check);
+ if (candidates != NULL) {
+ CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
+ mDir = candidates->at(type); // and the winner is...
+ }
+ } catch (...) {
+ if (check)
+ throw;
+ // We wanted a NON-checked peek and failed to safely decode the existing CodeDirectory.
+ // Pretend this is unsigned, but make sure we didn't somehow cache an invalid CodeDirectory.
+ if (mDir) {
+ assert(false);
+ Syslog::warning("code signing internal problem: mDir set despite exception exit");
+ MacOSError::throwMe(errSecCSInternalError);
+ }
}
}
if (mDir)
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)
cd->checkIntegrity();
auto result = candidates.insert(make_pair(cd->hashType, cdData.get()));
if (!result.second)
- MacOSError::throwMe(errSecCSSignatureFailed); // duplicate hashType, go to heck
- mHashAlgorithms.insert(cd->hashType);
+ MacOSError::throwMe(errSecCSSignatureInvalid); // duplicate hashType, go to heck
+ hashAlgorithms.insert(cd->hashType);
if (slot == cdCodeDirectorySlot)
- mBaseDir = cdData;
+ baseDir = cdData;
return true;
};
if (!add(cdCodeDirectorySlot))
break;
if (candidates.empty())
MacOSError::throwMe(errSecCSSignatureFailed); // no viable CodeDirectory in sight
+ // commit to cached values
cdMap.swap(candidates);
+ mHashAlgorithms.swap(hashAlgorithms);
+ mBaseDir = baseDir;
return true;
}
return mCDHashes;
}
+//
+// Get a dictionary of untruncated cdhashes for all digest types in this signature.
+//
+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;
+}
+
//
// Return the CMS signature blob; NULL if none found.
mValidationResult = err.osStatus();
throw;
} catch (...) {
- secdebug("staticCode", "%p validation threw non-common exception", this);
+ 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());
+ // XXX: Embedded doesn't have CSSMERR_TP_CERT_EXPIRED so we can't throw it
+ // XXX: This should be implemented for embedded once we implement
+ // XXX: verifySignature and see how we're going to handle expired certs
+#if TARGET_OS_OSX
if (mValidationResult == errSecSuccess) {
if (mValidationExpired)
if ((mValidationFlags & kSecCSConsiderExpiration)
MacOSError::throwMe(CSSMERR_TP_CERT_EXPIRED);
} else
MacOSError::throwMe(mValidationResult);
+#endif
}
if (component(slot))
foundVector.push_back(slot);
int alternateCount = int(mCodeDirectories.size() - 1); // one will go into cdCodeDirectorySlot
- for (unsigned n = 0; n < alternateCount; n++)
+ for (int n = 0; n < alternateCount; n++)
foundVector.push_back(cdAlternateCodeDirectorySlots + n);
foundVector.push_back(cdSignatureSlot); // mandatory (may be empty)
return mSigningTimestamp;
}
+#if TARGET_OS_OSX
+#define kSecSHA256HashSize 32
+// subject:/C=US/ST=California/L=San Jose/O=Adobe Systems Incorporated/OU=Information Systems/OU=Digital ID Class 3 - Microsoft Software Validation v2/CN=Adobe Systems Incorporated
+// issuer :/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Code Signing 2010 CA
+// Not Before: Dec 15 00:00:00 2010 GMT
+// Not After : Dec 14 23:59:59 2012 GMT
+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)
+{
+ // only applicable to revoked results
+ if (cssmTrustResult != CSSMERR_TP_CERT_REVOKED) {
+ return false;
+ }
+
+ // only this leaf certificate
+ 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;
+ }
+
+ // detached dmg signature
+ if (!isDetached() || format() != std::string("disk image")) {
+ return false;
+ }
+
+ // sha-1 signed
+ if (hashAlgorithms().size() != 1 || hashAlgorithm() != kSecCodeSignatureHashSHA1) {
+ return false;
+ }
+
+ // not a privileged binary - no TeamID and no entitlements
+ if (component(cdEntitlementSlot) || teamID()) {
+ return false;
+ }
+
+ // no flags and old version
+ 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
//
// Verify the CMS signature.
}
DTRACK(CODESIGN_EVAL_STATIC_SIGNATURE, this, (char*)this->mainExecutablePath().c_str());
-
+#if TARGET_OS_OSX
// decode CMS and extract SecTrust for verification
CFRef<CMSDecoderRef> cms;
MacOSError::check(CMSDecoderCreate(&cms.aref())); // create decoder
MacOSError::check(CMSDecoderSetDetachedContent(cms, mBaseDir));
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));
+ 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;
reason, (int)status);
MacOSError::throwMe(errSecCSSignatureFailed);
}
-
- // retrieve auxiliary data bag and verify against current state
- CFRef<CFDataRef> hashBag;
- switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashBag.aref())) {
+
+ // retrieve auxiliary v1 data bag and verify against current state
+ CFRef<CFDataRef> hashAgilityV1;
+ switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgility(cms, 0, &hashAgilityV1.aref())) {
case noErr:
- if (hashBag) {
- CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashBag);
+ if (hashAgilityV1) {
+ CFRef<CFDictionaryRef> hashDict = makeCFDictionaryFrom(hashAgilityV1);
CFArrayRef cdList = CFArrayRef(CFDictionaryGetValue(hashDict, CFSTR("cdhashes")));
CFArrayRef myCdList = this->cdHashes();
+
+ /* Note that this is not very "agile": There's no way to calculate the exact
+ * list for comparison if it contains hash algorithms we don't know yet... */
if (cdList == NULL || !CFEqual(cdList, myCdList))
MacOSError::throwMe(errSecCSSignatureFailed);
}
MacOSError::throwMe(rc);
}
+ // retrieve auxiliary v2 data bag and verify against current state
+ CFRef<CFDictionaryRef> hashAgilityV2;
+ switch (OSStatus rc = CMSDecoderCopySignerAppleCodesigningHashAgilityV2(cms, 0, &hashAgilityV2.aref())) {
+ case noErr:
+ if (hashAgilityV2) {
+ /* Require number of code directoris and entries in the hash agility
+ * dict to be the same size (no stripping out code directories).
+ */
+ if (CFDictionaryGetCount(hashAgilityV2) != mCodeDirectories.size()) {
+ MacOSError::throwMe(errSecCSSignatureFailed);
+ }
+
+ /* Require every cdhash of every code directory whose hash
+ * algorithm we know to be in the agility dictionary.
+ *
+ * We check untruncated cdhashes here because we can.
+ */
+ bool foundOurs = false;
+ for (auto& entry : mCodeDirectories) {
+ SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(entry.first);
+
+ if (tag == SEC_OID_UNKNOWN) {
+ // Unknown hash algorithm, ignore.
+ 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); // Untruncated cdhash!
+ if (!CFEqual(entryCdhash, ourCdhash)) {
+ MacOSError::throwMe(errSecCSSignatureFailed);
+ }
+
+ if (entry.first == this->hashAlgorithm()) {
+ foundOurs = true;
+ }
+ }
+
+ /* Require the cdhash of our chosen code directory to be in the dictionary.
+ * In theory, the dictionary could be full of unsupported cdhashes, but we
+ * really want ours, which is bound to be supported, to be covered.
+ */
+ if (!foundOurs) {
+ MacOSError::throwMe(errSecCSSignatureFailed);
+ }
+ }
+ break;
+ case -1: /* CMS used to return this for "no attribute found", so tolerate it. Now returning noErr/NULL */
+ break;
+ default:
+ MacOSError::throwMe(rc);
+ }
+
// internal signing time (as specified by the signer; optional)
mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-)
switch (OSStatus rc = CMSDecoderCopySignerSigningTime(cms, 0, &mSigningTime)) {
if (mValidationFlags & kSecCSNoNetworkAccess) {
MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); // no network?
}
- MacOSError::check(SecTrustSetKeychains(mTrust, cfEmptyArray())); // no keychains
-
+ MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false));
+
CSSM_APPLE_TP_ACTION_DATA actionData = {
CSSM_APPLE_TP_ACTION_VERSION, // version of data structure
0 // action flags
};
-
+
if (!(mValidationFlags & kSecCSCheckTrustedAnchors)) {
/* no need to evaluate anchor trust when building cert chain */
MacOSError::check(SecTrustSetAnchorCertificates(mTrust, cfEmptyArray())); // no anchors
CFRef<CFStringRef> teamIDFromCD = CFStringCreateWithCString(NULL, teamID(), kCFStringEncodingUTF8);
if (!teamIDFromCD) {
Security::Syslog::error("Could not get team identifier (%s)", teamID());
- MacOSError::throwMe(errSecCSInternalError);
+ 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(errSecCSSignatureInvalid);
+ 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);
}
}
}
continue; // retry validation while tolerating expiration
}
}
+ if (checkfix41082220(result)) {
+ break; // success
+ }
Security::Syslog::error("SecStaticCode: verification failed (trust result %d, error %d)", trustResult, (int)result);
MacOSError::throwMe(result);
}
return actionData.ActionFlags & CSSM_TP_ACTION_ALLOW_EXPIRED;
}
-}
+#else
+ // Do some pre-verification initialization
+ CFDataRef sig = this->signature();
+ this->codeDirectory(); // load CodeDirectory (sets mDir)
+ mSigningTime = 0; // "not present" marker (nobody could code sign on Jan 1, 2001 :-)
+
+ CFRef<CFDictionaryRef> attrs;
+ CFRef<CFArrayRef> vf_policies(createVerificationPolicies());
+
+ // Verify the CMS signature against mBaseDir (SHA1)
+ MacOSError::check(SecCMSVerifyCopyDataAndAttributes(sig, mBaseDir, vf_policies, &mTrust.aref(), NULL, &attrs.aref()));
+
+ // Copy the signing time
+ mSigningTime = SecTrustGetVerifyTime(mTrust);
+
+ // Validate the cert chain
+ SecTrustResultType trustResult;
+ MacOSError::check(SecTrustEvaluate(mTrust, &trustResult));
+
+ // retrieve auxiliary data bag and verify against current state
+ 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);
+ }
+
+ /*
+ * Populate mCertChain with the certs. If we failed validation, the
+ * signer's cert will be checked installed provisioning profiles as an
+ * alternative to verification against the policy for store-signed binaries
+ */
+ 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);
+ }
+
+ // Did we implicitly trust the signer?
+ mTrustedSigningCertChain = (trustResult == kSecTrustResultUnspecified || trustResult == kSecTrustResultProceed);
+ return false; // XXX: Not checking for expired certs
+#endif
+}
+#if TARGET_OS_OSX
//
// Return the TP policy used for signature verification.
// This may be a simple SecPolicyRef or a CFArray of policies.
// The caller owns the return value.
//
-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()
+static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags)
{
- 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));
+ CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(flags));
return policy.yield();
}
+#endif
-CFArrayRef SecStaticCode::verificationPolicies()
+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) {
- // Skips all revocation since they require network connectivity
- // therefore annihilates kSecCSEnforceRevocationChecks if present
- CFRef<SecPolicyRef> no_revoc = SecPolicyCreateRevocation(kSecRevocationNetworkAccessDisabled);
- return makeCFArray(2, core.get(), no_revoc.get());
- }
+ &CSSMOID_APPLE_TP_CODE_SIGNING, &core.aref()));
+ if (mValidationFlags & kSecCSNoNetworkAccess) {
+ // Skips all revocation since they require network connectivity
+ // therefore annihilates kSecCSEnforceRevocationChecks if present
+ CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
+ return makeCFArray(2, core.get(), no_revoc.get());
+ }
else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
- // Add CRL and OCSPPolicies
- CFRef<SecPolicyRef> crl = makeCRLPolicy();
- CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
- return makeCFArray(3, core.get(), crl.get(), ocsp.get());
+ // Add CRL and OCSP policies
+ 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) {
+ // Skips all revocation since they require network connectivity
+ // therefore annihilates kSecCSEnforceRevocationChecks if present
+ CFRef<SecPolicyRef> no_revoc = makeRevocationPolicy(kSecRevocationNetworkAccessDisabled);
+ return makeCFArray(2, tsPolicy.get(), no_revoc.get());
+ }
+ else if (mValidationFlags & kSecCSEnforceRevocationChecks) {
+ // Add CRL and OCSP policies
+ 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
+
}
if (codeDirectory()->slotIsPresent(-slot)) // was supposed to be there...
MacOSError::throwMe(fail); // ... and is missing
} else {
- if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot))
+ if (!codeDirectory()->validateSlot(CFDataGetBytePtr(data), CFDataGetLength(data), -slot, false))
MacOSError::throwMe(fail);
}
}
__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)[slot]))
+ if (!hasher->verify(cd->getSlot(slot,
+ mValidationFlags & kSecCSValidatePEH)))
good = false;
});
if (!good) {
mExecutableValidResult = err.osStatus();
throw;
} catch (...) {
- secdebug("staticCode", "%p executable validation threw non-common exception", this);
+ 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;
}
}
if (doit) {
if (mLimitedAsync == NULL) {
- mLimitedAsync = new LimitedAsync(diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey);
+ bool runMultiThreaded = ((flags & kSecCSSingleThreaded) == kSecCSSingleThreaded) ? false :
+ (diskRep()->fd().mediumType() == kIOPropertyMediumTypeSolidStateKey);
+ mLimitedAsync = new LimitedAsync(runMultiThreaded);
}
try {
- // sanity first
- CFDictionaryRef sealedResources = resourceDictionary();
- if (this->resourceBase()) // disk has resources
- if (sealedResources)
- /* go to work below */;
- else
- MacOSError::throwMe(errSecCSResourcesNotFound);
- else // disk has no resources
- if (sealedResources)
- MacOSError::throwMe(errSecCSResourcesNotFound);
- else
- return; // no resources, not sealed - fine (no work)
+ CFDictionaryRef rules;
+ CFDictionaryRef files;
+ uint32_t version;
+ if (!loadResources(rules, files, version))
+ return; // validly no resources; nothing to do (ok)
// found resources, and they are sealed
DTRACK(CODESIGN_EVAL_STATIC_RESOURCES, this,
// scan through the resources on disk, checking each against the resourceDirectory
mResourcesValidContext = new CollectingContext(*this); // collect all failures in here
- // use V2 resource seal if available, otherwise fall back to V1
- CFDictionaryRef rules;
- CFDictionaryRef files;
- uint32_t version;
- if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature
- rules = cfget<CFDictionaryRef>(sealedResources, "rules2");
- files = cfget<CFDictionaryRef>(sealedResources, "files2");
- version = 2;
- } else { // only V1 available
- rules = cfget<CFDictionaryRef>(sealedResources, "rules");
- files = cfget<CFDictionaryRef>(sealedResources, "files");
- version = 1;
- }
- if (!rules || !files)
- MacOSError::throwMe(errSecCSResourcesInvalid);
-
// check for weak resource rules
bool strict = flags & kSecCSStrictValidate;
if (strict) {
unsigned leftovers = unsigned(CFDictionaryGetCount(resourceMap));
if (leftovers > 0) {
- secdebug("staticCode", "%d sealed resource(s) not found in code", int(leftovers));
+ secinfo("staticCode", "%d sealed resource(s) not found in code", int(leftovers));
CFDictionaryApplyFunction(resourceMap, SecStaticCode::checkOptionalResource, mResourcesValidContext);
}
mResourcesValidResult = err.osStatus();
throw;
} catch (...) {
- secdebug("staticCode", "%p executable validation threw non-common exception", this);
+ 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;
}
}
}
+bool SecStaticCode::loadResources(CFDictionaryRef& rules, CFDictionaryRef& files, uint32_t& version)
+{
+ // sanity first
+ CFDictionaryRef sealedResources = resourceDictionary();
+ if (this->resourceBase()) { // disk has resources
+ if (sealedResources)
+ /* go to work below */;
+ else
+ MacOSError::throwMe(errSecCSResourcesNotFound);
+ } else { // disk has no resources
+ if (sealedResources)
+ MacOSError::throwMe(errSecCSResourcesNotFound);
+ else
+ return false; // no resources, not sealed - fine (no work)
+ }
+
+ // use V2 resource seal if available, otherwise fall back to V1
+ if (CFDictionaryGetValue(sealedResources, CFSTR("files2"))) { // have V2 signature
+ rules = cfget<CFDictionaryRef>(sealedResources, "rules2");
+ files = cfget<CFDictionaryRef>(sealedResources, "files2");
+ version = 2;
+ } else { // only V1 available
+ 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);
{
// compute allowed omissions
CFRef<CFArrayRef> defaultOmissions = this->diskRep()->allowedResourceOmissions();
- if (!defaultOmissions)
+ 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)));
{
if (!mInfoDict) {
mInfoDict.take(getDictionary(cdInfoSlot, errSecCSInfoPlistFailed));
- secdebug("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get());
+ secinfo("staticCode", "%p loaded InfoDict %p", this, mInfoDict.get());
}
return mInfoDict;
}
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());
+ secinfo("staticCode", "%p loaded Entitlements %p", this, mEntitlements.get());
}
// we do not consider a different blob type to be an error. We think it's a new format we don't understand
}
return mResourceDict;
if (CFRef<CFDictionaryRef> dict = getDictionary(cdResourceDirSlot, check))
if (cfscan(dict, "{rules=%Dn,files=%Dn}")) {
- secdebug("staticCode", "%p loaded ResourceDict %p",
+ secinfo("staticCode", "%p loaded ResourceDict %p",
this, mResourceDict.get());
return mResourceDict = dict;
}
// bad format
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; // mismatch
+ }
+ }
+ return component.yield();
+ }
+ return NULL;
+}
+
//
return NULL;
}
-
//
-// Load, validate, and return a sealed resource.
-// The resource data (loaded in to memory as a blob) is returned and becomes
-// the responsibility of the caller; it is NOT cached by SecStaticCode.
//
-// A resource that is not sealed will not be returned, and an error will be thrown.
-// A missing resource will cause an error unless it's marked optional in the Directory.
-// Under no circumstances will a corrupt resource be returned.
-// NULL will only be returned for a resource that is neither sealed nor present
-// (or that is sealed, absent, and marked optional).
-// If the ResourceDictionary itself is not sealed, this function will always fail.
//
-// There is currently no interface for partial retrieval of the resource data.
-// (Since the ResourceDirectory does not currently support segmentation, all the
-// data would have to be read anyway, but it could be read into a reusable buffer.)
-//
-CFDataRef SecStaticCode::resource(string path, ValidationContext &ctx)
+CFDictionaryRef SecStaticCode::diskRepInformation()
{
- if (CFDictionaryRef rdict = resourceDictionary()) {
- if (CFTypeRef file = cfget(rdict, "files.%s", path.c_str())) {
- ResourceSeal seal(file);
- if (!resourceBase()) // no resources in DiskRep
- MacOSError::throwMe(errSecCSResourcesNotFound);
- if (seal.nested())
- MacOSError::throwMe(errSecCSResourcesNotSealed); // (it's nested code)
- 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(hashAlgorithm())))
- return data.yield(); // good
- else
- ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered
- } else {
- if (!seal.optional())
- ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing
- else
- return NULL; // validly missing
- }
- } else
- ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
- return NULL;
- } else
- MacOSError::throwMe(errSecCSResourcesNotSealed);
+ return mRep->diskRepInformation();
}
-CFDataRef SecStaticCode::resource(string path)
-{
- ValidationContext ctx(*this);
- return resource(path, ctx);
+bool SecStaticCode::checkfix30814861(string path, bool addition) {
+ // <rdar://problem/30814861> v2 resource rules don't match v1 resource rules
+
+ //// Condition 1: Is the app an iOS app that was built with an SDK lower than 9.0?
+
+ // We started signing correctly in 2014, 9.0 was first seeded mid-2016.
+
+ 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;
+ }
+
+ //// Condition 2: Is it a .sinf/.supf/.supp file at the right location?
+
+ 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));
+ });
+
+ // .sinf is added, .supf/.supp are modified.
+ 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) {
+ // Huh?
+ secerror("unexpected regexec result %d for path '%s'", result, path.c_str());
+ return false;
+ }
+
+ //// Condition 3: Do the v1 rules actually exclude the file?
+
+ dispatch_once(&mCheckfix30814861builder1_once, ^{
+ // Create the v1 resource builder lazily.
+ 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;
+ }
+
+ //// All matched, this file is a check-fixed sinf/supf/supp.
+
+ return true;
+
}
void SecStaticCode::validateResource(CFDictionaryRef files, string path, bool isSymlink, ValidationContext &ctx, SecCSFlags flags, uint32_t version)
if (!resourceBase()) // no resources in DiskRep
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 (!hasher->verify(rseal.hash(type)))
good = false;
});
- if (!good)
- ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered
+ if (!good) {
+ if (version == 2 && checkfix30814861(path, false)) {
+ secinfo("validateResource", "%s check-fixed (altered).", path.c_str());
+ } else {
+ ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAltered, fullpath); // altered
+ }
+ }
} else {
if (!seal.optional())
ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceMissing, fullpath); // was sealed but is now missing
if (::readlink(cfString(fullpath).c_str(), target, sizeof(target)) > 0)
return;
}
- ctx.reportProblem(errSecCSBadResource, kSecCFErrorResourceAdded, CFTempURL(path, false, resourceBase()));
+ 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); // no resources sealed; this can't be right
+ if (CFTypeRef file = CFDictionaryGetValue(files, CFTempString(path))) {
+ ResourceSeal seal(file);
+ const Byte *sealHash = seal.hash(hashAlgorithm());
+ if (sealHash) {
+ if (codeDirectory()->verifyMemoryContent(fileData, sealHash))
+ return; // success
+ }
+ }
+ MacOSError::throwMe(errSecCSBadResource);
+}
+
void SecStaticCode::validateSymlinkResource(std::string fullpath, std::string seal, ValidationContext &ctx, SecCSFlags flags)
{
static const char* const allowedDestinations[] = {
// recursively verify this nested code
try {
if (!(flags & kSecCSCheckNestedCode))
- flags |= kSecCSBasicValidateOnly;
+ flags |= kSecCSBasicValidateOnly | kSecCSQuickCheck;
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(cfString(path)));
code->initializeFromParent(*this);
- code->staticValidate(flags & ~kSecCSRestrictToAppLike, SecRequirement::required(req));
+ code->staticValidate(flags & (~kSecCSRestrictToAppLike), SecRequirement::required(req));
if (isFramework && (flags & kSecCSStrictValidate))
try {
- validateOtherVersions(path, flags, req, code);
+ validateOtherVersions(path, flags & (~kSecCSRestrictToAppLike), req, code);
} catch (const CSError &err) {
MacOSError::throwMe(errSecCSBadFrameworkVersion);
} catch (const MacOSError &err) {
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)
+ if (entry->d_type != DT_DIR || strcmp(entry->d_name, "Current") == 0)
continue;
fullPath << versionsPath.str() << entry->d_name;
}
return maker.make();
} else {
+#if TARGET_OS_OSX
// full signature: Gin up full context and let DRMaker do its thing
validateDirectory(); // need the cert chain
+ CFRef<CFDateRef> secureTimestamp;
+ if (CFAbsoluteTime time = this->signingTimestamp()) {
+ secureTimestamp.take(CFDateCreate(NULL, time));
+ }
Requirement::Context context(this->certificates(),
this->infoDictionary(),
this->entitlements(),
this->identifier(),
- this->codeDirectory()
+ this->codeDirectory(),
+ NULL,
+ kSecCodeSignatureNoHash,
+ false,
+ secureTimestamp,
+ this->teamID()
);
return DRMaker(context).make();
+#else
+ MacOSError::throwMe(errSecCSUnimplemented);
+#endif
}
}
/* accept it */;
}
-/* Public Key Hash for root:/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority */
-static const UInt8 retryRootBytes[] = {0x00,0xd8,0x5a,0x4c,0x25,0xc1,0x22,0xe5,0x8b,0x31,0xef,0x6d,0xba,0xf3,0xcc,0x5f,0x29,0xf1,0x0d,0x61};
-
//
// Validate this StaticCode against an external Requirement
//
bool result = false;
assert(req);
validateDirectory();
- result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure);
- if (result == false) {
- /* Fix for rdar://problem/21437632: Work around untrusted root in validation chain */
- CFArrayRef certs = certificates();
- if (!certs || ((int)CFArrayGetCount(certs) < 1)) {
- return false;
- }
- SecCertificateRef root = cert((int)CFArrayGetCount(certs) - 1);
- if (!root) {
- return false;
- }
- CFDataRef rootHash = SecCertificateCopyPublicKeySHA1Digest(root);
- if (!rootHash) {
- return false;
- }
-
- if ((CFDataGetLength(rootHash) == sizeof(retryRootBytes)) &&
- !memcmp(CFDataGetBytePtr(rootHash), retryRootBytes, sizeof(retryRootBytes))) {
- // retry with a rebuilt certificate chain, this time evaluating anchor trust
- Security::Syslog::debug("Requirements validation failed: retrying");
- mResourcesValidated = mValidated = false;
- setValidationFlags(mValidationFlags | kSecCSCheckTrustedAnchors);
-
- validateDirectory();
- result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(), codeDirectory()->identifier(), codeDirectory()), failure);
- }
- CFRelease(rootHash);
+ CFRef<CFDateRef> secureTimestamp;
+ if (CFAbsoluteTime time = this->signingTimestamp()) {
+ secureTimestamp.take(CFDateCreate(NULL, time));
}
-
+ result = req->validates(Requirement::Context(mCertChain, infoDictionary(), entitlements(),
+ codeDirectory()->identifier(), codeDirectory(),
+ NULL, kSecCodeSignatureNoHash, mRep->appleInternalForcePlatform(),
+ secureTimestamp, teamID()),
+ failure);
return result;
}
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()));
+ }
//
// Deliver any Info.plist only if it looks intact
// kSecCSRequirementInformation adds information on requirements
//
if (flags & kSecCSRequirementInformation)
- try {
+
+//DR not currently supported on iOS
+#if TARGET_OS_OSX
+ try {
if (const Requirements *reqs = this->internalRequirements()) {
CFDictionaryAddValue(dict, kSecCodeInfoRequirements,
CFTempString(Dumper::dump(reqs)));
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 (...) { }
+ try {
+ if (CFDataRef ent = this->component(cdEntitlementSlot)) {
+ CFDictionaryAddValue(dict, kSecCodeInfoEntitlements, ent);
+ if (CFDictionaryRef entdict = this->entitlements())
+ CFDictionaryAddValue(dict, kSecCodeInfoEntitlementsDict, entdict);
+ }
+ } catch (...) { }
//
// kSecCSInternalInformation adds internal information meant to be for Apple internal
// to reliably transmit through the API wall so that code outside the Security.framework
// can use it without having to play nasty tricks to get it.
//
- if (flags & kSecCSInternalInformation)
+ if (flags & kSecCSInternalInformation) {
try {
if (mDir)
CFDictionaryAddValue(dict, kSecCodeInfoCodeDirectory, mDir);
CFDictionaryAddValue(dict, kSecCodeInfoCodeOffset, CFTempNumber(mRep->signingBase()));
- if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation
- CFDictionaryAddValue(dict, kSecCodeInfoResourceDirectory, rdict);
+ if (!(flags & kSecCSSkipResourceDirectory)) {
+ if (CFRef<CFDictionaryRef> rdict = getDictionary(cdResourceDirSlot, false)) // suppress validation
+ 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 (...) { }
+ }
//
// kSecCSContentInformation adds more information about the physical layout
// of the signed code. This is (only) useful for packaging or patching-oriented
// applications.
//
- if (flags & kSecCSContentInformation)
+ if (flags & kSecCSContentInformation && !(flags & kSecCSSkipResourceDirectory))
if (CFRef<CFArrayRef> files = mRep->modifiedFiles())
CFDictionaryAddValue(dict, kSecCodeInfoChangedFiles, files);
{
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
+
// initialize progress/cancellation state
if (flags & kSecCSReportProgress)
prepareProgress(estimateResourceWorkload() + 2); // +1 head, +1 tail
- // core components: once per architecture (if any)
+
+ // core components: once per architecture (if any)
this->staticValidateCore(flags, req);
if (flags & kSecCSCheckAllArchitectures)
handleOtherArchitectures(^(SecStaticCode* subcode) {
this->validateResources(flags);
// perform strict validation if desired
- if (flags & kSecCSStrictValidate)
+ if (flags & kSecCSStrictValidate) {
mRep->strictValidate(codeDirectory(), mTolerateErrors, mValidationFlags);
reportProgress();
+ } else if (flags & kSecCSStrictValidateStructure) {
+ mRep->strictValidateStructure(codeDirectory(), mTolerateErrors, mValidationFlags);
+ }
// allow monitor intervention
if (CFRef<CFTypeRef> veto = reportEvent(CFSTR("validated"), NULL)) {
fat->architectures(architectures);
if (architectures.size() > 1) {
DiskRep::Context ctx;
- size_t activeOffset = fat->archOffset();
+ off_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) { // inactive architecture; check it
- SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx));
- subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature
- if (this->teamID() == NULL || subcode->teamID() == NULL) {
- if (this->teamID() != subcode->teamID())
+ 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) { // inactive architecture; check it
+ SecPointer<SecStaticCode> subcode = new SecStaticCode(DiskRep::bestGuess(this->mainExecutablePath(), &ctx));
+ subcode->detachedSignature(this->mDetachedSig); // carry over explicit (but not implicit) detached signature
+ 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);
- } else if (strcmp(this->teamID(), subcode->teamID()) != 0)
- MacOSError::throwMe(errSecCSSignatureInvalid);
- handle(subcode);
+ handle(subcode);
+ }
+ } catch(std::out_of_range e) {
+ // some of our int_casts fell over.
+ MacOSError::throwMe(errSecCSBadObjectFormat);
}
}
}
{
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);
+ Requirement::Context ctx(certs, NULL, NULL, "", NULL, NULL, kSecCodeSignatureNoHash, false, NULL, "");
return req->requirement()->validates(ctx);
}
+CFDataRef SecStaticCode::createCmsDigest()
+{
+ /*
+ * The CMS digest is a hash of the primary (first, most compatible) code directory,
+ * but its hash algorithm is fixed and not related to the code directory's
+ * hash algorithm.
+ */
+
+ 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;
+}
+
} // end namespace CodeSigning
} // end namespace Security