#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>
{
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 candidates;
+ if (loadCodeDirectories(candidates)) {
+ CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
+ mDir = candidates[type]; // and the winner is...
+ candidates.swap(mCodeDirectories);
+ }
+ } 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;
}
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;
}
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(verificationPolicies());
+ CFRef<CFArrayRef> ts_policies(SecPolicyCreateAppleTimeStampingAndRevocationPolicies(vf_policies));
+
+ CMSSignerStatus status;
+ MacOSError::check(CMSDecoderCopySignerStatus(cms, 0, vf_policies,
+ false, &status, &mTrust.aref(), NULL));
if (status != kCMSSignerValid) {
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())) {
if (mValidationFlags & kSecCSNoNetworkAccess) {
MacOSError::check(SecTrustSetNetworkFetchAllowed(mTrust,false)); // no network?
}
+#if !SECTRUST_OSX
MacOSError::check(SecTrustSetKeychains(mTrust, cfEmptyArray())); // no keychains
-
+#else
+ MacOSError::check(SecTrustSetKeychainsAllowed(mTrust, false));
+#endif
+
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);
}
}
}
// This may be a simple SecPolicyRef or a CFArray of policies.
// The caller owns the return value.
//
+#if !SECTRUST_OSX
static SecPolicyRef makeCRLPolicy()
{
CFRef<SecPolicyRef> policy;
MacOSError::check(SecPolicySetValue(policy, &optData));
return policy.yield();
}
+#else
+static SecPolicyRef makeRevocationPolicy(CFOptionFlags flags)
+{
+ CFRef<SecPolicyRef> policy(SecPolicyCreateRevocation(flags));
+ return policy.yield();
+}
+#endif
CFArrayRef SecStaticCode::verificationPolicies()
{
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());
- }
+ 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
+ // Add CRL and OCSP policies
+#if !SECTRUST_OSX
CFRef<SecPolicyRef> crl = makeCRLPolicy();
CFRef<SecPolicyRef> ocsp = makeOCSPPolicy();
return makeCFArray(3, core.get(), crl.get(), ocsp.get());
+#else
+ CFRef<SecPolicyRef> revoc = makeRevocationPolicy(kSecRevocationUseAnyAvailableMethod);
+ return makeCFArray(2, core.get(), revoc.get());
+#endif
} else {
return makeCFArray(1, core.get());
}
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;
}
}
}
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)[slot];
+ 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);
-}
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;
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));
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;
/* 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
//
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);
- }
-
return result;
}
// 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 (CFRef<CFDictionaryRef> ddict = diskRepInformation())
+ CFDictionaryAddValue(dict, kSecCodeInfoDiskRepInfo, ddict);
} catch (...) { }
+ }
//
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);
+ MacOSError::throwMe(errSecCSBadObjectFormat);
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));