#include "bundlediskrep.h"
#include "der_plist.h"
#include "signer.h"
#include "resources.h"
#include "signerutils.h"
#include "SecCodeSigner.h"
#include <Security/SecIdentity.h>
#include <Security/CMSEncoder.h>
#include <Security/CMSPrivate.h>
#include <Security/CSCommonPriv.h>
#include <CoreFoundation/CFBundlePriv.h>
#include "resources.h"
#include "machorep.h"
#include "reqparser.h"
#include "reqdumper.h"
#include "csutilities.h"
#include <security_utilities/unix++.h>
#include <security_utilities/unixchild.h>
#include <security_utilities/cfmunge.h>
#include <security_utilities/dispatch.h>
#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
namespace Security {
namespace CodeSigning {
void SecCodeSigner::Signer::sign(SecCSFlags flags)
{
rep = code->diskRep()->base();
this->prepare(flags);
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)
{
std::string teamIDFromCert = state.getTeamIDFromSigner(context.certs);
if (state.mPreserveMetadata & kSecCodeSignerPreserveTeamIdentifier) {
if (!teamIDFromCert.empty() && teamID != teamIDFromCert)
MacOSError::throwMe(errSecCSInvalidFlags);
} else {
if (teamIDFromCert.empty()) {
teamID = state.mTeamID;
} else if (state.mTeamID.empty() || (state.mTeamID == teamIDFromCert)) {
teamID = teamIDFromCert;
} else {
MacOSError::throwMe(errSecCSInvalidFlags);
}
}
}
void SecCodeSigner::Signer::remove(SecCSFlags flags)
{
if (state.mDetached)
MacOSError::throwMe(errSecCSNotSupported);
rep = code->diskRep();
if (state.mPreserveAFSC)
rep->writer()->setPreserveAFSC(state.mPreserveAFSC);
if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) {
MachOEditor editor(rep->writer(), *fat, digestAlgorithms(), rep->mainExecutablePath());
editor.allocate(); editor.commit(); } else {
RefPointer<DiskRep::Writer> writer = rep->writer();
writer->remove();
writer->flush();
}
}
void SecCodeSigner::Signer::prepare(SecCSFlags flags)
{
if (strict)
rep->strictValidate(NULL, MacOSErrorSet(), flags | (kSecCSQuickCheck|kSecCSRestrictSidebandData));
code->prepareProgress(0);
CFRef<CFDictionaryRef> infoDict;
if (CFRef<CFDataRef> infoData = rep->component(cdInfoSlot))
infoDict.take(makeCFDictionaryFrom(infoData));
uint32_t inherit = code->isSigned() ? state.mPreserveMetadata : 0;
identifier = state.mIdentifier;
if (identifier.empty() && (inherit & kSecCodeSignerPreserveIdentifier))
identifier = code->identifier();
if (identifier.empty()) {
identifier = rep->recommendedIdentifier(*this);
if (identifier.find('.') == string::npos)
identifier = state.mIdentifierPrefix + identifier;
if (identifier.find('.') == string::npos && isAdhoc())
identifier = identifier + "-" + uniqueName();
secinfo("signer", "using default identifier=%s", identifier.c_str());
} else
secinfo("signer", "using explicit identifier=%s", identifier.c_str());
teamID = state.mTeamID;
if (teamID.empty() && (inherit & kSecCodeSignerPreserveTeamIdentifier)) {
const char *c_id = code->teamID();
if (c_id)
teamID = c_id;
}
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;
bool haveCdFlags = false;
if (!haveCdFlags && state.mCdFlagsGiven) {
cdFlags = state.mCdFlags;
secinfo("signer", "using explicit cdFlags=0x%x", cdFlags);
haveCdFlags = true;
}
if (!haveCdFlags) {
cdFlags = 0;
if (infoDict)
if (CFTypeRef csflags = CFDictionaryGetValue(infoDict, CFSTR("CSFlags"))) {
if (CFGetTypeID(csflags) == CFNumberGetTypeID()) {
cdFlags = cfNumber<uint32_t>(CFNumberRef(csflags));
secinfo("signer", "using numeric cdFlags=0x%x from Info.plist", cdFlags);
} else if (CFGetTypeID(csflags) == CFStringGetTypeID()) {
cdFlags = cdTextFlags(cfString(CFStringRef(csflags)));
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;
secinfo("signer", "using inherited cdFlags=0x%x", cdFlags);
haveCdFlags = true;
}
if (!haveCdFlags)
cdFlags = 0;
if ((state.mSigner == SecIdentityRef(kCFNull)) &&
!state.mOmitAdhocFlag) cdFlags |= kSecCodeSignatureAdhoc;
if (state.mRequirements) {
if (CFGetTypeID(state.mRequirements) == CFDataGetTypeID()) { const Requirements *rp = (const Requirements *)CFDataGetBytePtr(state.mRequirements.as<CFDataRef>());
if (!rp->validateBlob())
MacOSError::throwMe(errSecCSReqInvalid);
requirements = rp->clone();
} else if (CFGetTypeID(state.mRequirements) == CFStringGetTypeID()) { CFRef<CFMutableStringRef> reqText = CFStringCreateMutableCopy(NULL, 0, state.mRequirements.as<CFStringRef>());
CFRange range = { 0, CFStringGetLength(reqText) };
CFStringFindAndReplace(reqText, CFSTR("$self.identifier"), CFTempString(identifier), range, 0);
requirements = parseRequirements(cfString(reqText));
} else
MacOSError::throwMe(errSecCSInvalidObjectRef);
} else if (inherit & kSecCodeSignerPreserveRequirements)
if (const Requirements *rp = code->internalRequirements())
requirements = rp->clone();
string rpath = rep->resourcesRootPath();
string rrpath;
CFCopyRef<CFDictionaryRef> resourceRules;
if (!rpath.empty()) {
resourceRules = state.mResourceRules;
if (!resourceRules && (inherit & kSecCodeSignerPreserveResourceRules))
if (CFDictionaryRef oldRules = code->resourceDictionary(false))
resourceRules = oldRules;
if (!resourceRules && infoDict)
if (CFTypeRef spec = CFDictionaryGetValue(infoDict, _kCFBundleResourceSpecificationKey)) {
if (CFGetTypeID(spec) == CFStringGetTypeID())
if (CFRef<CFDataRef> data = cfLoadFile(rpath + "/" + cfString(CFStringRef(spec))))
if (CFDictionaryRef dict = makeCFDictionaryFrom(data))
resourceRules.take(dict);
if (!resourceRules) MacOSError::throwMe(errSecCSResourceRulesInvalid);
}
if (resourceRules) {
CFTypeRef rules = CFDictionaryGetValue(resourceRules, CFSTR("rules"));
if (!rules || CFGetTypeID(rules) != CFDictionaryGetTypeID())
MacOSError::throwMe(errSecCSResourceRulesInvalid);
}
if (!resourceRules)
resourceRules.take(rep->defaultResourceRules(*this));
rrpath = rpath;
if (signingFlags() & kSecCSSignBundleRoot)
rrpath = cfStringRelease(rep->copyCanonicalPath());
}
if (state.mSigningTime == CFDateRef(kCFNull)) {
emitSigningTime = false; } else if (!state.mSigningTime) {
emitSigningTime = true;
signingTime = 0; } else {
CFAbsoluteTime time = CFDateGetAbsoluteTime(state.mSigningTime);
if (time > CFAbsoluteTimeGetCurrent()) MacOSError::throwMe(errSecCSBadDictionaryFormat);
emitSigningTime = true;
signingTime = time;
}
pagesize = state.mPageSize ? cfNumber<size_t>(state.mPageSize) : rep->pageSize(*this);
rep->prepareForSigning(*this);
if (hashAlgorithms.empty()) { hashAlgorithms.insert(kSecCodeSignatureHashSHA1);
hashAlgorithms.insert(kSecCodeSignatureHashSHA256);
}
if (!rpath.empty()) {
buildResources(rrpath, rpath, resourceRules);
}
if (inherit & kSecCodeSignerPreservePEH) {
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()); Architecture arch = fat->bestNativeArch(); addPreEncryptHashes(preEncryptHashMaps[arch], subcode);
});
}
if (inherit & kSecCodeSignerPreserveRuntime) {
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()); Architecture arch = fat->bestNativeArch(); 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();
}
}
}
}
void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase, CFDictionaryRef rulesDict)
{
typedef ResourceBuilder::Rule Rule;
secinfo("codesign", "start building resource directory");
__block CFRef<CFMutableDictionaryRef> result = makeCFMutableDictionary();
CFDictionaryRef rules = cfget<CFDictionaryRef>(rulesDict, "rules");
assert(rules);
if (this->state.mLimitedAsync == NULL) {
this->state.mLimitedAsync =
new LimitedAsync(false);
}
CFDictionaryRef files2 = NULL;
if (!(signingFlags() & kSecCSSignV1)) {
CFCopyRef<CFDictionaryRef> rules2 = cfget<CFDictionaryRef>(rulesDict, "rules2");
if (!rules2) {
rules2 = cfmake<CFDictionaryRef>("{+%O"
"'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=0}" "}", rules);
}
Dispatch::Group group;
Dispatch::Group &groupRef = group;
__block CFRef<CFMutableDictionaryRef> files = makeCFMutableDictionary();
CFMutableDictionaryRef filesRef = files.get(); ResourceBuilder resourceBuilder(root, relBase, rules2, strict, MacOSErrorSet());
ResourceBuilder &resources = resourceBuilder; rep->adjustResources(resources);
resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const std::string relpath, Rule *rule) {
bool isSymlink = (ent->fts_info == FTS_SL);
const std::string path(ent->fts_path);
const std::string accpath(ent->fts_accpath);
this->state.mLimitedAsync->perform(groupRef, ^{
CFRef<CFMutableDictionaryRef> seal;
if (ruleFlags & ResourceBuilder::nested) {
seal.take(signNested(path, relpath));
} else if (isSymlink) {
char target[PATH_MAX];
ssize_t len = ::readlink(accpath.c_str(), target, sizeof(target)-1);
if (len < 0)
UnixError::check(-1);
target[len] = '\0';
seal.take(cfmake<CFMutableDictionaryRef>("{symlink=%s}", target));
} else {
seal.take(resources.hashFile(accpath.c_str(), digestAlgorithms(), signingFlags() & kSecCSSignStrictPreflight));
}
if (ruleFlags & ResourceBuilder::optional)
CFDictionaryAddValue(seal, CFSTR("optional"), kCFBooleanTrue);
CFTypeRef hash;
StLock<Mutex> _(resourceLock);
if ((hash = CFDictionaryGetValue(seal, CFSTR("hash"))) && CFDictionaryGetCount(seal) == 1) CFDictionaryAddValue(filesRef, CFTempString(relpath).get(), hash);
else
CFDictionaryAddValue(filesRef, CFTempString(relpath).get(), seal.get());
code->reportProgress();
});
});
group.wait();
CFDictionaryAddValue(result, CFSTR("rules2"), resourceBuilder.rules());
files2 = files;
CFDictionaryAddValue(result, CFSTR("files2"), files2);
}
CFDictionaryAddValue(result, CFSTR("rules"), rules); if (!(signingFlags() & kSecCSSignNoV1)) {
__block CFRef<CFMutableDictionaryRef> files = makeCFMutableDictionary();
ResourceBuilder resourceBuilder(root, relBase, rules, strict, MacOSErrorSet());
ResourceBuilder &resources = resourceBuilder;
rep->adjustResources(resources); resources.scan(^(FTSENT *ent, uint32_t ruleFlags, std::string relpath, Rule *rule) {
if (ent->fts_info == FTS_F) {
CFRef<CFDataRef> hash;
if (files2) if (CFTypeRef seal = CFDictionaryGetValue(files2, CFTempString(relpath))) {
if (CFGetTypeID(seal) == CFDataGetTypeID())
hash = CFDataRef(seal);
else
hash = CFDataRef(CFDictionaryGetValue(CFDictionaryRef(seal), CFSTR("hash")));
}
if (!hash)
hash.take(resources.hashFile(ent->fts_accpath, kSecCodeSignatureHashSHA1));
if (ruleFlags == 0) { cfadd(files, "{%s=%O}", relpath.c_str(), hash.get());
secinfo("csresource", "%s added simple (rule %p)", relpath.c_str(), rule);
} else { cfadd(files, "{%s={hash=%O,optional=%B}}",
relpath.c_str(), hash.get(), ruleFlags & ResourceBuilder::optional);
secinfo("csresource", "%s added complex (rule %p)", relpath.c_str(), rule);
}
}
});
CFDictionaryAddValue(result, CFSTR("files"), files.get());
}
resourceDirectory = result.get();
resourceDictData.take(makeCFData(resourceDirectory.get()));
}
CFMutableDictionaryRef SecCodeSigner::Signer::signNested(const std::string &path, const std::string &relpath)
{
try {
SecPointer<SecStaticCode> code = new SecStaticCode(DiskRep::bestGuess(path));
if (signingFlags() & kSecCSSignNestedCode)
this->state.sign(code, signingFlags());
std::string dr = Dumper::dump(code->designatedRequirement());
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)
{
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(writer, *fat, this->digestAlgorithms(), rep->mainExecutablePath()));
assert(editor->count() > 0);
if (!editor->attribute(writerNoGlobal)) populate(*editor);
for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) {
MachOEditor::Arch &arch = *it->second;
arch.source.reset(fat->architecture(it->first));
if (arch.architecture.cpuType() == CPU_TYPE_I386) {
if (cdFlags & kSecCodeSignatureLibraryValidation) {
MacOSError::throwMe(errSecCSBadLVArch);
}
}
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)) 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),
preEncryptHashMaps[arch.architecture], runtimeVersionToUse);
});
}
if (state.mDetached) {
CFRef<CFDataRef> identification = MachORep::identificationFor(arch.source.get());
arch.add(cdIdentificationSlot, BlobWrapper::alloc(
CFDataGetBytePtr(identification), CFDataGetLength(identification)));
}
__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();
for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) {
MachOEditor::Arch &arch = *it->second;
editor->reset(arch);
__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);
cdSet.populate(&arch);
arch.add(cdSignatureSlot, BlobWrapper::alloc(
CFDataGetBytePtr(signature), CFDataGetLength(signature)));
if (!state.mDryRun) {
EmbeddedSignatureBlob *blob = arch.make();
editor->write(arch, blob); }
}
if (!state.mDryRun) {
editor->commit();
}
}
void SecCodeSigner::Signer::signArchitectureAgnostic(const Requirement::Context &context)
{
RefPointer<DiskRep::Writer> writer = state.mDetached ?
(new DetachedBlobWriter(*this)) : rep->writer();
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, rep->execSegBase(NULL), rep->execSegLimit(NULL),
unsigned(digestAlgorithms().size()-1),
preEncryptHashMaps[preEncryptMainArch], (cdFlags & kSecCodeSignatureRuntime) ? state.mRuntimeVersionOverride : 0);
CodeDirectory *cd = builder.build();
if (!state.mDryRun)
cdSet.add(cd);
}
if (state.mDetached) {
CFRef<CFDataRef> identification = rep->identification();
writer->component(cdIdentificationSlot, identification);
}
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);
writer->flush();
}
void SecCodeSigner::Signer::populate(DiskRep::Writer &writer)
{
if (resourceDirectory && !state.mDryRun)
writer.component(cdResourceDirSlot, resourceDictData);
}
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,
PreEncryptHashMap const &preEncryptHashMap,
uint32_t runtimeVersion)
{
builder.executable(rep->mainExecutablePath(), pagesize, offset, length);
builder.flags(cdFlags);
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 (ireqs) {
CFRef<CFDataRef> data = makeCFData(*ireqs);
writer.component(cdRequirementsSlot, data);
builder.specialSlot(cdRequirementsSlot, data);
}
if (resourceDirectory)
builder.specialSlot(cdResourceDirSlot, resourceDictData);
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) {
std::vector<Endian<uint32_t> > slotVector;
slotVector.push_back(cdCodeDirectorySlot); std::set<CodeDirectory::Slot> filledSlots = builder.filledSpecialSlots();
filledSlots.insert(cdTopDirectorySlot); 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>
CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd,
CFDictionaryRef hashDict,
CFArrayRef hashList)
{
assert(state.mSigner);
CFRef<CFMutableDictionaryRef> defaultTSContext = NULL;
if (state.mSigner == SecIdentityRef(kCFNull))
return CFDataCreate(NULL, NULL, 0);
CFRef<CMSEncoderRef> cms;
MacOSError::check(CMSEncoderCreate(&cms.aref()));
MacOSError::check(CMSEncoderSetCertificateChainMode(cms, kCMSCertificateChainWithRoot));
CMSEncoderAddSigners(cms, state.mSigner);
CMSEncoderSetSignerAlgorithm(cms, kCMSEncoderDigestAlgorithmSHA256);
MacOSError::check(CMSEncoderSetHasDetachedContent(cms, true));
if (emitSigningTime) {
MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrSigningTime));
CFAbsoluteTime time = signingTime ? signingTime : CFAbsoluteTimeGetCurrent();
MacOSError::check(CMSEncoderSetSigningTime(cms, time));
}
if (hashDict != NULL) {
assert(hashList != NULL);
MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrAppleCodesigningHashAgilityV2));
MacOSError::check(CMSEncoderSetAppleCodesigningHashAgilityV2(cms, hashDict));
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()));
if (state.mWantTimeStamp)
{
CFRef<CFErrorRef> error = NULL;
defaultTSContext = SecCmsTSAGetDefaultContext(&error.aref());
if (error)
MacOSError::throwMe(errSecDataNotAvailable);
if (state.mNoTimeStampCerts || state.mTimestampService) {
if (state.mTimestampService)
CFDictionarySetValue(defaultTSContext, kTSAContextKeyURL, state.mTimestampService);
if (state.mNoTimeStampCerts)
CFDictionarySetValue(defaultTSContext, kTSAContextKeyNoCerts, kCFBooleanTrue);
}
CmsMessageSetTSAContext(cms, defaultTSContext);
}
CFDataRef signature;
MacOSError::check(CMSEncoderCopyEncodedContent(cms, &signature));
return signature;
}
string SecCodeSigner::Signer::sdkPath(const std::string &path) const
{
assert(path[0] == '/'); 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;
}
uint32_t SecCodeSigner::Signer::cdTextFlags(std::string text)
{
uint32_t flags = 0;
for (string::size_type comma = text.find(','); ; text = text.substr(comma+1), comma = text.find(',')) {
string word = (comma == string::npos) ? text : text.substr(0, comma);
const SecCodeDirectoryFlagTable *item;
for (item = kSecCodeDirectoryFlagTable; item->name; item++)
if (item->signable && word == item->name) {
flags |= item->value;
break;
}
if (!item->name) MacOSError::throwMe(errSecCSInvalidFlags);
if (comma == string::npos) break;
}
return flags;
}
std::string SecCodeSigner::Signer::uniqueName() const
{
CFRef<CFDataRef> identification = rep->identification();
const UInt8 *ident = CFDataGetBytePtr(identification);
const CFIndex length = CFDataGetLength(identification);
string result;
for (CFIndex n = 0; n < length; n++) {
char hex[3];
snprintf(hex, sizeof(hex), "%02x", ident[n]);
result += hex;
}
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; }
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);
secwarning("failed to parse entitlements: %s", err.what());
if (generateDER) {
throw;
}
}
}
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);
editMainArch = (machO != NULL ? machO->bestNativeArch() : Architecture::local());
if (machO != NULL) {
if (machO->narrowed()) {
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) {
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) {
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()); Architecture arch = fat->bestNativeArch(); editArch(subcode, arch);
});
resourceDictData = rep->component(cdResourceDirSlot);
}
void SecCodeSigner::Signer::editMachO(Universal *fat) {
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)) {
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));
if (resourceDictData && editor->attribute(writerNoGlobal)) {
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);
}
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); }
}
if (!state.mDryRun) {
editor->commit();
}
}
void SecCodeSigner::Signer::editArchitectureAgnostic()
{
if (state.mDryRun) {
return;
}
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);
}
writer->flush();
}
} }