// otherwise we need to check la_result
if (auth_items_exist(engine->context, AGENT_CONTEXT_AP_PAM_SERVICE_NAME) || auth_items_exist(engine->context, kAuthorizationEnvironmentPassword)) {
// do not try to get credentials as it has been already passed by sheet
- os_log(AUTHD_LOG, "engine: ingoring builtin sheet authenticate");
+ os_log(AUTHD_LOG, "engine: ignoring builtin sheet authenticate");
} else {
// sheet itself did the authenticate the user
os_log(AUTHD_LOG, "engine: running builtin sheet authenticate");
auth_items_copy(engine->hints, environment);
}
+ // First restore all context values from the AuthorizationRef
+ auth_items_t decrypted_items = auth_items_create();
+ require_action(decrypted_items != NULL, done, os_log_error(AUTHD_LOG, "engine: enable to create items"));
+ auth_items_content_copy(decrypted_items, auth_token_get_context(engine->auth));
+ auth_items_decrypt(decrypted_items, auth_token_get_encryption_key(engine->auth));
+ auth_items_copy(engine->context, decrypted_items);
+ CFReleaseSafe(decrypted_items);
+
if (engine->flags & kAuthorizationFlagSheet) {
CFTypeRef extract_password_entitlement = auth_token_copy_entitlement_value(engine->auth, "com.apple.authorization.extract-password");
if (extract_password_entitlement && (CFGetTypeID(extract_password_entitlement) == CFBooleanGetTypeID()) && extract_password_entitlement == kCFBooleanTrue) {
if (!enforced_entitlement()) {
save_password = true;
}
- const char *user = auth_items_get_string(environment, kAuthorizationEnvironmentUsername);
+
+ // Try to use/update fresh context values from the environment
+ require_action(environment, done, os_log_debug(AUTHD_LOG, "engine: Missing environment for sheet authorization"); status = errAuthorizationDenied);
+
+ const char *user = auth_items_get_string(environment, kAuthorizationEnvironmentUsername);
require_action(user, done, os_log_debug(AUTHD_LOG, "engine: Missing username"); status = errAuthorizationDenied);
auth_items_set_string(engine->context, kAuthorizationEnvironmentUsername, user);
_extract_password_from_la(engine);
engine->preauthorizing = true;
}
-
- auth_items_t decrypted_items = auth_items_create();
- require_action(decrypted_items != NULL, done, os_log_error(AUTHD_LOG, "engine: enable to create items"));
- auth_items_content_copy(decrypted_items, auth_token_get_context(engine->auth));
- auth_items_decrypt(decrypted_items, auth_token_get_encryption_key(engine->auth));
- auth_items_copy(engine->context, decrypted_items);
- CFReleaseSafe(decrypted_items);
engine->dismissed = false;
auth_rights_clear(engine->grantedRights);
errSecCSBadTeamIdentifier = -66997, /* a Team Identifier is wrong or inappropriate */
errSecCSSignatureUntrusted = -66996, /* signature is valid but signer is not trusted */
errSecMultipleExecSegments = -66995, /* the image contains multiple executable segments */
+ errSecCSInvalidEntitlements = -66994, /* invalid entitlement plist */
+ errSecCSInvalidRuntimeVersion = -66993, /* an invalid runtime version was explicitly set */
};
/*
immediately if it becomes invalid.
@constant kSecCodeSignatureForceExpiration
Forces the kSecCSConsiderExpiration flag on all validations of the code.
+ @constant kSecCodeSignatureRuntime
+ Instructs the kernel to apply runtime hardening policies as required by the
+ hardened runtime version
*/
typedef CF_OPTIONS(uint32_t, SecCodeSignatureFlags) {
kSecCodeSignatureHost = 0x0001, /* may host guest code */
kSecCodeSignatureRestrict = 0x0800, /* restrict dyld loading */
kSecCodeSignatureEnforcement = 0x1000, /* enforce code signing */
kSecCodeSignatureLibraryValidation = 0x2000, /* library validation required */
+ kSecCodeSignatureRuntime = 0x10000, /* apply runtime hardening policies */
};
/*!
data that is usually written to separate files. This is the format of
detached signatures if the program is capable of having multiple architectures.
@constant kSecCodeMagicEntitlement Magic number for a standard entitlement blob.
+ @constant kSecCodeMagicEntitlementDER Magic number for a DER entitlement blob.
@constant kSecCodeMagicByte The first byte (in NBO) shared by all these magic
numbers. This is not a valid ASCII character; test for this to distinguish
between text and binary data if you expect a code signing-related binary blob.
kSecCodeMagicEmbeddedSignature = 0xfade0cc0, /* single-architecture embedded signature */
kSecCodeMagicDetachedSignature = 0xfade0cc1, /* detached multi-architecture signature */
kSecCodeMagicEntitlement = 0xfade7171, /* entitlement blob */
-
+ kSecCodeMagicEntitlementDER = 0xfade7172, /* entitlement DER blob */
+
kSecCodeMagicByte = 0xfa /* shared first byte */
};
#include <Security/SecCertificate.h>
#include <Security/SecCertificatePriv.h>
#include <vector>
+#include <errno.h>
namespace Security {
else
return false;
}
+
+ uint32_t parseRuntimeVersion(std::string& runtime)
+ {
+ uint32_t version = 0;
+ char* cursor = const_cast<char*>(runtime.c_str());
+ char* end = cursor + runtime.length();
+ char* nxt = NULL;
+ long component = 0;
+ int component_shift = 16;
+
+ // x should convert to 0x00XX0000
+ // x.y should convert to 0x00XXYY00
+ // x.y.z should covert to 0x00XXYYZZ
+ // 0, 0.0, and 0.0.0 are rejected
+ // anything else should be rejected
+ while (cursor < end) {
+ nxt = NULL;
+ errno = 0;
+ component = strtol(cursor, &nxt, 10);
+ if (cursor == nxt ||
+ (errno != 0) ||
+ (component < 0 || component > UINT8_MAX)) {
+ secdebug("signer", "Runtime version: %s is invalid", runtime.c_str());
+ MacOSError::throwMe(errSecCSInvalidRuntimeVersion);
+ }
+ version |= (component & 0xff) << component_shift;
+ component_shift -= 8;
+
+ if (*nxt == '\0') {
+ break;
+ }
+
+ if (*nxt != '.' || component_shift < 0 || (nxt + 1) == end) {
+ // Catch a trailing "."
+ secdebug("signer", "Runtime version: %s is invalid", runtime.c_str());
+ MacOSError::throwMe(errSecCSInvalidRuntimeVersion);
+ }
+ cursor = nxt + 1;
+ }
+
+ if (version == 0) {
+ secdebug("signer","Runtime version: %s is a version of zero", runtime.c_str());
+ MacOSError::throwMe(errSecCSInvalidRuntimeVersion);
+ }
+
+ return version;
+ }
};
// Construct a SecCodeSigner
//
SecCodeSigner::SecCodeSigner(SecCSFlags flags)
- : mOpFlags(flags), mLimitedAsync(NULL)
+ : mOpFlags(flags), mLimitedAsync(NULL), mRuntimeVersionOverride(0)
{
}
if (CFNumberRef cmsSize = get<CFNumberRef>(CFSTR("cmssize")))
state.mCMSSize = cfNumber<size_t>(cmsSize);
else
- state.mCMSSize = 9000; // likely big enough
+ state.mCMSSize = 18000; // big enough for now, not forever.
// metadata preservation options
if (CFNumberRef preserve = get<CFNumberRef>(kSecCodeSignerPreserveMetadata)) {
state.mTimestampAuthentication = get<SecIdentityRef>(kSecCodeSignerTimestampAuthentication);
state.mTimestampService = get<CFURLRef>(kSecCodeSignerTimestampServer);
state.mNoTimeStampCerts = getBool(kSecCodeSignerTimestampOmitCertificates);
+
+ if (CFStringRef runtimeVersionOverride = get<CFStringRef>(kSecCodeSignerRuntimeVersion)) {
+ std::string runtime = cfString(runtimeVersionOverride);
+ if (runtime.empty()) {
+ MacOSError::throwMe(errSecCSInvalidRuntimeVersion);
+ }
+ state.mRuntimeVersionOverride = parseRuntimeVersion(runtime);
+ }
}
bool mWantTimeStamp; // use a Timestamp server
bool mNoTimeStampCerts; // don't request certificates with timestamping request
LimitedAsync *mLimitedAsync; // limited async workers for verification
+ uint32_t mRuntimeVersionOverride; // runtime Version Override
};
kSecCSConsiderExpiration
| kSecCSStrictValidate
| kSecCSRestrictSidebandData
- | kSecCSEnforceRevocationChecks);
+ | kSecCSEnforceRevocationChecks
+ );
SecPointer<SecCode> code = SecCode::required(codeRef);
code->checkValidity(flags);
if (const SecRequirement *req = SecRequirement::optional(requirementRef))
const CFStringRef kSecCodeInfoTrust = CFSTR("trust");
const CFStringRef kSecCodeInfoUnique = CFSTR("unique");
const CFStringRef kSecCodeInfoCdHashes = CFSTR("cdhashes");
+const CFStringRef kSecCodeInfoRuntimeVersion = CFSTR("runtime-version");
const CFStringRef kSecCodeInfoCodeDirectory = CFSTR("CodeDirectory");
const CFStringRef kSecCodeInfoResourceDirectory = CFSTR("ResourceDirectory");
/* DiskInfoRepInfo types */
-const CFStringRef kSecCodeInfoDiskRepOSPlatform = CFSTR("OSPlatform");
-const CFStringRef kSecCodeInfoDiskRepOSVersionMin = CFSTR("OSVersionMin");
-const CFStringRef kSecCodeInfoDiskRepOSSDKVersion = CFSTR("SDKVersion");
-const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation = CFSTR("NoLibraryValidation");
+const CFStringRef kSecCodeInfoDiskRepVersionPlatform = CFSTR("VersionPlatform");
+const CFStringRef kSecCodeInfoDiskRepVersionMin = CFSTR("VersionMin");
+const CFStringRef kSecCodeInfoDiskRepVersionSDK = CFSTR("VersionSDK");
+const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation = CFSTR("NoLibraryValidation");
OSStatus SecCodeCopySigningInformation(SecStaticCodeRef codeRef, SecCSFlags flags,
extern const CFStringRef kSecCodeInfoTrust; /* Signing */
extern const CFStringRef kSecCodeInfoUnique; /* generic */
extern const CFStringRef kSecCodeInfoCdHashes; /* generic */
+extern const CFStringRef kSecCodeInfoRuntimeVersion; /*generic */
OSStatus SecCodeCopySigningInformation(SecStaticCodeRef code, SecCSFlags flags,
CFDictionaryRef * __nonnull CF_RETURNS_RETAINED information);
extern const CFStringRef kSecCodeInfoDiskRepInfo; /* Internal */
extern const CFStringRef kSecCodeInfoResourceDirectory; /* Internal */
-extern const CFStringRef kSecCodeInfoDiskRepOSPlatform; /* Number */
-extern const CFStringRef kSecCodeInfoDiskRepOSVersionMin; /* Number */
-extern const CFStringRef kSecCodeInfoDiskRepOSSDKVersion; /* Number */
+extern const CFStringRef kSecCodeInfoDiskRepVersionPlatform; /* Number */
+extern const CFStringRef kSecCodeInfoDiskRepVersionMin; /* Number */
+extern const CFStringRef kSecCodeInfoDiskRepVersionSDK; /* Number */
extern const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation; /* String */
/*!
const CFStringRef kSecCodeSignerPreserveMetadata = CFSTR("preserve-metadata");
const CFStringRef kSecCodeSignerTeamIdentifier = CFSTR("teamidentifier");
const CFStringRef kSecCodeSignerPlatformIdentifier = CFSTR("platform-identifier");
+const CFStringRef kSecCodeSignerRuntimeVersion = CFSTR("runtime-version");
| kSecCSSignV1
| kSecCSSignNoV1
| kSecCSSignBundleRoot
- | kSecCSSignStrictPreflight);
+ | kSecCSSignStrictPreflight
+ | kSecCSSignGeneratePEH
+ | kSecCSSignGenerateEntitlementDER);
SecPointer<SecCodeSigner> signer = new SecCodeSigner(flags);
signer->parameters(parameters);
CodeSigning::Required(signerRef) = signer->handle();
on the verifying system.
The default is to embed enough certificates to ensure proper verification of Apple-generated
timestamp signatures.
+ @constant kSecCodeSignerRuntimeVersion A CFString indicating the version of runtime hardening policies
+ that the process should be opted into. The string should be of the form "x", "x.x", or "x.x.x" where
+ x is a number between 0 and 255. This parameter is optional. If the signer specifies
+ kSecCodeSignatureRuntime but does not provide this parameter, the runtime version will be the SDK
+ version built into the Mach-O.
+
*/
extern const CFStringRef kSecCodeSignerApplicationData;
extern const CFStringRef kSecCodeSignerDetached;
extern const CFStringRef kSecCodeSignerPreserveMetadata;
extern const CFStringRef kSecCodeSignerTeamIdentifier;
extern const CFStringRef kSecCodeSignerPlatformIdentifier;
+extern const CFStringRef kSecCodeSignerRuntimeVersion;
enum {
kSecCodeSignerPreserveIdentifier = 1 << 0, // preserve signing identifier
kSecCodeSignerPreserveResourceRules = 1 << 3, // preserve resource rules (and thus resources)
kSecCodeSignerPreserveFlags = 1 << 4, // preserve signing flags
kSecCodeSignerPreserveTeamIdentifier = 1 << 5, // preserve team identifier flags
- kSecCodeSignerPreserveDigestAlgorithm = 1 << 6, // preserve digest algorithms used
+ kSecCodeSignerPreserveDigestAlgorithm = 1 << 6, // preserve digest algorithms used
+ kSecCodeSignerPreservePEH = 1 << 7, // preserve pre-encryption hashes
+ kSecCodeSignerPreserveRuntime = 1 << 8, // preserve the runtime version
};
kSecCSSignNoV1 = 1 << 5, // do not include V1 form
kSecCSSignBundleRoot = 1 << 6, // include files in bundle root
kSecCSSignStrictPreflight = 1 << 7, // fail signing operation if signature would fail strict validation
+ kSecCSSignGeneratePEH = 1 << 8, // generate pre-encryption hashes
+ kSecCSSignGenerateEntitlementDER = 1 << 9, // generate entitlement DER
};
| kSecCSRestrictSymlinks
| kSecCSRestrictToAppLike
| kSecCSUseSoftwareSigningCert
+ | kSecCSValidatePEH
);
if (errors)
kSecCSRestrictToAppLike = 1 << 8,
kSecCSRestrictSidebandData = 1 << 9,
kSecCSUseSoftwareSigningCert = 1 << 10,
+ kSecCSValidatePEH = 1 << 11,
};
OSStatus SecStaticCodeCheckValidity(SecStaticCodeRef staticCode, SecCSFlags flags,
#include "sigblob.h"
#include "resources.h"
#include "detachedrep.h"
+#include "signerutils.h"
#if TARGET_OS_OSX
#include "csdatabase.h"
#endif
mResourcesValidContext = NULL;
}
mDir = NULL;
+ mCodeDirectories.clear();
mSignature = NULL;
for (unsigned n = 0; n < cdSlotCount; n++)
mCache[n] = NULL;
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
try {
- CodeDirectoryMap candidates;
- if (loadCodeDirectories(candidates)) {
+ CodeDirectoryMap const *candidates = codeDirectories(check);
+ if (candidates != NULL) {
CodeDirectory::HashAlgorithm type = CodeDirectory::bestHashOf(mHashAlgorithms);
- mDir = candidates[type]; // and the winner is...
- candidates.swap(mCodeDirectories);
+ mDir = candidates->at(type); // and the winner is...
}
} catch (...) {
if (check)
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 (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) {
const CodeDirectory* cd = this->codeDirectory();
if (CFCopyRef<CFDataRef> component = this->component(slot)) {
if (hash) {
- const void *slotHash = (*cd)[slot];
+ 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
CFRef<CFDictionaryRef> inf = diskRepInformation();
try {
CFDictionary info(diskRepInformation(), errSecCSNotSupported);
- uint32_t platformCmd =
- cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepOSPlatform, errSecCSNotSupported), 0);
+ uint32_t platform =
+ cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionPlatform, errSecCSNotSupported), 0);
uint32_t sdkVersion =
- cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepOSSDKVersion, errSecCSNotSupported), 0);
+ cfNumber(info.get<CFNumberRef>(kSecCodeInfoDiskRepVersionSDK, errSecCSNotSupported), 0);
- if (platformCmd != LC_VERSION_MIN_IPHONEOS || sdkVersion >= 0x00090000) {
+ if (platform != PLATFORM_IOS || sdkVersion >= 0x00090000) {
return false;
}
} catch (const MacOSError &error) {
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
void detachedSignature(CFDataRef sig); // attach an explicitly given detached signature
void checkForSystemSignature(); // check for and attach system-supplied detached signature
+ typedef std::map<CodeDirectory::HashAlgorithm, CFCopyRef<CFDataRef> > CodeDirectoryMap;
+
const CodeDirectory *codeDirectory(bool check = true) const;
+ const CodeDirectoryMap *codeDirectories(bool check = true) const;
CodeDirectory::HashAlgorithm hashAlgorithm() const { return codeDirectory()->hashType; }
CodeDirectory::HashAlgorithms hashAlgorithms() const { return mHashAlgorithms; }
CFDataRef cdHash();
bool trustedSigningCertChain() { return mTrustedSigningCertChain; }
#endif
+ void handleOtherArchitectures(void (^handle)(SecStaticCode* other));
+
public:
void staticValidate(SecCSFlags flags, const SecRequirement *req);
void staticValidateCore(SecCSFlags flags, const SecRequirement *req);
protected:
- typedef std::map<CodeDirectory::HashAlgorithm, CFCopyRef<CFDataRef> > CodeDirectoryMap;
bool loadCodeDirectories(CodeDirectoryMap& cdMap) const;
protected:
static void checkOptionalResource(CFTypeRef key, CFTypeRef value, void *context);
bool hasWeakResourceRules(CFDictionaryRef rulesDict, uint32_t version, CFArrayRef allowedOmissions);
- void handleOtherArchitectures(void (^handle)(SecStaticCode* other));
-
private:
void validateOtherVersions(CFURLRef path, SecCSFlags flags, SecRequirementRef req, SecStaticCode *code);
bool checkfix30814861(string path, bool addition);
checkModifiedFile(files, cdResourceDirSlot);
checkModifiedFile(files, cdTopDirectorySlot);
checkModifiedFile(files, cdEntitlementSlot);
+ checkModifiedFile(files, cdEntitlementDERSlot);
checkModifiedFile(files, cdRepSpecificSlot);
for (CodeDirectory::Slot slot = cdAlternateCodeDirectorySlots; slot < cdAlternateCodeDirectoryLimit; ++slot)
checkModifiedFile(files, slot);
mExecSegOffset(0),
mExecSegLimit(0),
mExecSegFlags(0),
+ mGeneratePreEncryptHashes(false),
+ mRuntimeVersion(0),
mDir(NULL)
{
mDigestLength = (uint32_t)MakeHash<Builder>(this)->digestLength();
size_t CodeDirectory::Builder::fixedSize(const uint32_t version)
{
size_t cdSize = sizeof(CodeDirectory);
+ if (version < supportsPreEncrypt)
+ cdSize -= sizeof(mDir->runtime) + sizeof(mDir->preEncryptOffset);
if (version < supportsExecSegment)
cdSize -= sizeof(mDir->execSegBase) + sizeof(mDir->execSegLimit) + sizeof(mDir->execSegFlags);
if (version < supportsCodeLimit64)
if (mTeamID.size())
offset += mTeamID.size() + 1; // size of teamID (with null byte)
offset += (mCodeSlots + mSpecialSlots) * mDigestLength; // hash vector
+
+ if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty()) {
+ offset += mCodeSlots * mDigestLength;
+ }
+
if (offset <= offset0)
UnixError::throwMe(ENOEXEC);
size_t teamIDLength = mTeamID.size() + 1;
// Determine the version
- if (mExecSegLimit > 0) {
+ if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty() || mRuntimeVersion) {
version = currentVersion;
+ } else if (mExecSegLimit > 0) {
+ version = supportsExecSegment;
} else if (mExecLength > UINT32_MAX) {
version = supportsCodeLimit64;
} else if (mTeamID.size()) {
mDir->execSegBase = mExecSegOffset;
mDir->execSegLimit = mExecSegLimit;
mDir->execSegFlags = mExecSegFlags;
+ mDir->runtime = mRuntimeVersion;
// locate and fill flex fields
size_t offset = fixedSize(mDir->version);
memcpy(mDir->teamID(), mTeamID.c_str(), teamIDLength);
offset += teamIDLength;
}
+
// (add new flexibly-allocated fields here)
+ /* Pre-encrypt hashes come before normal hashes, so that the kernel can free
+ * the normal, potentially post-encrypt hashes away easily. */
+ if (mGeneratePreEncryptHashes || !mPreservedPreEncryptHashMap.empty()) {
+ mDir->preEncryptOffset = (uint32_t)offset;
+ offset += mCodeSlots * mDigestLength;
+ }
+
mDir->hashOffset = (uint32_t)(offset + mSpecialSlots * mDigestLength);
offset += (mSpecialSlots + mCodeSlots) * mDigestLength;
+
assert(offset == total); // matches allocated size
(void)offset;
// fill special slots
- memset((*mDir)[(int)-mSpecialSlots], 0, mDigestLength * mSpecialSlots);
+ memset(mDir->getSlotMutable((int)-mSpecialSlots, false), 0, mDigestLength * mSpecialSlots);
for (size_t slot = 1; slot <= mSpecialSlots; ++slot)
- memcpy((*mDir)[(int)-slot], specialSlot((SpecialSlot)slot), mDigestLength);
+ memcpy(mDir->getSlotMutable((int)-slot, false), specialSlot((SpecialSlot)slot), mDigestLength);
// fill code slots
mExec.seek(mExecOffset);
if (mPageSize)
thisPage = min(thisPage, mPageSize);
MakeHash<Builder> hasher(this);
- generateHash(hasher, mExec, (*mDir)[slot], thisPage);
+ generateHash(hasher, mExec, mDir->getSlotMutable(slot, false), thisPage);
+ if (mGeneratePreEncryptHashes && mPreservedPreEncryptHashMap.empty()) {
+ memcpy(mDir->getSlotMutable(slot, true), mDir->getSlot(slot, false),
+ mDir->hashSize);
+ }
remaining -= thisPage;
}
assert(remaining == 0);
+
+ PreEncryptHashMap::iterator preEncrypt =
+ mPreservedPreEncryptHashMap.find(mHashType);
+ if (preEncrypt != mPreservedPreEncryptHashMap.end()) {
+ memcpy(mDir->getSlotMutable(0, true),
+ CFDataGetBytePtr(preEncrypt->second),
+ mCodeSlots * mDigestLength);
+ mPreservedPreEncryptHashMap.erase(preEncrypt->first); // Releases the CFData memory.
+ }
// all done. Pass ownership to caller
return mDir;
mExecSegOffset = base; mExecSegLimit = limit; mExecSegFlags = flags; }
void addExecSegFlags(uint64_t flags) { mExecSegFlags |= flags; }
+ typedef std::map<CodeDirectory::HashAlgorithm, CFCopyRef<CFDataRef> >
+ PreEncryptHashMap;
+
+ void generatePreEncryptHashes(bool pre) { mGeneratePreEncryptHashes = pre; }
+ void preservePreEncryptHashMap(PreEncryptHashMap preEncryptHashMap) {
+ mPreservedPreEncryptHashMap = preEncryptHashMap;
+ }
+
+ void runTimeVersion(uint32_t runtime) {
+ mRuntimeVersion = runtime;
+ }
+
size_t size(const uint32_t version); // calculate size
CodeDirectory *build(); // build CodeDirectory and return it
size_t fixedSize(const uint32_t version); // calculate fixed size of the CodeDirectory
uint64_t mExecSegLimit; // limit of executable segment
uint64_t mExecSegFlags; // executable segment flags
+ bool mGeneratePreEncryptHashes; // whether to also generate new pre-encrypt hashes
+ PreEncryptHashMap mPreservedPreEncryptHashMap; // existing pre-encrypt hashes to be set
+
+ uint32_t mRuntimeVersion; // Hardened Runtime Version
+
CodeDirectory *mDir; // what we're building
};
return kSecCS_TOPDIRECTORYFILE;
case cdEntitlementSlot:
return kSecCS_ENTITLEMENTFILE;
+ case cdEntitlementDERSlot:
+ return kSecCS_ENTITLEMENTDERFILE;
case cdRepSpecificSlot:
return kSecCS_REPSPECIFICFILE;
default:
case cdSignatureSlot:
return cdComponentPerArchitecture; // raw
case cdEntitlementSlot:
+ case cdEntitlementDERSlot:
return cdComponentIsBlob; // global
case cdIdentificationSlot:
return cdComponentPerArchitecture; // raw
if (version > currentVersion)
secinfo("codedir", "%p version 0x%x newer than current 0x%x",
this, uint32_t(version), currentVersion);
-
+
+ bool hasPreEncryptHashes = version >= supportsPreEncrypt && preEncryptOffset != 0;
+
// now check interior offsets for validity
if (!stringAt(identOffset))
MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range
MacOSError::throwMe(errSecCSSignatureFailed); // identifier out of blob range
if (!contains(hashOffset - int64_t(hashSize) * nSpecialSlots, hashSize * (int64_t(nSpecialSlots) + nCodeSlots)))
MacOSError::throwMe(errSecCSSignatureFailed); // hash array out of blob range
+ if (hasPreEncryptHashes && !contains(preEncryptOffset, hashSize * (int64_t(nCodeSlots))))
+ MacOSError::throwMe(errSecCSSignatureFailed); // pre-encrypt array out of blob range
if (const Scatter *scatter = this->scatterVector()) {
// the optional scatter vector is terminated with an element having (count == 0)
unsigned int pagesConsumed = 0;
break;
pagesConsumed += scatter->count;
}
- if (!contains((*this)[pagesConsumed-1], hashSize)) // referenced too many main hash slots
+ if (!contains(getSlot(pagesConsumed-1, false), hashSize) ||
+ (hasPreEncryptHashes && !contains(getSlot(pagesConsumed-1, true), hashSize))) // referenced too many main hash slots
MacOSError::throwMe(errSecCSSignatureFailed);
}
//
// Validate a slot against data in memory.
//
-bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot) const
+bool CodeDirectory::validateSlot(const void *data, size_t length, Slot slot, bool preEncrypt) const
{
secinfo("codedir", "%p validating slot %d", this, int(slot));
MakeHash<CodeDirectory> hasher(this);
Hashing::Byte digest[hasher->digestLength()];
generateHash(hasher, data, length, digest);
- return memcmp(digest, (*this)[slot], hasher->digestLength()) == 0;
+ return memcmp(digest, getSlot(slot, preEncrypt), hasher->digestLength()) == 0;
}
// Validate a slot against the contents of an open file. At most 'length' bytes
// will be read from the file.
//
-bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot) const
+bool CodeDirectory::validateSlot(FileDesc fd, size_t length, Slot slot, bool preEncrypt) const
{
MakeHash<CodeDirectory> hasher(this);
Hashing::Byte digest[hasher->digestLength()];
generateHash(hasher, fd, digest, length);
- return memcmp(digest, (*this)[slot], hasher->digestLength()) == 0;
+ return memcmp(digest, getSlot(slot, preEncrypt), hasher->digestLength()) == 0;
}
bool CodeDirectory::slotIsPresent(Slot slot) const
{
if (slot >= -Slot(nSpecialSlots) && slot < Slot(nCodeSlots)) {
- const Hashing::Byte *digest = (*this)[slot];
+ const Hashing::Byte *digest = getSlot(slot, false);
for (unsigned n = 0; n < hashSize; n++)
if (digest[n])
return true; // non-zero digest => present
//
// Generate the canonical cdhash - the internal hash of the CodeDirectory itself.
-// We currently truncate to 20 bytes because that's what the kernel can deal with.
+// With 'truncate' truncates to 20 bytes, because that's what's commonly used.
//
-CFDataRef CodeDirectory::cdhash() const
+CFDataRef CodeDirectory::cdhash(bool truncate) const
{
MakeHash<CodeDirectory> hash(this);
Hashing::Byte digest[hash->digestLength()];
hash->update(this, this->length());
hash->finish(digest);
- return makeCFData(digest, min(hash->digestLength(), size_t(kSecCodeCDHashLength)));
+ return makeCFData(digest,
+ truncate ? min(hash->digestLength(), size_t(kSecCodeCDHashLength)) :
+ hash->digestLength());
}
std::string CodeDirectory::screeningCode() const
{
if (slotIsPresent(-cdInfoSlot)) // has Info.plist
- return "I" + hexHash((*this)[-cdInfoSlot]); // use Info.plist hash
+ return "I" + hexHash(getSlot(-cdInfoSlot, false)); // use Info.plist hash
if (slotIsPresent(-cdRepSpecificSlot)) // has Info.plist
- return "R" + hexHash((*this)[-cdRepSpecificSlot]); // use Info.plist hash
+ return "R" + hexHash(getSlot(-cdRepSpecificSlot, false)); // use Info.plist hash
if (pageSize == 0) // good-enough proxy for "not a Mach-O file"
- return "M" + hexHash((*this)[0]); // use hash of main executable
+ return "M" + hexHash(getSlot(0, false)); // use hash of main executable
return "N"; // no suitable screening code
}
{ "restrict", kSecCodeSignatureRestrict, true },
{ "enforcement", kSecCodeSignatureEnforcement, true },
{ "library-validation", kSecCodeSignatureLibraryValidation, true },
+ { "runtime", kSecCodeSignatureRuntime, true },
{ NULL }
};
#define kSecCS_ENTITLEMENTFILE "CodeEntitlements" // entitlement configuration
#define kSecCS_REPSPECIFICFILE "CodeRepSpecific" // DiskRep-specific use slot
#define kSecCS_TOPDIRECTORYFILE "CodeTopDirectory" // Top-level directory list
-
+#define kSecCS_ENTITLEMENTDERFILE "CodeEntitlementDER" // DER entitlement representation
//
// Special hash slot values. In a CodeDirectory, these show up at negative slot
cdTopDirectorySlot = 4, // Application specific slot
cdEntitlementSlot = 5, // embedded entitlement configuration
cdRepSpecificSlot = 6, // for use by disk rep
+ cdEntitlementDERSlot = 7, // DER representation of entitlements
// (add further primary slot numbers here)
cdSlotCount, // total number of special slots (+1 for slot 0)
Endian<uint64_t> execSegLimit; // limit of executable segment
Endian<uint64_t> execSegFlags; // exec segment flags
+ Endian<uint32_t> runtime; // Runtime version encoded as an unsigned int
+ Endian<uint32_t> preEncryptOffset; // offset of pre-encrypt hash slots
+
// works with the version field; see comments above
- static const uint32_t currentVersion = 0x20400; // "version 2.4"
+ static const uint32_t currentVersion = 0x20500; // "version 2.5"
static const uint32_t compatibilityLimit = 0x2F000; // "version 3 with wiggle room"
static const uint32_t earliestVersion = 0x20001; // earliest supported version
static const uint32_t supportsTeamID = 0x20200; // first version to support team ID option
static const uint32_t supportsCodeLimit64 = 0x20300; // first version to support codeLimit64
static const uint32_t supportsExecSegment = 0x20400; // first version to support exec base and limit
+ static const uint32_t supportsPreEncrypt = 0x20500; // first version to support pre-encrypt hashes and runtime version
void checkIntegrity() const; // throws if inconsistent or unsupported version
// main hash array access
SpecialSlot maxSpecialSlot() const;
- unsigned char *operator [] (Slot slot)
+ unsigned char *getSlotMutable (Slot slot, bool preEncrypt)
{
assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots));
- return at<unsigned char>(hashOffset) + hashSize * slot;
+
+ if (preEncrypt) {
+ if (version >= supportsPreEncrypt && preEncryptOffset != 0) {
+ assert(slot >= 0);
+ return at<unsigned char>(preEncryptOffset) + hashSize * slot;
+ } else {
+ return NULL;
+ }
+ } else {
+ return at<unsigned char>(hashOffset) + hashSize * slot;
+ }
}
-
- const unsigned char *operator [] (Slot slot) const
+
+ const unsigned char *getSlot (Slot slot, bool preEncrypt) const
{
- assert(slot >= int(-nSpecialSlots) && slot < int(nCodeSlots));
- return at<unsigned char>(hashOffset) + hashSize * slot;
+ CodeDirectory *cd = const_cast<CodeDirectory *>(this);
+ return const_cast<const unsigned char *>(cd->getSlotMutable(slot, preEncrypt));
}
-
+
//
// The main page hash array can be "scattered" across the code file
// by specifying an array of Scatter elements, terminated with an
uint64_t execSegmentLimit() const { return (version >= supportsExecSegment) ? execSegLimit.get() : 0; }
uint64_t execSegmentFlags() const { return (version >= supportsExecSegment) ? execSegFlags.get() : 0; }
+ const unsigned char *preEncryptHashes() const { return getSlot(0, true); }
+
+ uint32_t runtimeVersion() const {return (version >= supportsPreEncrypt) ? runtime.get() : 0; }
+
public:
- bool validateSlot(const void *data, size_t size, Slot slot) const; // validate memory buffer against page slot
- bool validateSlot(UnixPlusPlus::FileDesc fd, size_t size, Slot slot) const; // read and validate file
+ bool validateSlot(const void *data, size_t size, Slot slot, bool preEncrypted) const; // validate memory buffer against page slot
+ bool validateSlot(UnixPlusPlus::FileDesc fd, size_t size, Slot slot, bool preEncrypted) const; // read and validate file
bool slotIsPresent(Slot slot) const;
class Builder;
public:
static DynamicHash *hashFor(HashAlgorithm hashType); // create a DynamicHash subclass for (hashType) digests
DynamicHash *getHash() const { return hashFor(this->hashType); } // make one for me
- CFDataRef cdhash() const;
+ CFDataRef cdhash(bool truncate = true) const;
static void multipleHashFileData(UnixPlusPlus::FileDesc fd, size_t limit, HashAlgorithms types, void (^action)(HashAlgorithm type, DynamicHash* hasher));
bool verifyMemoryContent(CFDataRef data, const Byte* digest) const;
{
if (context.digestAlgorithms().empty()) {
auto_ptr<MachO> macho(mainExecutableImage()->architecture());
-
- if (const version_min_command *version = macho->findMinVersion()) {
- uint32_t limit = 0;
- switch (macho->flip(version->cmd)) {
- case LC_VERSION_MIN_MACOSX:
+
+ uint32_t limit = 0;
+ switch (macho->platform()) {
+ case 0:
+ // If we don't know the platform, we stay agile.
+ return;
+ case PLATFORM_MACOS:
+ // 10.11.4 had first proper sha256 support.
limit = (10 << 16 | 11 << 8 | 4 << 0);
break;
-#if 0 /* need updated libMIS before we can do this switch */
- case LC_VERSION_MIN_IPHONEOS:
- limit = (9 << 16 | 3 << 8);
- break;
- case LC_VERSION_MIN_WATCHOS:
- limit = (2 << 16 | 2 << 8);
- break;
- case LC_VERSION_MIN_TVOS:
- limit = (9 << 16 | 2 << 8);
+ case PLATFORM_TVOS:
+ case PLATFORM_IOS:
+ // iOS 11 and tvOS 11 had first proper sha256 support.
+ limit = (11 << 16 | 0 << 8 | 0 << 0);
break;
+ case PLATFORM_WATCHOS:
+ // We stay agile on the watch right now.
+ return;
default:
+ // All other platforms are assumed to be new and support SHA256.
break;
-#else
- case LC_VERSION_MIN_IPHONEOS:
- case LC_VERSION_MIN_WATCHOS:
- case LC_VERSION_MIN_TVOS:
- return;
- default:
- break;
-#endif
- }
- if (macho->flip(version->version) >= limit) {
- // young enough not to need SHA-1 legacy support
- context.setDigestAlgorithm(kSecCodeSignatureHashSHA256);
- }
+ }
+ if (macho->minVersion() >= limit) {
+ // young enough not to need SHA-1 legacy support
+ context.setDigestAlgorithm(kSecCodeSignatureHashSHA256);
}
}
}
}
bool MachORep::needsExecSeg(const MachO& macho) {
- if (const version_min_command *version = macho.findMinVersion()) {
- uint32_t min = UINT32_MAX;
-
- switch (macho.flip(version->cmd)) {
- case LC_VERSION_MIN_IPHONEOS:
- case LC_VERSION_MIN_TVOS:
- min = (11 << 16 | 0 << 8);
- break;
- case LC_VERSION_MIN_WATCHOS:
- min = (4 << 16 | 0 << 8);
- break;
-
- default:
- /* macOS currently does not get this. */
- return false;
- }
-
- if (macho.flip(version->version) >= min) {
- return true;
- }
- }
-
- return false;
+ uint32_t platform = macho.platform();
+ // Everything embedded gets an exec segment.
+ return platform != 0 && platform != PLATFORM_MACOS;
}
size_t MachORep::execSegBase(const Architecture *arch)
auto_ptr<MachO> macho (mainExecutableImage()->architecture());
CFRef<CFDictionaryRef> info;
- if (const version_min_command *version = macho->findMinVersion()) {
-
+ uint32_t platform = 0;
+ uint32_t minVersion = 0;
+ uint32_t sdkVersion = 0;
+
+ if (macho->version(&platform, &minVersion, &sdkVersion)) {
+
+ /* These keys replace the old kSecCodeInfoDiskRepOSPlatform, kSecCodeInfoDiskRepOSVersionMin
+ * and kSecCodeInfoDiskRepOSSDKVersion. The keys were renamed because we changed what value
+ * "platform" represents: For the old key, the actual load command (e.g. LC_VERSION_MIN_MACOSX)
+ * was returned; for the new key, we return one of the PLATFORM_* values used by LC_BUILD_VERSION.
+ *
+ * The keys are private and undocumented, and maintaining a translation table between the old and
+ * new domain would provide little value at high cost, but we do remove the old keys to make
+ * the change obvious.
+ */
+
info.take(cfmake<CFMutableDictionaryRef>("{%O = %d,%O = %d,%O = %d}",
- kSecCodeInfoDiskRepOSPlatform, macho->flip(version->cmd),
- kSecCodeInfoDiskRepOSVersionMin, macho->flip(version->version),
- kSecCodeInfoDiskRepOSSDKVersion, macho->flip(version->sdk)));
+ kSecCodeInfoDiskRepVersionPlatform, platform,
+ kSecCodeInfoDiskRepVersionMin, minVersion,
+ kSecCodeInfoDiskRepVersionSDK, sdkVersion));
- if (macho->flip(version->cmd) == LC_VERSION_MIN_MACOSX &&
- macho->flip(version->sdk) < (10 << 16 | 9 << 8))
+ if (platform == PLATFORM_MACOS && sdkVersion < (10 << 16 | 9 << 8))
{
info.take(cfmake<CFMutableDictionaryRef>("{+%O, %O = 'OS X SDK version before 10.9 does not support Library Validation'}",
info.get(),
{
// save the Info.plist slot
if (cd->slotIsPresent(cdInfoSlot)) {
- mInfoPlistHash.take(makeCFData((*cd)[cdInfoSlot], cd->hashSize));
+ mInfoPlistHash.take(makeCFData(cd->getSlot(cdInfoSlot, false), cd->hashSize));
}
}
this->length() - sizeof(EntitlementBlob));
}
+EntitlementDERBlob *EntitlementDERBlob::alloc(size_t length) {
+ size_t blobLength = length + sizeof(BlobCore);
+ if (blobLength < length) {
+ // overflow
+ return NULL;
+ }
+
+ EntitlementDERBlob *b = (EntitlementDERBlob *)malloc(blobLength);
+
+ if (b != NULL) {
+ b->BlobCore::initialize(kSecCodeMagicEntitlementDER, blobLength);
+ }
+
+ return b;
+}
} // end namespace CodeSigning
} // end namespace Security
CFDictionaryRef entitlements() const;
};
+//
+// Similar, but in DER representation.
+//
+class EntitlementDERBlob : public Blob<EntitlementDERBlob, kSecCodeMagicEntitlementDER> {
+public:
+ static EntitlementDERBlob *alloc(size_t length);
+
+ uint8_t *der() { return data; }
+ const uint8_t *der() const { return data; }
+ size_t derLength() const { return BlobCore::length() - sizeof(BlobCore); }
+
+private:
+ uint8_t data[0];
+};
+
} // end namespace CodeSigning
} // end namespace Security
//
// signer - Signing operation supervisor and controller
//
+#include "der_plist.h"
#include "signer.h"
#include "resources.h"
#include "signerutils.h"
entitlements = state.mEntitlementData;
if (!entitlements && (inherit & kSecCodeSignerPreserveEntitlements))
entitlements = code->component(cdEntitlementSlot);
+
+ generateEntitlementDER = signingFlags() & kSecCSSignGenerateEntitlementDER;
// work out the CodeDirectory flags word
bool haveCdFlags = false;
if (!rpath.empty()) {
buildResources(rrpath, rpath, resourceRules);
}
+
+
+
+ if (inherit & kSecCodeSignerPreservePEH) {
+ /* We need at least one architecture in all cases because we index our
+ * PreEncryptionMaps by architecture. However, only machOs have any
+ * architecture at all, for generic targets there will just be one
+ * PreEncryptionHashMap.
+ * So if the main executable is not a machO, we just choose the local
+ * (signer's) main architecture as dummy value for the first element in our pair. */
+ preEncryptMainArch = (code->diskRep()->mainExecutableIsMachO() ?
+ code->diskRep()->mainExecutableImage()->bestNativeArch() :
+ Architecture::local());
+
+ addPreEncryptHashes(preEncryptHashMaps[preEncryptMainArch], code);
+
+ code->handleOtherArchitectures(^(Security::CodeSigning::SecStaticCode *subcode) {
+ Universal *fat = subcode->diskRep()->mainExecutableImage();
+ assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice.
+ Architecture arch = fat->bestNativeArch(); // actually, only architecture for this slice.
+ addPreEncryptHashes(preEncryptHashMaps[arch], subcode);
+ });
+ }
+
+ if (inherit & kSecCodeSignerPreserveRuntime) {
+ /* We need at least one architecture in all cases because we index our
+ * RuntimeVersionMaps by architecture. However, only machOs have any
+ * architecture at all, for generic targets there will just be one
+ * RuntimeVersionMap.
+ * So if the main executable is not a machO, we just choose the local
+ * (signer's) main architecture as dummy value for the first element in our pair. */
+ runtimeVersionMainArch = (code->diskRep()->mainExecutableIsMachO() ?
+ code->diskRep()->mainExecutableImage()->bestNativeArch() :
+ Architecture::local());
+
+ addRuntimeVersions(runtimeVersionMap[runtimeVersionMainArch], code);
+
+ code->handleOtherArchitectures(^(Security::CodeSigning::SecStaticCode *subcode) {
+ Universal *fat = subcode->diskRep()->mainExecutableImage();
+ assert(fat && fat->narrowed()); // handleOtherArchitectures gave us a focused architecture slice.
+ Architecture arch = fat->bestNativeArch(); // actually, only architecture for this slice.
+ addRuntimeVersions(runtimeVersionMap[arch], subcode);
+ });
+ }
}
+void SecCodeSigner::Signer::addPreEncryptHashes(PreEncryptHashMap &map, SecStaticCode const *code) {
+ SecStaticCode::CodeDirectoryMap const *cds = code->codeDirectories();
+
+ if (cds != NULL) {
+ for(auto const& pair : *cds) {
+ CodeDirectory::HashAlgorithm const alg = pair.first;
+ CFDataRef const cddata = pair.second;
+
+ CodeDirectory const * cd =
+ reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(cddata));
+ if (cd->preEncryptHashes() != NULL) {
+ CFRef<CFDataRef> preEncrypt = makeCFData(cd->preEncryptHashes(),
+ cd->nCodeSlots * cd->hashSize);
+ map[alg] = preEncrypt;
+ }
+ }
+ }
+}
+
+void SecCodeSigner::Signer::addRuntimeVersions(RuntimeVersionMap &map, const SecStaticCode *code)
+{
+ SecStaticCode::CodeDirectoryMap const *cds = code->codeDirectories();
+
+ if (cds != NULL) {
+ for(auto const& pair : *cds) {
+ CodeDirectory::HashAlgorithm const alg = pair.first;
+ CFDataRef const cddata = pair.second;
+
+ CodeDirectory const * cd =
+ reinterpret_cast<const CodeDirectory *>(CFDataGetBytePtr(cddata));
+ if (cd->runtimeVersion()) {
+ map[alg] = cd->runtimeVersion();
+ }
+ }
+ }
+}
//
// Collect the resource seal for a program.
bool mainBinary = arch.source.get()->type() == MH_EXECUTE;
+ uint32_t runtimeVersion = 0;
+ if (cdFlags & kSecCodeSignatureRuntime) {
+ runtimeVersion = state.mRuntimeVersionOverride ? state.mRuntimeVersionOverride : arch.source.get()->sdkVersion();
+ }
+
arch.ireqs(requirements, rep->defaultRequirements(&arch.architecture, *this), context);
if (editor->attribute(writerNoGlobal)) // can't store globally, add per-arch
populate(arch);
for (auto type = digestAlgorithms().begin(); type != digestAlgorithms().end(); ++type) {
+ uint32_t runtimeVersionToUse = runtimeVersion;
+ if ((cdFlags & kSecCodeSignatureRuntime) && runtimeVersionMap.count(arch.architecture)) {
+ if (runtimeVersionMap[arch.architecture].count(*type)) {
+ runtimeVersionToUse = runtimeVersionMap[arch.architecture][*type];
+ }
+ }
arch.eachDigest(^(CodeDirectory::Builder& builder) {
populate(builder, arch, arch.ireqs,
arch.source->offset(), arch.source->signingExtent(),
mainBinary, rep->execSegBase(&(arch.architecture)), rep->execSegLimit(&(arch.architecture)),
- unsigned(digestAlgorithms().size()-1));
+ unsigned(digestAlgorithms().size()-1),
+ preEncryptHashMaps[arch.architecture], runtimeVersionToUse);
});
}
CodeDirectory *cd = builder.build();
cdSet.add(cd);
});
- CFRef<CFArrayRef> hashes = cdSet.hashBag();
- CFTemp<CFDictionaryRef> hashDict("{cdhashes=%O}", hashes.get());
- CFRef<CFDataRef> hashBag = makeCFData(hashDict.get());
- CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashBag);
+
+ CFRef<CFDictionaryRef> hashDict = cdSet.hashDict();
+ CFRef<CFArrayRef> hashList = cdSet.hashList();
+ CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashDict, hashList);
// complete the SuperBlob
cdSet.populate(&arch);
populate(builder, *writer, ireqs, rep->signingBase(), rep->signingLimit(),
false, // only machOs can currently be main binaries
rep->execSegBase(NULL), rep->execSegLimit(NULL),
- unsigned(digestAlgorithms().size()-1));
+ unsigned(digestAlgorithms().size()-1),
+ preEncryptHashMaps[preEncryptMainArch], // Only one map, the default.
+ (cdFlags & kSecCodeSignatureRuntime) ? state.mRuntimeVersionOverride : 0);
CodeDirectory *cd = builder.build();
if (!state.mDryRun)
if (!state.mDryRun)
cdSet.populate(writer);
- CFRef<CFArrayRef> hashes = cdSet.hashBag();
- CFTemp<CFDictionaryRef> hashDict("{cdhashes=%O}", hashes.get());
- CFRef<CFDataRef> hashBag = makeCFData(hashDict.get());
- CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashBag);
+ CFRef<CFDictionaryRef> hashDict = cdSet.hashDict();
+ CFRef<CFArrayRef> hashList = cdSet.hashList();
+ CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashDict, hashList);
writer->signature(signature);
// commit to storage
void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::Writer &writer,
InternalRequirements &ireqs, size_t offset, size_t length,
bool mainBinary, size_t execSegBase, size_t execSegLimit,
- unsigned alternateDigestCount)
+ unsigned alternateDigestCount,
+ PreEncryptHashMap const &preEncryptHashMap,
+ uint32_t runtimeVersion)
{
// fill the CodeDirectory
builder.executable(rep->mainExecutablePath(), pagesize, offset, length);
builder.teamID(teamID);
builder.platform(state.mPlatform);
builder.execSeg(execSegBase, execSegLimit, mainBinary ? kSecCodeExecSegMainBinary : 0);
+ builder.generatePreEncryptHashes(signingFlags() & kSecCSSignGeneratePEH);
+ builder.preservePreEncryptHashMap(preEncryptHashMap);
+ builder.runTimeVersion(runtimeVersion);
if (CFRef<CFDataRef> data = rep->component(cdInfoSlot))
builder.specialSlot(cdInfoSlot, data);
builder.specialSlot(cdEntitlementSlot, entitlements);
if (mainBinary) {
- builder.addExecSegFlags(entitlementsToExecSegFlags(entitlements));
+ CFRef<CFDataRef> entitlementDER;
+ uint64_t execSegFlags = 0;
+ cookEntitlements(entitlements, generateEntitlementDER,
+ &execSegFlags, &entitlementDER.aref());
+
+ if (generateEntitlementDER) {
+ writer.component(cdEntitlementDERSlot, entitlementDER);
+ builder.specialSlot(cdEntitlementDERSlot, entitlementDER);
+ }
+
+ builder.addExecSegFlags(execSegFlags);
}
}
if (CFRef<CFDataRef> repSpecific = rep->component(cdRepSpecificSlot))
//
// Generate the CMS signature for a (finished) CodeDirectory.
//
-CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd, CFDataRef hashBag)
+CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd,
+ CFDictionaryRef hashDict,
+ CFArrayRef hashList)
{
assert(state.mSigner);
CFRef<CFMutableDictionaryRef> defaultTSContext = NULL;
MacOSError::check(CMSEncoderSetSigningTime(cms, time));
}
- if (hashBag) {
+ if (hashDict != NULL) {
+ assert(hashList != NULL);
+
+ // V2 Hash Agility
+
+ MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrAppleCodesigningHashAgilityV2));
+ MacOSError::check(CMSEncoderSetAppleCodesigningHashAgilityV2(cms, hashDict));
+
+ // V1 Hash Agility
+
+ CFTemp<CFDictionaryRef> hashDict("{cdhashes=%O}", hashList);
+ CFRef<CFDataRef> hashAgilityV1Attribute = makeCFData(hashDict.get());
+
MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrAppleCodesigningHashAgility));
- MacOSError::check(CMSEncoderSetAppleCodesigningHashAgility(cms, hashBag));
+ MacOSError::check(CMSEncoderSetAppleCodesigningHashAgility(cms, hashAgilityV1Attribute));
}
MacOSError::check(CMSEncoderUpdateContent(cms, cd, cd->length()));
return CFBooleanGetValue(entValue);
}
-uint64_t SecCodeSigner::Signer::entitlementsToExecSegFlags(CFDataRef entitlements)
+void SecCodeSigner::Signer::cookEntitlements(CFDataRef entitlements, bool generateDER,
+ uint64_t *execSegFlags, CFDataRef *entitlementDER)
{
if (!entitlements) {
- return 0;
+ return; // nothing to do.
}
- const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlements));
-
- if (blob == NULL || !blob->validateBlob(CFDataGetLength(entitlements))) {
- return 0;
- }
+ EntitlementDERBlob *derBlob = NULL;
try {
+ const EntitlementBlob *blob = reinterpret_cast<const EntitlementBlob *>(CFDataGetBytePtr(entitlements));
+
+ if (blob == NULL || !blob->validateBlob(CFDataGetLength(entitlements))) {
+ MacOSError::throwMe(errSecCSInvalidEntitlements);
+ }
+
CFRef<CFDictionaryRef> entDict = blob->entitlements();
- uint64_t flags = 0;
+ if (generateDER) {
+ CFRef<CFErrorRef> error = NULL;
+ size_t const der_size = der_sizeof_plist(entDict, &error.aref());
- flags |= booleanEntitlement(entDict, CFSTR("get-task-allow")) ? kSecCodeExecSegAllowUnsigned : 0;
- flags |= booleanEntitlement(entDict, CFSTR("run-unsigned-code")) ? kSecCodeExecSegAllowUnsigned : 0;
- flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.cs.debugger")) ? kSecCodeExecSegDebugger : 0;
- flags |= booleanEntitlement(entDict, CFSTR("dynamic-codesigning")) ? kSecCodeExecSegJit : 0;
- flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.skip-library-validation")) ? kSecCodeExecSegSkipLibraryVal : 0;
- flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-load-cdhash")) ? kSecCodeExecSegCanLoadCdHash : 0;
- flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-execute-cdhash")) ? kSecCodeExecSegCanExecCdHash : 0;
+ if (der_size == 0) {
+ secerror("Getting DER size for entitlement plist failed: %@", error.get());
+ MacOSError::throwMe(errSecCSInvalidEntitlements);
+ }
+
+ derBlob = EntitlementDERBlob::alloc(der_size);
+
+ if (derBlob == NULL) {
+ secerror("Cannot allocate buffer for DER entitlements of size %zu", der_size);
+ MacOSError::throwMe(errSecCSInvalidEntitlements);
+ }
+ uint8_t * const der_end = derBlob->der() + der_size;
+ uint8_t * const der_start = der_encode_plist(entDict, &error.aref(), derBlob->der(), der_end);
- return flags;
+ if (der_start != derBlob->der()) {
+ secerror("Entitlement DER start mismatch (%zu)", (size_t)(der_start - derBlob->der()));
+ free(derBlob);
+ MacOSError::throwMe(errSecCSInvalidEntitlements);
+ }
+
+ *entitlementDER = makeCFData(derBlob, derBlob->length());
+ free(derBlob);
+ derBlob = NULL;
+ }
+
+ if (execSegFlags != NULL) {
+ uint64_t flags = 0;
+
+ flags |= booleanEntitlement(entDict, CFSTR("get-task-allow")) ? kSecCodeExecSegAllowUnsigned : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("run-unsigned-code")) ? kSecCodeExecSegAllowUnsigned : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.cs.debugger")) ? kSecCodeExecSegDebugger : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("dynamic-codesigning")) ? kSecCodeExecSegJit : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.skip-library-validation")) ? kSecCodeExecSegSkipLibraryVal : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-load-cdhash")) ? kSecCodeExecSegCanLoadCdHash : 0;
+ flags |= booleanEntitlement(entDict, CFSTR("com.apple.private.amfi.can-execute-cdhash")) ? kSecCodeExecSegCanExecCdHash : 0;
+
+ *execSegFlags = flags;
+ }
} catch (const CommonError &err) {
- // Not fatal.
+ free(derBlob);
+ // Not fatal if we're not asked to generate DER entitlements.
+
secwarning("failed to parse entitlements: %s", err.what());
- return 0;
+ if (generateDER) {
+ throw;
+ }
}
}
void signMachO(Universal *fat, const Requirement::Context &context); // sign a Mach-O binary
void signArchitectureAgnostic(const Requirement::Context &context); // sign anything else
+ // HashAlgorithm -> PreEncrypt hashes
+ typedef std::map<CodeDirectory::HashAlgorithm, CFCopyRef<CFDataRef> >
+ PreEncryptHashMap;
+ // Architecture -> PreEncryptHashMap
+ typedef std::map<Architecture, PreEncryptHashMap >
+ PreEncryptHashMaps;
+ // HashAlgorithm -> Hardened Runtime Version
+ typedef std::map<CodeDirectory::HashAlgorithm, uint32_t>
+ RuntimeVersionMap;
+ // Architecture -> RuntimeVersionMap
+ typedef std::map<Architecture, RuntimeVersionMap>
+ RuntimeVersionMaps;
+
void populate(DiskRep::Writer &writer); // global
void populate(CodeDirectory::Builder &builder, DiskRep::Writer &writer,
InternalRequirements &ireqs,
size_t offset, size_t length,
bool mainBinary, size_t execSegBase, size_t execSegLimit,
- unsigned alternateDigestCount); // per-architecture
- CFDataRef signCodeDirectory(const CodeDirectory *cd, CFDataRef hashBag);
+ unsigned alternateDigestCount,
+ const PreEncryptHashMap& preEncryptHashMap,
+ uint32_t runtimeVersion); // per-architecture
+ CFDataRef signCodeDirectory(const CodeDirectory *cd,
+ CFDictionaryRef hashDict, CFArrayRef hashList);
uint32_t cdTextFlags(std::string text); // convert text CodeDirectory flags
std::string uniqueName() const; // derive unique string from rep
protected:
+
std::string sdkPath(const std::string &path) const;
bool isAdhoc() const;
SecCSFlags signingFlags() const;
private:
+ void addPreEncryptHashes(PreEncryptHashMap &map, SecStaticCode const *code);
+ void addRuntimeVersions(RuntimeVersionMap &map, SecStaticCode const *code);
+
void considerTeamID(const PreSigningContext& context);
std::vector<Endian<uint32_t> > topSlots(CodeDirectory::Builder &builder) const;
static bool booleanEntitlement(CFDictionaryRef entDict, CFStringRef key);
- static uint64_t entitlementsToExecSegFlags(CFDataRef entitlements);
+ static void cookEntitlements(CFDataRef entitlements, bool generateDER,
+ uint64_t *execSegFlags, CFDataRef *entitlementsDER);
protected:
void buildResources(std::string root, std::string relBase, CFDictionaryRef rules);
std::string identifier; // signing identifier
std::string teamID; // team identifier
CFRef<CFDataRef> entitlements; // entitlements
+ Architecture preEncryptMainArch; // pre-encrypt main architecture
+ PreEncryptHashMaps preEncryptHashMaps; // pre-encrypt hashes to keep
+ Architecture runtimeVersionMainArch; // runtime version main architecture
+ RuntimeVersionMaps runtimeVersionMap; // runtime versions to keep
uint32_t cdFlags; // CodeDirectory flags
const Requirements *requirements; // internal requirements ready-to-use
size_t pagesize; // size of main executable pages
CFAbsoluteTime signingTime; // signing time for CMS signature (0 => now)
bool emitSigningTime; // emit signing time as a signed CMS attribute
bool strict; // strict validation
+ bool generateEntitlementDER; // generate entitlement DER
private:
Mutex resourceLock;
//
// signerutils - utilities for signature generation
//
+#include "csutilities.h"
+#include "drmaker.h"
+#include "resources.h"
#include "signerutils.h"
#include "signer.h"
-#include "SecCodeSigner.h"
+
+#include <Security/SecCmsBase.h>
#include <Security/SecIdentity.h>
#include <Security/CMSEncoder.h>
-#include "resources.h"
-#include "csutilities.h"
-#include "drmaker.h"
+
+#include "SecCodeSigner.h"
+
#include <security_utilities/unix++.h>
#include <security_utilities/logging.h>
#include <security_utilities/unixchild.h>
+
#include <vector>
// for helper validation
return mPrimary;
}
-
-CFArrayRef CodeDirectorySet::hashBag() const
+CFArrayRef CodeDirectorySet::hashList() const
{
CFRef<CFMutableArrayRef> hashList = makeCFMutableArray(0);
for (auto it = begin(); it != end(); ++it) {
- CFRef<CFDataRef> cdhash = it->second->cdhash();
+ CFRef<CFDataRef> cdhash = it->second->cdhash(true);
CFArrayAppendValue(hashList, cdhash);
}
return hashList.yield();
}
+CFDictionaryRef CodeDirectorySet::hashDict() const
+{
+ CFRef<CFMutableDictionaryRef> hashDict = makeCFMutableDictionary();
+
+ for (auto it = begin(); it != end(); ++it) {
+ SECOidTag tag = CodeDirectorySet::SECOidTagForAlgorithm(it->first);
+
+ if (tag == SEC_OID_UNKNOWN) {
+ MacOSError::throwMe(errSecCSUnsupportedDigestAlgorithm);
+ }
+
+ CFRef<CFNumberRef> hashType = makeCFNumber(int(tag));
+ CFRef<CFDataRef> fullCdhash = it->second->cdhash(false); // Full-length cdhash!
+ CFDictionarySetValue(hashDict, hashType, fullCdhash);
+ }
+
+ return hashDict.yield();
+}
+
+SECOidTag CodeDirectorySet::SECOidTagForAlgorithm(CodeDirectory::HashAlgorithm algorithm) {
+ SECOidTag tag;
+
+ switch (algorithm) {
+ case kSecCodeSignatureHashSHA1:
+ tag = SEC_OID_SHA1;
+ break;
+ case kSecCodeSignatureHashSHA256:
+ case kSecCodeSignatureHashSHA256Truncated: // truncated *page* hashes, not cdhash
+ tag = SEC_OID_SHA256;
+ break;
+ case kSecCodeSignatureHashSHA384:
+ tag = SEC_OID_SHA384;
+ break;
+ default:
+ tag = SEC_OID_UNKNOWN;
+ }
+
+ return tag;
+}
+
+
} // end namespace CodeSigning
} // end namespace Security
#include "CodeSigner.h"
#include "sigblob.h"
#include "cdbuilder.h"
+
+#include <Security/SecCmsBase.h>
+
#include <security_utilities/utilities.h>
#include <security_utilities/blob.h>
#include <security_utilities/unix++.h>
void populate(DiskRep::Writer* writer) const;
const CodeDirectory* primary() const;
- CFArrayRef hashBag() const;
-
+
+ // Note that the order of the hashList is relevant.
+ // (Which is also why there are separate methods, CFDictionary is not ordered.)
+ CFArrayRef hashList() const;
+ CFDictionaryRef hashDict() const;
+
+ static SECOidTag SECOidTagForAlgorithm(CodeDirectory::HashAlgorithm algorithm);
+
private:
mutable const CodeDirectory* mPrimary;
};
return NULL;
}
+const build_version_command *MachOBase::findBuildVersion() const
+{
+ for (const load_command *command = loadCommands(); command; command = nextCommand(command)) {
+ if (flip(command->cmd) == LC_BUILD_VERSION) {
+ if(flip(command->cmdsize) < sizeof(build_version_command)) {
+ UnixError::throwMe(ENOEXEC);
+ }
+
+ return reinterpret_cast<const build_version_command *>(command);
+ }
+ }
+ return NULL;
+}
+
+bool MachOBase::version(uint32_t *platform, uint32_t *minVersion, uint32_t *sdkVersion) const
+{
+ const build_version_command *bc = findBuildVersion();
+
+ if (bc != NULL) {
+ if (platform != NULL) { *platform = flip(bc->platform); }
+ if (minVersion != NULL) { *minVersion = flip(bc->minos); }
+ if (sdkVersion != NULL) { *sdkVersion = flip(bc->sdk); }
+ return true;
+ }
+
+ const version_min_command *vc = findMinVersion();
+
+ if (vc != NULL) {
+ uint32_t pf;
+ switch (flip(vc->cmd)) {
+ case LC_VERSION_MIN_MACOSX:
+ pf = PLATFORM_MACOS;
+ break;
+ case LC_VERSION_MIN_IPHONEOS:
+ pf = PLATFORM_IOS;
+ break;
+ case LC_VERSION_MIN_WATCHOS:
+ pf = PLATFORM_WATCHOS;
+ break;
+ case LC_VERSION_MIN_TVOS:
+ pf = PLATFORM_TVOS;
+ break;
+ default:
+ // Old style load command, but we don't know what platform to map to.
+ pf = 0;
+ }
+
+ if (platform != NULL) { *platform = pf; }
+ if (minVersion != NULL) { *minVersion = flip(vc->version); }
+ if (sdkVersion != NULL) { *sdkVersion = flip(vc->sdk); }
+ return true;
+ }
+
+ return false;
+}
//
// Return the signing-limit length for this Mach-O binary image.
const linkedit_data_command *findCodeSignature() const;
const linkedit_data_command *findLibraryDependencies() const;
- const version_min_command *findMinVersion() const;
size_t signingOffset() const; // starting offset of CS section, or 0 if none
size_t signingLength() const; // length of CS section, or 0 if none
+
+ bool version(uint32_t *platform, uint32_t *minVersion, uint32_t *sdkVersion) const;
+ uint32_t platform() const { uint32_t ret; return version(&ret, NULL, NULL) ? ret : 0; };
+ uint32_t minVersion() const { uint32_t ret; return version(NULL, &ret, NULL) ? ret : 0; };
+ uint32_t sdkVersion() const { uint32_t ret; return version(NULL, NULL, &ret) ? ret : 0; };
protected:
void initHeader(const mach_header *address);
size_t headerSize() const; // size of header
size_t commandSize() const; // size of commands area
+ const version_min_command *findMinVersion() const;
+ const build_version_command *findBuildVersion() const;
+
private:
const mach_header *mHeader; // Mach-O header
const load_command *mCommands; // load commands
#include <Security/SecTrust.h>
#include <Security/SecTrustSettings.h>
#include <Security/SecTrustSettingsPriv.h>
+#include <Security/SecTrustPriv.h>
#include <utilities/SecCFRelease.h>
#include <stdlib.h>
#include <unistd.h>
}
+#define kNumberPolicyNamePinnningConstraintsTests (1 + 1 + 5)
+static void test_policy_name_pinning_constraints(void) {
+ /* allow all but */
+ NSArray *allowAllBut = @[
+ @{(__bridge NSString*)kSecTrustSettingsPolicy: (__bridge id)sslPolicy ,
+ (__bridge NSString*)kSecTrustSettingsResult: @(kSecTrustSettingsResultUnspecified)},
+ @{(__bridge NSString*)kSecTrustSettingsResult: @(kSecTrustSettingsResultTrustRoot) }
+ ];
+ setTS(cert0, (__bridge CFArrayRef)allowAllBut);
+
+ SecTrustRef trust = NULL;
+ SecTrustResultType trust_result;
+ ok_status(SecTrustCreateWithCertificates(sslChain, sslPolicy, &trust), "create trust with ssl policy");
+ ok_status(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)verify_date), "set trust verify date");
+ ok_status(SecTrustSetPinningPolicyName(trust, CFSTR("not-a-db-policy-name")), "set policy name");
+ ok_status(SecTrustEvaluate(trust, &trust_result), "evaluate trust");
+ is(trust_result, kSecTrustResultRecoverableTrustFailure, "check trust result for sslServer policy with policy name");
+ CFReleaseSafe(trust);
+
+ removeTS(cert0);
+}
+
int si_28_sectrustsettings(int argc, char *const *argv)
{
- plan_tests(kNumberNoConstraintsTests +
- kNumberPolicyConstraintsTests +
- kNumberPolicyStringConstraintsTests +
- kNumberApplicationsConstraintsTests +
- kNumberKeyUsageConstraintsTests +
- kNumberAllowedErrorsTests +
- kNumberMultipleConstraintsTests);
+ plan_tests(kNumberNoConstraintsTests
+ + kNumberPolicyConstraintsTests
+ + kNumberPolicyStringConstraintsTests
+ + kNumberApplicationsConstraintsTests
+ + kNumberKeyUsageConstraintsTests
+ + kNumberAllowedErrorsTests
+ + kNumberMultipleConstraintsTests
+ + kNumberPolicyNamePinnningConstraintsTests
+ );
#if !TARGET_OS_IPHONE
if (getuid() != 0) {
test_key_usage_constraints();
test_allowed_errors();
test_multiple_constraints();
+ test_policy_name_pinning_constraints();
cleanup_globals();
}
_kSecCodeInfoEntitlements
_kSecCodeInfoEntitlementsDict
_kSecCodeInfoUnique
+_kSecCodeInfoRuntimeVersion
_kSecCFErrorResourceAdded
_kSecCFErrorResourceAltered
if (policyIX) { *policyIX = ix; }
return true;
}
+ /* <rdar://40617139> sslServer trust settings need to apply to evals using policyName pinning
+ * but make sure we don't use this for SSL Client trust settings or policies. */
+ if (CFEqual(searchOid, policyOid) &&
+ CFEqual(searchName, kSecPolicyNameSSLServer) && !CFEqual(policyName, kSecPolicyNameSSLClient)) {
+ if (policyIX) { *policyIX = ix; }
+ return true;
+ }
}
/* Next best is just OID. */
if (!searchName && searchOid && policyOid) {
_kSecCodeSignerRequireTimestamp
_kSecCodeSignerTeamIdentifier
_kSecCodeSignerPlatformIdentifier
+_kSecCodeSignerRuntimeVersion
_kSecCodeSignerTimestampServer
_kSecCodeSignerTimestampAuthentication
_kSecCodeSignerTimestampOmitCertificates
_kSecCodeInfoTrust
_kSecCodeInfoUnique
_kSecCodeInfoCdHashes
+_kSecCodeInfoRuntimeVersion
_kSecCodeInfoCodeDirectory
_kSecCodeInfoCodeOffset
_kSecCodeInfoDiskRepInfo
-_kSecCodeInfoDiskRepOSPlatform
-_kSecCodeInfoDiskRepOSVersionMin
-_kSecCodeInfoDiskRepOSSDKVersion
_kSecCodeInfoDiskRepNoLibraryValidation
+_kSecCodeInfoDiskRepVersionPlatform
+_kSecCodeInfoDiskRepVersionMin
+_kSecCodeInfoDiskRepVersionSDK
_kSecCodeInfoResourceDirectory
_kSecGuestAttributeCanonical
_kSecGuestAttributeDynamicCode
_kSecCodeInfoDigestAlgorithms
_kSecCodeInfoDiskRepInfo
_kSecCodeInfoDiskRepNoLibraryValidation
-_kSecCodeInfoDiskRepOSPlatform
-_kSecCodeInfoDiskRepOSSDKVersion
-_kSecCodeInfoDiskRepOSVersionMin
+_kSecCodeInfoDiskRepVersionPlatform
+_kSecCodeInfoDiskRepVersionMin
+_kSecCodeInfoDiskRepVersionSDK
_kSecCodeInfoFlags
_kSecCodeInfoFormat
_kSecCodeInfoImplicitDesignatedRequirement
//
// The internal Guest object
//
+CodeSigningHost::Guest::Guest() : mAttrData(NULL), mAttrDataLength(0)
+{
+}
+
CodeSigningHost::Guest::~Guest()
-{ }
+{
+ if (mAttrData != NULL) {
+ vm_size_t rounded_size = mach_vm_round_page(mAttrDataLength);
+ vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(mAttrData), rounded_size);
+ }
+}
void CodeSigningHost::Guest::setAttributes(const CssmData &attrData)
{
}
}
-CFDataRef CodeSigningHost::Guest::attrData() const
-{
- if (!mAttrData)
- mAttrData = makeCFData(this->attributes.get());
- return mAttrData;
+void CodeSigningHost::Guest::createAttrData() const {
+ if (!mAttrData) {
+ CFRef<CFDataRef> data = makeCFData(this->attributes.get());
+
+ /* cshosting_server_identifyGuest() will point to the attrData in a MIG
+ * OOL buffer. To prevent leaking surrounding memory, the attr data gets its
+ * own (zeroed) pages. */
+ vm_address_t addr = 0;
+ vm_size_t rounded_size = mach_vm_round_page(CFDataGetLength(data.get()));
+ kern_return_t ret = vm_allocate(mach_task_self(), &addr, rounded_size, VM_FLAGS_ANYWHERE);
+
+ if (ret == KERN_SUCCESS) {
+ mAttrData = reinterpret_cast<uint8_t *>(addr);
+ mAttrDataLength = CFDataGetLength(data.get());
+ memcpy(mAttrData, CFDataGetBytePtr(data.get()), mAttrDataLength);
+ // pages returned by vm_allocate are zeroed, no need to fill out padding.
+ } else {
+ secerror("csproxy attrData vm_allocate failed: %d", ret);
+ }
+ }
}
-
void CodeSigningHost::Guest::setHash(const CssmData &given, bool generate)
{
if (given.length()) // explicitly given
*hashLength = 0; // unavailable
// visible attributes. This proxy returns all attributes set by the host
- CFDataRef attrData = guest->attrData(); // (the guest will cache this until it dies)
- *attributes = (void *)CFDataGetBytePtr(attrData); // MIG botch (it doesn't need a writable pointer)
- *attributesLength = int_cast<CFIndex, mach_msg_type_number_t>(CFDataGetLength(attrData));
+ *attributes = (void*)guest->attrData(); // MIG botch (it doesn't need a writable pointer)
+ *attributesLength = int_cast<CFIndex, mach_msg_type_number_t>(guest->attrDataLength());
END_IPC
}
struct Guest : public RefCount, public HandleObject {
public:
+ Guest();
~Guest();
std::vector<SecGuestRef> guestPath; // guest chain to this guest
uint32_t status; // dynamic status
operator bool() const { return attributes; } // exists
SecGuestRef guestRef() const { return int_cast<long, SecGuestRef>(handle()); }
void setAttributes(const CssmData &attrData);
- CFDataRef attrData() const;
+ uint8_t const *attrData() const { createAttrData(); return mAttrData; };
+ CFIndex attrDataLength() const { createAttrData(); return mAttrDataLength; };
void setHash(const CssmData &given, bool generate);
bool isGuestOf(Guest *host, GuestCheck check) const;
IFDUMP(void dump() const);
private:
- mutable CFRef<CFDataRef> mAttrData; // XML form of attributes (must live until guest destruction)
+ void createAttrData() const;
+
+ mutable uint8_t *mAttrData; // XML form of attributes (vm_allocate'd, must live until guest destruction)
+ mutable CFIndex mAttrDataLength;
};
void registerCodeSigning(mach_port_t hostingPort, SecCSFlags flags);