//
// signer - Signing operation supervisor and controller
//
+#include "bundlediskrep.h"
+#include "der_plist.h"
#include "signer.h"
#include "resources.h"
#include "signerutils.h"
PreSigningContext context(*this);
+ considerTeamID(context);
+
+ if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) {
+ signMachO(fat, context);
+ } else {
+ signArchitectureAgnostic(context);
+ }
+}
+
+
+void SecCodeSigner::Signer::considerTeamID(const PreSigningContext& context)
+{
/* If an explicit teamID was passed in it must be
the same as what came from the cert */
std::string teamIDFromCert = state.getTeamIDFromSigner(context.certs);
-
+
if (state.mPreserveMetadata & kSecCodeSignerPreserveTeamIdentifier) {
/* If preserving the team identifier, teamID is set previously when the
code object is still available */
MacOSError::throwMe(errSecCSInvalidFlags);
}
}
-
- if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) {
- signMachO(fat, context);
- } else {
- signArchitectureAgnostic(context);
- }
}
-
+
//
// Remove any existing code signature from code
MacOSError::throwMe(errSecCSNotSupported);
rep = code->diskRep();
+
+ if (state.mPreserveAFSC)
+ rep->writer()->setPreserveAFSC(state.mPreserveAFSC);
+
if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) {
// architecture-sensitive removal
- MachOEditor editor(rep->writer(), *fat, kSecCodeSignatureNoHash, rep->mainExecutablePath());
+ MachOEditor editor(rep->writer(), *fat, digestAlgorithms(), rep->mainExecutablePath());
editor.allocate(); // create copy
editor.commit(); // commit change
} else {
{
// make sure the rep passes strict validation
if (strict)
- rep->strictValidate(NULL, MacOSErrorSet());
+ rep->strictValidate(NULL, MacOSErrorSet(), flags | (kSecCSQuickCheck|kSecCSRestrictSidebandData));
// initialize progress/cancellation state
code->prepareProgress(0); // totally fake workload - we don't know how many files we'll encounter
if (identifier.empty() && (inherit & kSecCodeSignerPreserveIdentifier))
identifier = code->identifier();
if (identifier.empty()) {
- identifier = rep->recommendedIdentifier(state);
+ identifier = rep->recommendedIdentifier(*this);
if (identifier.find('.') == string::npos)
identifier = state.mIdentifierPrefix + identifier;
- if (identifier.find('.') == string::npos && state.isAdhoc())
+ if (identifier.find('.') == string::npos && isAdhoc())
identifier = identifier + "-" + uniqueName();
- secdebug("signer", "using default identifier=%s", identifier.c_str());
+ secinfo("signer", "using default identifier=%s", identifier.c_str());
} else
- secdebug("signer", "using explicit identifier=%s", identifier.c_str());
+ secinfo("signer", "using explicit identifier=%s", identifier.c_str());
teamID = state.mTeamID;
if (teamID.empty() && (inherit & kSecCodeSignerPreserveTeamIdentifier)) {
teamID = c_id;
}
+ // Digest algorithms: explicit or preserved. Subject to diskRep defaults or final default later.
+ hashAlgorithms = state.mDigestAlgorithms;
+ if (hashAlgorithms.empty() && (inherit & kSecCodeSignerPreserveDigestAlgorithm))
+ hashAlgorithms = code->hashAlgorithms();
+
entitlements = state.mEntitlementData;
if (!entitlements && (inherit & kSecCodeSignerPreserveEntitlements))
entitlements = code->component(cdEntitlementSlot);
+
+ generateEntitlementDER = signingFlags() & kSecCSSignGenerateEntitlementDER;
// work out the CodeDirectory flags word
bool haveCdFlags = false;
if (!haveCdFlags && state.mCdFlagsGiven) {
cdFlags = state.mCdFlags;
- secdebug("signer", "using explicit cdFlags=0x%x", cdFlags);
+ secinfo("signer", "using explicit cdFlags=0x%x", cdFlags);
haveCdFlags = true;
}
if (!haveCdFlags) {
if (CFTypeRef csflags = CFDictionaryGetValue(infoDict, CFSTR("CSFlags"))) {
if (CFGetTypeID(csflags) == CFNumberGetTypeID()) {
cdFlags = cfNumber<uint32_t>(CFNumberRef(csflags));
- secdebug("signer", "using numeric cdFlags=0x%x from Info.plist", cdFlags);
+ secinfo("signer", "using numeric cdFlags=0x%x from Info.plist", cdFlags);
} else if (CFGetTypeID(csflags) == CFStringGetTypeID()) {
cdFlags = cdTextFlags(cfString(CFStringRef(csflags)));
- secdebug("signer", "using text cdFlags=0x%x from Info.plist", cdFlags);
+ secinfo("signer", "using text cdFlags=0x%x from Info.plist", cdFlags);
} else
MacOSError::throwMe(errSecCSBadDictionaryFormat);
haveCdFlags = true;
}
if (!haveCdFlags && (inherit & kSecCodeSignerPreserveFlags)) {
cdFlags = code->codeDirectory(false)->flags & ~kSecCodeSignatureAdhoc;
- secdebug("signer", "using inherited cdFlags=0x%x", cdFlags);
+ secinfo("signer", "using inherited cdFlags=0x%x", cdFlags);
haveCdFlags = true;
}
if (!haveCdFlags)
cdFlags = 0;
- if (state.mSigner == SecIdentityRef(kCFNull)) // ad-hoc signing requested...
+ if ((state.mSigner == SecIdentityRef(kCFNull)) &&
+ !state.mOmitAdhocFlag) // ad-hoc signing requested...
cdFlags |= kSecCodeSignatureAdhoc; // ... so note that
// prepare the internal requirements input
// prepare the resource directory, if any
string rpath = rep->resourcesRootPath();
+ string rrpath;
+ CFCopyRef<CFDictionaryRef> resourceRules;
if (!rpath.empty()) {
// explicitly given resource rules always win
- CFCopyRef<CFDictionaryRef> resourceRules = state.mResourceRules;
+ resourceRules = state.mResourceRules;
// inherited rules come next (overriding embedded ones!)
if (!resourceRules && (inherit & kSecCodeSignerPreserveResourceRules))
// finally, ask the DiskRep for its default
if (!resourceRules)
- resourceRules.take(rep->defaultResourceRules(state));
+ resourceRules.take(rep->defaultResourceRules(*this));
// resource root can optionally be the canonical bundle path,
// but sealed resource paths are always relative to rpath
- string root = rpath;
- if (state.signingFlags() & kSecCSSignBundleRoot)
- root = cfStringRelease(rep->copyCanonicalPath());
-
- // build the resource directory
- buildResources(root, rpath, resourceRules);
+ rrpath = rpath;
+ if (signingFlags() & kSecCSSignBundleRoot)
+ rrpath = cfStringRelease(rep->copyCanonicalPath());
}
// screen and set the signing time
- CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
if (state.mSigningTime == CFDateRef(kCFNull)) {
- signingTime = 0; // no time at all
+ emitSigningTime = false; // no time at all
} else if (!state.mSigningTime) {
- signingTime = now; // default
+ emitSigningTime = true;
+ signingTime = 0; // wall clock, established later
} else {
CFAbsoluteTime time = CFDateGetAbsoluteTime(state.mSigningTime);
- if (time > now) // not allowed to post-date a signature
+ if (time > CFAbsoluteTimeGetCurrent()) // not allowed to post-date a signature
MacOSError::throwMe(errSecCSBadDictionaryFormat);
+ emitSigningTime = true;
signingTime = time;
}
- pagesize = state.mPageSize ? cfNumber<size_t>(state.mPageSize) : rep->pageSize(state);
-
- // Timestamping setup
- CFRef<SecIdentityRef> mTSAuth; // identity for client-side authentication to the Timestamp server
+ pagesize = state.mPageSize ? cfNumber<size_t>(state.mPageSize) : rep->pageSize(*this);
+
+ // Allow the DiskRep to modify the signing parameters. This sees explicit and inherited values but not defaults.
+ rep->prepareForSigning(*this);
+
+ // apply some defaults after diskRep intervention
+ if (hashAlgorithms.empty()) { // default to SHA256 + SHA-1
+ hashAlgorithms.insert(kSecCodeSignatureHashSHA1);
+ hashAlgorithms.insert(kSecCodeSignatureHashSHA256);
+ }
+
+ // build the resource directory (once and for all, using the digests determined above)
+ 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.
{
typedef ResourceBuilder::Rule Rule;
- secdebug("codesign", "start building resource directory");
+ secinfo("codesign", "start building resource directory");
__block CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary();
CFDictionaryRef rules = cfget<CFDictionaryRef>(rulesDict, "rules");
}
CFDictionaryRef files2 = NULL;
- if (!(state.signingFlags() & kSecCSSignV1)) {
+ if (!(signingFlags() & kSecCSSignV1)) {
CFCopyRef<CFDictionaryRef> rules2 = cfget<CFDictionaryRef>(rulesDict, "rules2");
if (!rules2) {
- // Clone V1 rules and add default nesting rules at weight 0 (overridden by anything in rules).
+ // Clone V1 rules and add default nesting rules at weight 0 (overridden by anything in rules,
+ // because the default weight, according to ResourceBuilder::addRule(), is 1).
// V1 rules typically do not cover these places so we'll prevail, but if they do, we defer to them.
rules2 = cfmake<CFDictionaryRef>("{+%O"
"'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=0}" // exclude dynamic repositories
// build the modern (V2) resource seal
__block CFRef<CFMutableDictionaryRef> files = makeCFMutableDictionary();
CFMutableDictionaryRef filesRef = files.get(); // (into block)
- ResourceBuilder resourceBuilder(root, relBase, rules2, digestAlgorithm(), strict, MacOSErrorSet());
+ ResourceBuilder resourceBuilder(root, relBase, rules2, strict, MacOSErrorSet());
ResourceBuilder &resources = resourceBuilder; // (into block)
rep->adjustResources(resources);
target[len] = '\0';
seal.take(cfmake<CFMutableDictionaryRef>("{symlink=%s}", target));
} else {
- seal.take(cfmake<CFMutableDictionaryRef>("{hash=%O}",
- CFRef<CFDataRef>(resources.hashFile(accpath.c_str())).get()));
+ seal.take(resources.hashFile(accpath.c_str(), digestAlgorithms(), signingFlags() & kSecCSSignStrictPreflight));
}
if (ruleFlags & ResourceBuilder::optional)
CFDictionaryAddValue(seal, CFSTR("optional"), kCFBooleanTrue);
}
CFDictionaryAddValue(result, CFSTR("rules"), rules); // preserve V1 rules in any case
- if (!(state.signingFlags() & kSecCSSignNoV1)) {
+ if (!(signingFlags() & kSecCSSignNoV1)) {
// build the legacy (V1) resource seal
__block CFRef<CFMutableDictionaryRef> files = makeCFMutableDictionary();
- ResourceBuilder resourceBuilder(root, relBase, rules, digestAlgorithm(), strict, MacOSErrorSet());
+ ResourceBuilder resourceBuilder(root, relBase, rules, strict, MacOSErrorSet());
ResourceBuilder &resources = resourceBuilder;
rep->adjustResources(resources); // DiskRep-specific adjustments
resources.scan(^(FTSENT *ent, uint32_t ruleFlags, std::string relpath, Rule *rule) {
hash = CFDataRef(CFDictionaryGetValue(CFDictionaryRef(seal), CFSTR("hash")));
}
if (!hash)
- hash.take(resources.hashFile(ent->fts_accpath));
+ hash.take(resources.hashFile(ent->fts_accpath, kSecCodeSignatureHashSHA1));
if (ruleFlags == 0) { // default case - plain hash
cfadd(files, "{%s=%O}", relpath.c_str(), hash.get());
- secdebug("csresource", "%s added simple (rule %p)", relpath.c_str(), rule);
+ secinfo("csresource", "%s added simple (rule %p)", relpath.c_str(), rule);
} else { // more complicated - use a sub-dictionary
cfadd(files, "{%s={hash=%O,optional=%B}}",
relpath.c_str(), hash.get(), ruleFlags & ResourceBuilder::optional);
- secdebug("csresource", "%s added complex (rule %p)", relpath.c_str(), rule);
+ secinfo("csresource", "%s added complex (rule %p)", relpath.c_str(), rule);
}
}
});
// sign nested code and collect nesting information
try {
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(path));
- if (state.signingFlags() & kSecCSSignNestedCode)
- this->state.sign(code, state.signingFlags());
+ if (signingFlags() & kSecCSSignNestedCode)
+ this->state.sign(code, signingFlags());
std::string dr = Dumper::dump(code->designatedRequirement());
- return cfmake<CFMutableDictionaryRef>("{requirement=%s,cdhash=%O}",
- Dumper::dump(code->designatedRequirement()).c_str(),
- code->cdHash());
+ if (CFDataRef hash = code->cdHash())
+ return cfmake<CFMutableDictionaryRef>("{requirement=%s,cdhash=%O}",
+ Dumper::dump(code->designatedRequirement()).c_str(),
+ hash);
+ MacOSError::throwMe(errSecCSUnsigned);
} catch (const CommonError &err) {
CSError::throwMe(err.osStatus(), kSecCFErrorPath, CFTempURL(relpath, false, this->code->resourceBase()));
}
void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context &context)
{
// Mach-O executable at the core - perform multi-architecture signing
+ RefPointer<DiskRep::Writer> writer = rep->writer();
+
+ if (state.mPreserveAFSC)
+ writer->setPreserveAFSC(state.mPreserveAFSC);
+
auto_ptr<ArchEditor> editor(state.mDetached
? static_cast<ArchEditor *>(new BlobEditor(*fat, *this))
- : new MachOEditor(rep->writer(), *fat, this->digestAlgorithm(), rep->mainExecutablePath()));
+ : new MachOEditor(writer, *fat, this->digestAlgorithms(), rep->mainExecutablePath()));
assert(editor->count() > 0);
if (!editor->attribute(writerNoGlobal)) // can store architecture-common components
populate(*editor);
MacOSError::throwMe(errSecCSBadLVArch);
}
}
-
- arch.ireqs(requirements, rep->defaultRequirements(&arch.architecture, state), context);
+
+ 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);
- populate(arch.cdbuilder, arch, arch.ireqs,
- arch.source->offset(), arch.source->signingExtent());
+ 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),
+ preEncryptHashMaps[arch.architecture], runtimeVersionToUse);
+ });
+ }
// add identification blob (made from this architecture) only if we're making a detached signature
if (state.mDetached) {
}
// prepare SuperBlob size estimate
- size_t cdSize = arch.cdbuilder.size(CodeDirectory::currentVersion);
- arch.blobSize = arch.size(cdSize, state.mCMSSize, 0);
+ __block std::vector<size_t> sizes;
+ arch.eachDigest(^(CodeDirectory::Builder& builder){
+ sizes.push_back(builder.size(CodeDirectory::currentVersion));
+ });
+ arch.blobSize = arch.size(sizes, state.mCMSSize, 0);
}
editor->allocate();
MachOEditor::Arch &arch = *it->second;
editor->reset(arch);
- // finish CodeDirectory (off new binary) and sign it
- CodeDirectory *cd = arch.cdbuilder.build();
- CFRef<CFDataRef> signature = signCodeDirectory(cd);
+ // finish CodeDirectories (off new binary) and sign it
+ __block CodeDirectorySet cdSet;
+ arch.eachDigest(^(CodeDirectory::Builder &builder) {
+ CodeDirectory *cd = builder.build();
+ cdSet.add(cd);
+ });
+
+ CFRef<CFDictionaryRef> hashDict = cdSet.hashDict();
+ CFRef<CFArrayRef> hashList = cdSet.hashList();
+ CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashDict, hashList);
// complete the SuperBlob
- arch.add(cdCodeDirectorySlot, cd); // takes ownership
+ cdSet.populate(&arch);
arch.add(cdSignatureSlot, BlobWrapper::alloc(
CFDataGetBytePtr(signature), CFDataGetLength(signature)));
if (!state.mDryRun) {
}
// done: write edit copy back over the original
- if (!state.mDryRun)
+ if (!state.mDryRun) {
editor->commit();
+ }
}
// non-Mach-O executable - single-instance signing
RefPointer<DiskRep::Writer> writer = state.mDetached ?
(new DetachedBlobWriter(*this)) : rep->writer();
- CodeDirectory::Builder builder(state.mDigestAlgorithm);
- InternalRequirements ireqs;
- ireqs(requirements, rep->defaultRequirements(NULL, state), context);
- populate(*writer);
- populate(builder, *writer, ireqs, rep->signingBase(), rep->signingLimit());
+
+ if(state.mPreserveAFSC)
+ writer->setPreserveAFSC(state.mPreserveAFSC);
+
+ CodeDirectorySet cdSet;
+
+ for (auto type = digestAlgorithms().begin(); type != digestAlgorithms().end(); ++type) {
+ CodeDirectory::Builder builder(*type);
+ InternalRequirements ireqs;
+ ireqs(requirements, rep->defaultRequirements(NULL, *this), context);
+ populate(*writer);
+ 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),
+ preEncryptHashMaps[preEncryptMainArch], // Only one map, the default.
+ (cdFlags & kSecCodeSignatureRuntime) ? state.mRuntimeVersionOverride : 0);
+
+ CodeDirectory *cd = builder.build();
+ if (!state.mDryRun)
+ cdSet.add(cd);
+ }
// add identification blob (made from this architecture) only if we're making a detached signature
if (state.mDetached) {
CFRef<CFDataRef> identification = rep->identification();
writer->component(cdIdentificationSlot, identification);
- }
-
- CodeDirectory *cd = builder.build();
- CFRef<CFDataRef> signature = signCodeDirectory(cd);
- if (!state.mDryRun) {
- writer->codeDirectory(cd);
- writer->signature(signature);
- writer->flush();
}
- ::free(cd);
+
+ // write out all CodeDirectories
+ if (!state.mDryRun)
+ cdSet.populate(writer);
+
+ CFRef<CFDictionaryRef> hashDict = cdSet.hashDict();
+ CFRef<CFArrayRef> hashList = cdSet.hashList();
+ CFRef<CFDataRef> signature = signCodeDirectory(cdSet.primary(), hashDict, hashList);
+ writer->signature(signature);
+
+ // commit to storage
+ writer->flush();
}
// for the purposes of this call.
//
void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::Writer &writer,
- InternalRequirements &ireqs, size_t offset /* = 0 */, size_t length /* = 0 */)
+ InternalRequirements &ireqs, size_t offset, size_t length,
+ bool mainBinary, size_t execSegBase, size_t execSegLimit,
+ unsigned alternateDigestCount,
+ PreEncryptHashMap const &preEncryptHashMap,
+ uint32_t runtimeVersion)
{
// fill the CodeDirectory
builder.executable(rep->mainExecutablePath(), pagesize, offset, length);
builder.identifier(identifier);
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);
}
if (resourceDirectory)
builder.specialSlot(cdResourceDirSlot, resourceDictData);
-#if NOT_YET
- if (state.mApplicationData)
- builder.specialSlot(cdApplicationSlot, state.mApplicationData);
-#endif
if (entitlements) {
writer.component(cdEntitlementSlot, entitlements);
builder.specialSlot(cdEntitlementSlot, entitlements);
+
+ if (mainBinary) {
+ 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))
+ builder.specialSlot(cdRepSpecificSlot, repSpecific);
writer.addDiscretionary(builder);
+
+#if 0 // rdar://problem/25720754
+ if ((signingFlags() & (kSecCSSignOpaque|kSecCSSignV1)) == 0 && builder.hashType() != kSecCodeSignatureHashSHA1) {
+ // calculate sorted list of top SuperBlob keys in this EmbeddedSignatureBlob (if any)
+ // (but not for opaque or V1 construction, which must remain bit-for-bit compatible)
+ std::vector<Endian<uint32_t> > slotVector;
+ slotVector.push_back(cdCodeDirectorySlot); // mandatory
+ std::set<CodeDirectory::Slot> filledSlots = builder.filledSpecialSlots();
+ filledSlots.insert(cdTopDirectorySlot); // will be added below
+ copy(filledSlots.begin(), filledSlots.end(), back_inserter(slotVector));
+ for (unsigned n = 0; n < alternateDigestCount; n++)
+ slotVector.push_back(cdAlternateCodeDirectorySlots + n);
+ slotVector.push_back(cdSignatureSlot);
+ CFTempData cfSlotVector(&slotVector[0], slotVector.size() * sizeof(slotVector[0]));
+ writer.component(cdTopDirectorySlot, cfSlotVector);
+ builder.specialSlot(cdTopDirectorySlot, cfSlotVector);
+ }
+#endif
}
+
#include <security_smime/tsaSupport.h>
//
// Generate the CMS signature for a (finished) CodeDirectory.
//
-CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd)
+CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd,
+ CFDictionaryRef hashDict,
+ CFArrayRef hashList)
{
assert(state.mSigner);
CFRef<CFMutableDictionaryRef> defaultTSContext = NULL;
// generate CMS signature
CFRef<CMSEncoderRef> cms;
MacOSError::check(CMSEncoderCreate(&cms.aref()));
- MacOSError::check(CMSEncoderSetCertificateChainMode(cms, kCMSCertificateChainWithRoot));
+ MacOSError::check(CMSEncoderSetCertificateChainMode(cms, kCMSCertificateChainWithRootOrFail));
CMSEncoderAddSigners(cms, state.mSigner);
CMSEncoderSetSignerAlgorithm(cms, kCMSEncoderDigestAlgorithmSHA256);
MacOSError::check(CMSEncoderSetHasDetachedContent(cms, true));
- if (signingTime) {
+ if (emitSigningTime) {
MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrSigningTime));
- MacOSError::check(CMSEncoderSetSigningTime(cms, signingTime));
+ CFAbsoluteTime time = signingTime ? signingTime : CFAbsoluteTimeGetCurrent();
+ MacOSError::check(CMSEncoderSetSigningTime(cms, time));
+ }
+
+ 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, hashAgilityV1Attribute));
}
MacOSError::check(CMSEncoderUpdateContent(cms, cd, cd->length()));
// Set up to call Timestamp server if requested
-
if (state.mWantTimeStamp)
{
CFRef<CFErrorRef> error = NULL;
CFDictionarySetValue(defaultTSContext, kTSAContextKeyURL, state.mTimestampService);
if (state.mNoTimeStampCerts)
CFDictionarySetValue(defaultTSContext, kTSAContextKeyNoCerts, kCFBooleanTrue);
- }
+ }
- CmsMessageSetTSAContext(cms, defaultTSContext);
+ CmsMessageSetTSAContext(cms, defaultTSContext);
}
-
+
CFDataRef signature;
MacOSError::check(CMSEncoderCopyEncodedContent(cms, &signature));
return signature;
}
+
+
+//
+// Our DiskRep::signingContext methods communicate with the signing subsystem
+// in terms those callers can easily understand.
+//
+string SecCodeSigner::Signer::sdkPath(const std::string &path) const
+{
+ assert(path[0] == '/'); // need absolute path here
+ if (state.mSDKRoot)
+ return cfString(state.mSDKRoot) + path;
+ else
+ return path;
+}
+
+bool SecCodeSigner::Signer::isAdhoc() const
+{
+ return state.mSigner == SecIdentityRef(kCFNull);
+}
+
+SecCSFlags SecCodeSigner::Signer::signingFlags() const
+{
+ return state.mOpFlags;
+}
//
return result;
}
+bool SecCodeSigner::Signer::booleanEntitlement(CFDictionaryRef entDict, CFStringRef key) {
+ CFBooleanRef entValue = (CFBooleanRef)CFDictionaryGetValue(entDict, key);
+
+ if (entValue == NULL || CFGetTypeID(entValue) != CFBooleanGetTypeID()) {
+ return false;
+ }
+
+ return CFBooleanGetValue(entValue);
+}
+
+void SecCodeSigner::Signer::cookEntitlements(CFDataRef entitlements, bool generateDER,
+ uint64_t *execSegFlags, CFDataRef *entitlementDER)
+{
+ if (!entitlements) {
+ return; // nothing to do.
+ }
+
+ 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();
+
+ if (generateDER) {
+ CFRef<CFErrorRef> error = NULL;
+ size_t const der_size = der_sizeof_plist(entDict, &error.aref());
+
+ 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);
+
+ 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) {
+ free(derBlob);
+ // Not fatal if we're not asked to generate DER entitlements.
+
+ secwarning("failed to parse entitlements: %s", err.what());
+ if (generateDER) {
+ throw;
+ }
+ }
+}
+
+//// Signature Editing
+
+void SecCodeSigner::Signer::edit(SecCSFlags flags)
+{
+ rep = code->diskRep()->base();
+
+ Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage();
+
+ prepareForEdit(flags);
+
+ if (fat != NULL) {
+ editMachO(fat);
+ } else {
+ editArchitectureAgnostic();
+ }
+}
+
+EditableDiskRep *SecCodeSigner::Signer::editMainExecutableRep(DiskRep *rep)
+{
+ EditableDiskRep *mainExecRep = NULL;
+ BundleDiskRep *bundleDiskRep = dynamic_cast<BundleDiskRep*>(rep);
+
+ if (bundleDiskRep) {
+ mainExecRep = dynamic_cast<EditableDiskRep*>(bundleDiskRep->mainExecRep());
+ }
+
+ return mainExecRep;
+}
+
+void SecCodeSigner::Signer::prepareForEdit(SecCSFlags flags) {
+ setDigestAlgorithms(code->hashAlgorithms());
+
+ Universal *machO = (code->diskRep()->mainExecutableIsMachO() ?
+ code->diskRep()->mainExecutableImage() : NULL);
+
+ /* We need at least one architecture in all cases because we index our
+ * RawComponentMaps by architecture. However, only machOs have any
+ * architecture at all, for generic targets there will just be one
+ * RawComponentMap.
+ * 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. */
+ editMainArch = (machO != NULL ? machO->bestNativeArch() : Architecture::local());
+
+ if (machO != NULL) {
+ if (machO->narrowed()) {
+ /* --arch gives us a narrowed SecStaticCode, but because
+ * codesign_allocate always creates or replaces signatures
+ * for all slices, we must operate on the universal
+ * SecStaticCode. Instead, we provide --edit-arch to specify
+ * which slices to edit, the others have their code signature
+ * copied without modifications.
+ */
+ MacOSError::throwMe(errSecCSNotSupported,
+ "Signature editing must be performed on universal binary instead of narrow slice (using --edit-arch instead of --arch).");
+ }
+
+ if (state.mEditArch && !machO->isUniversal()) {
+ MacOSError::throwMe(errSecCSInvalidFlags,
+ "--edit-arch is only valid for universal binaries.");
+ }
+
+ if (state.mEditCMS && machO->isUniversal() && !state.mEditArch) {
+ /* Each slice has its own distinct code signature,
+ * so a CMS blob is only valid for its one slice.
+ * Therefore, replacing all CMS blobs in all slices
+ * with the same blob is rather nonsensical, and we refuse.
+ *
+ * (Universal binaries with only one slice can exist,
+ * and in that case the slice to operate on would be
+ * umambiguous, but we are not treating those binaries
+ * specially and still want --edit-arch for consistency.)
+ */
+ MacOSError::throwMe(errSecCSNotSupported,
+ "CMS editing must be performed on specific slice (specified with --edit-arch).");
+ }
+ }
+
+ void (^editArch)(SecStaticCode *code, Architecture arch) =
+ ^(SecStaticCode *code, Architecture arch) {
+ EditableDiskRep *editRep = dynamic_cast<EditableDiskRep *>(code->diskRep());
+
+ if (editRep == NULL) {
+ MacOSError::throwMe(errSecCSNotSupported,
+ "Signature editing not supported for code of this type.");
+ }
+
+ EditableDiskRep *mainExecRep = editMainExecutableRep(code->diskRep());
+
+ if (mainExecRep != NULL) {
+ // Delegate editing to the main executable if it is an EditableDiskRep.
+ //(Which is the case for machOs.)
+ editRep = mainExecRep;
+ }
+
+ editComponents[arch] = std::make_unique<RawComponentMap>(editRep->createRawComponents());
+
+ if (!state.mEditArch || arch == state.mEditArch) {
+ if (state.mEditCMS) {
+ CFDataRef cms = state.mEditCMS.get();
+ (*editComponents[arch])[cdSignatureSlot] = cms;
+ }
+ }
+ };
+
+ editArch(code, editMainArch);
+
+ 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.
+ editArch(subcode, arch);
+ });
+
+ /* The resource dictionary is special, because it is
+ * considered "global" instead of per architecture.
+ * For editing, that means it's usually not embedded
+ * in the main executable's signature if it exists,
+ * but in the containing disk rep (e.g. the
+ * CodeResources file if the rep is a Bundle).
+ */
+ resourceDictData = rep->component(cdResourceDirSlot);
+}
+
+void SecCodeSigner::Signer::editMachO(Universal *fat) {
+ // Mach-O executable at the core - perform multi-architecture signature editing
+ RefPointer<DiskRep::Writer> writer = rep->writer();
+
+ if (state.mPreserveAFSC)
+ writer->setPreserveAFSC(state.mPreserveAFSC);
+
+ unique_ptr<ArchEditor> editor(new MachOEditor(writer, *fat,
+ this->digestAlgorithms(),
+ rep->mainExecutablePath()));
+ assert(editor->count() > 0);
+
+ if (resourceDictData && !editor->attribute(writerNoGlobal)) {
+ // For when the resource dict is "global", e.g. for bundles.
+ editor->component(cdResourceDirSlot, resourceDictData);
+ }
+
+ for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) {
+ MachOEditor::Arch &arch = *it->second;
+ arch.source.reset(fat->architecture(it->first)); // transfer ownership
+
+ if (resourceDictData && editor->attribute(writerNoGlobal)) {
+ // Technically possible to embed a resource dict in the embedded sig.
+ arch.component(cdResourceDirSlot, resourceDictData);
+ }
+
+ for (auto const &entry : *editComponents[arch.architecture]) {
+ CodeDirectory::Slot slot = entry.first;
+ CFDataRef data = entry.second.get();
+ arch.component(slot, data);
+ }
+
+ /* We must preserve the original superblob's size, as the size is
+ * also in the macho's load commands, which are itself covered
+ * by the signature. */
+ arch.blobSize = arch.source->signingLength();
+ }
+
+ editor->allocate();
+
+ for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) {
+ MachOEditor::Arch &arch = *it->second;
+ editor->reset(arch);
+
+ if (!state.mDryRun) {
+ EmbeddedSignatureBlob *blob = arch.make();
+ editor->write(arch, blob); // takes ownership of blob
+ }
+ }
+
+ if (!state.mDryRun) {
+ editor->commit();
+ }
+
+}
+
+void SecCodeSigner::Signer::editArchitectureAgnostic()
+{
+ if (state.mDryRun) {
+ return;
+
+ }
+ // non-Mach-O executable - single-instance signature editing
+ RefPointer<DiskRep::Writer> writer = rep->writer();
+
+ if(state.mPreserveAFSC)
+ writer->setPreserveAFSC(state.mPreserveAFSC);
+
+ for (auto const &entry : *editComponents[editMainArch]) {
+ CodeDirectory::Slot slot = entry.first;
+ CFDataRef data = entry.second.get();
+
+ writer->component(slot, data);
+ }
+
+ // commit to storage
+ writer->flush();
+}
} // end namespace CodeSigning
} // end namespace Security