X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/5c19dc3ae3bd8e40a9c028b0deddd50ff337692c..7e6b461318c8a779d91381531435a68ee4e8b6ed:/OSX/libsecurity_codesigning/lib/signer.cpp?ds=inline diff --git a/OSX/libsecurity_codesigning/lib/signer.cpp b/OSX/libsecurity_codesigning/lib/signer.cpp index daa2dac7..89279238 100644 --- a/OSX/libsecurity_codesigning/lib/signer.cpp +++ b/OSX/libsecurity_codesigning/lib/signer.cpp @@ -24,6 +24,8 @@ // // signer - Signing operation supervisor and controller // +#include "bundlediskrep.h" +#include "der_plist.h" #include "signer.h" #include "resources.h" #include "signerutils.h" @@ -58,10 +60,22 @@ void SecCodeSigner::Signer::sign(SecCSFlags 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) +{ /* 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 */ @@ -81,14 +95,8 @@ void SecCodeSigner::Signer::sign(SecCSFlags flags) MacOSError::throwMe(errSecCSInvalidFlags); } } - - if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) { - signMachO(fat, context); - } else { - signArchitectureAgnostic(context); - } } - + // // Remove any existing code signature from code @@ -100,9 +108,13 @@ void SecCodeSigner::Signer::remove(SecCSFlags flags) 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 { @@ -121,7 +133,7 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) { // 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 @@ -138,14 +150,14 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) 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)) { @@ -154,15 +166,22 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) 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) { @@ -171,10 +190,10 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) if (CFTypeRef csflags = CFDictionaryGetValue(infoDict, CFSTR("CSFlags"))) { if (CFGetTypeID(csflags) == CFNumberGetTypeID()) { cdFlags = cfNumber(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; @@ -182,12 +201,13 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) } 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 @@ -211,9 +231,11 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) // prepare the resource directory, if any string rpath = rep->resourcesRootPath(); + string rrpath; + CFCopyRef resourceRules; if (!rpath.empty()) { // explicitly given resource rules always win - CFCopyRef resourceRules = state.mResourceRules; + resourceRules = state.mResourceRules; // inherited rules come next (overriding embedded ones!) if (!resourceRules && (inherit & kSecCodeSignerPreserveResourceRules)) @@ -240,37 +262,126 @@ void SecCodeSigner::Signer::prepare(SecCSFlags flags) // 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(state.mPageSize) : rep->pageSize(state); - - // Timestamping setup - CFRef mTSAuth; // identity for client-side authentication to the Timestamp server + pagesize = state.mPageSize ? cfNumber(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(CFDataGetBytePtr(cddata)); + if (cd->preEncryptHashes() != NULL) { + CFRef 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(CFDataGetBytePtr(cddata)); + if (cd->runtimeVersion()) { + map[alg] = cd->runtimeVersion(); + } + } + } +} // // Collect the resource seal for a program. @@ -280,7 +391,7 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase { typedef ResourceBuilder::Rule Rule; - secdebug("codesign", "start building resource directory"); + secinfo("codesign", "start building resource directory"); __block CFRef result = makeCFMutableDictionary(); CFDictionaryRef rules = cfget(rulesDict, "rules"); @@ -295,10 +406,11 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase } CFDictionaryRef files2 = NULL; - if (!(state.signingFlags() & kSecCSSignV1)) { + if (!(signingFlags() & kSecCSSignV1)) { CFCopyRef rules2 = cfget(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("{+%O" "'^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/' = {nested=#T, weight=0}" // exclude dynamic repositories @@ -311,7 +423,7 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase // build the modern (V2) resource seal __block CFRef 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); @@ -331,8 +443,7 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase target[len] = '\0'; seal.take(cfmake("{symlink=%s}", target)); } else { - seal.take(cfmake("{hash=%O}", - CFRef(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); @@ -352,10 +463,10 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase } 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 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) { @@ -369,14 +480,14 @@ void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase 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); } } }); @@ -396,12 +507,14 @@ CFMutableDictionaryRef SecCodeSigner::Signer::signNested(const std::string &path // sign nested code and collect nesting information try { SecPointer 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("{requirement=%s,cdhash=%O}", - Dumper::dump(code->designatedRequirement()).c_str(), - code->cdHash()); + if (CFDataRef hash = code->cdHash()) + return cfmake("{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())); } @@ -417,9 +530,14 @@ CFMutableDictionaryRef SecCodeSigner::Signer::signNested(const std::string &path void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context &context) { // Mach-O executable at the core - perform multi-architecture signing + RefPointer writer = rep->writer(); + + if (state.mPreserveAFSC) + writer->setPreserveAFSC(state.mPreserveAFSC); + auto_ptr editor(state.mDetached ? static_cast(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); @@ -435,12 +553,32 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context 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) { @@ -450,8 +588,11 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context } // prepare SuperBlob size estimate - size_t cdSize = arch.cdbuilder.size(CodeDirectory::currentVersion); - arch.blobSize = arch.size(cdSize, state.mCMSSize, 0); + __block std::vector sizes; + arch.eachDigest(^(CodeDirectory::Builder& builder){ + sizes.push_back(builder.size(CodeDirectory::currentVersion)); + }); + arch.blobSize = arch.size(sizes, state.mCMSSize, 0); } editor->allocate(); @@ -461,12 +602,19 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context MachOEditor::Arch &arch = *it->second; editor->reset(arch); - // finish CodeDirectory (off new binary) and sign it - CodeDirectory *cd = arch.cdbuilder.build(); - CFRef 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 hashDict = cdSet.hashDict(); + CFRef hashList = cdSet.hashList(); + CFRef 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) { @@ -476,8 +624,9 @@ void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context } // done: write edit copy back over the original - if (!state.mDryRun) + if (!state.mDryRun) { editor->commit(); + } } @@ -490,26 +639,46 @@ void SecCodeSigner::Signer::signArchitectureAgnostic(const Requirement::Context // non-Mach-O executable - single-instance signing RefPointer 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 identification = rep->identification(); writer->component(cdIdentificationSlot, identification); - } - - CodeDirectory *cd = builder.build(); - CFRef 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 hashDict = cdSet.hashDict(); + CFRef hashList = cdSet.hashList(); + CFRef signature = signCodeDirectory(cdSet.primary(), hashDict, hashList); + writer->signature(signature); + + // commit to storage + writer->flush(); } @@ -530,7 +699,11 @@ void SecCodeSigner::Signer::populate(DiskRep::Writer &writer) // 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); @@ -538,6 +711,10 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W 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 data = rep->component(cdInfoSlot)) builder.specialSlot(cdInfoSlot, data); @@ -548,24 +725,57 @@ void SecCodeSigner::Signer::populate(CodeDirectory::Builder &builder, DiskRep::W } 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 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 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 > slotVector; + slotVector.push_back(cdCodeDirectorySlot); // mandatory + std::set 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 // // 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 defaultTSContext = NULL; @@ -577,20 +787,37 @@ CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd) // generate CMS signature CFRef 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 hashDict("{cdhashes=%O}", hashList); + CFRef 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 error = NULL; @@ -603,16 +830,40 @@ CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd) 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; +} // @@ -665,6 +916,289 @@ std::string SecCodeSigner::Signer::uniqueName() const 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(CFDataGetBytePtr(entitlements)); + + if (blob == NULL || !blob->validateBlob(CFDataGetLength(entitlements))) { + MacOSError::throwMe(errSecCSInvalidEntitlements); + } + + CFRef entDict = blob->entitlements(); + + if (generateDER) { + CFRef 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(rep); + + if (bundleDiskRep) { + mainExecRep = dynamic_cast(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(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(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 writer = rep->writer(); + + if (state.mPreserveAFSC) + writer->setPreserveAFSC(state.mPreserveAFSC); + + unique_ptr 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 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