X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_codesigning/lib/signer.cpp?ds=inline diff --git a/Security/libsecurity_codesigning/lib/signer.cpp b/Security/libsecurity_codesigning/lib/signer.cpp new file mode 100644 index 00000000..59d0e05b --- /dev/null +++ b/Security/libsecurity_codesigning/lib/signer.cpp @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2006-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +// +// signer - Signing operation supervisor and controller +// +#include "signer.h" +#include "resources.h" +#include "signerutils.h" +#include "SecCodeSigner.h" +#include +#include +#include +#include +#include +#include "resources.h" +#include "machorep.h" +#include "reqparser.h" +#include "reqdumper.h" +#include "csutilities.h" +#include +#include +#include + +namespace Security { +namespace CodeSigning { + + +// +// Sign some code. +// +void SecCodeSigner::Signer::sign(SecCSFlags flags) +{ + rep = code->diskRep()->base(); + this->prepare(flags); + + PreSigningContext context(*this); + + /* 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 */ + if (!teamIDFromCert.empty() && teamID != teamIDFromCert) + MacOSError::throwMe(errSecCSInvalidFlags); + } else { + if (teamIDFromCert.empty()) { + /* state.mTeamID is an explicitly passed teamID */ + teamID = state.mTeamID; + } else if (state.mTeamID.empty() || (state.mTeamID == teamIDFromCert)) { + /* If there was no explicit team ID set, or the explicit team ID matches + what is in the cert, use the team ID from the certificate */ + teamID = teamIDFromCert; + } else { + /* The caller passed in an explicit team ID that does not match what is + in the signing cert, which is an invalid usage */ + MacOSError::throwMe(errSecCSInvalidFlags); + } + } + + if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) { + signMachO(fat, context); + } else { + signArchitectureAgnostic(context); + } +} + + +// +// Remove any existing code signature from code +// +void SecCodeSigner::Signer::remove(SecCSFlags flags) +{ + // can't remove a detached signature + if (state.mDetached) + MacOSError::throwMe(errSecCSNotSupported); + + rep = code->diskRep(); + if (Universal *fat = state.mNoMachO ? NULL : rep->mainExecutableImage()) { + // architecture-sensitive removal + MachOEditor editor(rep->writer(), *fat, kSecCodeSignatureNoHash, rep->mainExecutablePath()); + editor.allocate(); // create copy + editor.commit(); // commit change + } else { + // architecture-agnostic removal + RefPointer writer = rep->writer(); + writer->remove(); + writer->flush(); + } +} + + +// +// Contemplate the object-to-be-signed and set up the Signer state accordingly. +// +void SecCodeSigner::Signer::prepare(SecCSFlags flags) +{ + // make sure the rep passes strict validation + if (strict) + rep->strictValidate(MacOSErrorSet()); + + // initialize progress/cancellation state + code->prepareProgress(0); // totally fake workload - we don't know how many files we'll encounter + + // get the Info.plist out of the rep for some creative defaulting + CFRef infoDict; + if (CFRef infoData = rep->component(cdInfoSlot)) + infoDict.take(makeCFDictionaryFrom(infoData)); + + uint32_t inherit = code->isSigned() ? state.mPreserveMetadata : 0; + + // work out the canonical identifier + identifier = state.mIdentifier; + if (identifier.empty() && (inherit & kSecCodeSignerPreserveIdentifier)) + identifier = code->identifier(); + if (identifier.empty()) { + identifier = rep->recommendedIdentifier(state); + if (identifier.find('.') == string::npos) + identifier = state.mIdentifierPrefix + identifier; + if (identifier.find('.') == string::npos && state.isAdhoc()) + identifier = identifier + "-" + uniqueName(); + secdebug("signer", "using default identifier=%s", identifier.c_str()); + } else + secdebug("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; + } + + entitlements = state.mEntitlementData; + if (!entitlements && (inherit & kSecCodeSignerPreserveEntitlements)) + entitlements = code->component(cdEntitlementSlot); + + // work out the CodeDirectory flags word + bool haveCdFlags = false; + if (!haveCdFlags && state.mCdFlagsGiven) { + cdFlags = state.mCdFlags; + secdebug("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(CFNumberRef(csflags)); + secdebug("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); + } else + MacOSError::throwMe(errSecCSBadDictionaryFormat); + haveCdFlags = true; + } + } + if (!haveCdFlags && (inherit & kSecCodeSignerPreserveFlags)) { + cdFlags = code->codeDirectory(false)->flags & ~kSecCodeSignatureAdhoc; + secdebug("signer", "using inherited cdFlags=0x%x", cdFlags); + haveCdFlags = true; + } + if (!haveCdFlags) + cdFlags = 0; + if (state.mSigner == SecIdentityRef(kCFNull)) // ad-hoc signing requested... + cdFlags |= kSecCodeSignatureAdhoc; // ... so note that + + // prepare the internal requirements input + if (state.mRequirements) { + if (CFGetTypeID(state.mRequirements) == CFDataGetTypeID()) { // binary form + const Requirements *rp = (const Requirements *)CFDataGetBytePtr(state.mRequirements.as()); + if (!rp->validateBlob()) + MacOSError::throwMe(errSecCSReqInvalid); + requirements = rp->clone(); + } else if (CFGetTypeID(state.mRequirements) == CFStringGetTypeID()) { // text form + CFRef reqText = CFStringCreateMutableCopy(NULL, 0, state.mRequirements.as()); + // substitute $ variable tokens + 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(); + + // prepare the resource directory, if any + string rpath = rep->resourcesRootPath(); + if (!rpath.empty()) { + // explicitly given resource rules always win + CFCopyRef resourceRules = state.mResourceRules; + + // inherited rules come next (overriding embedded ones!) + if (!resourceRules && (inherit & kSecCodeSignerPreserveResourceRules)) + if (CFDictionaryRef oldRules = code->resourceDictionary(false)) + resourceRules = oldRules; + + // embedded resource rules come next + if (!resourceRules && infoDict) + if (CFTypeRef spec = CFDictionaryGetValue(infoDict, _kCFBundleResourceSpecificationKey)) { + if (CFGetTypeID(spec) == CFStringGetTypeID()) + if (CFRef data = cfLoadFile(rpath + "/" + cfString(CFStringRef(spec)))) + if (CFDictionaryRef dict = makeCFDictionaryFrom(data)) + resourceRules.take(dict); + if (!resourceRules) // embedded rules present but unacceptable + MacOSError::throwMe(errSecCSResourceRulesInvalid); + } + + // if we got one from anywhere (but the defaults), sanity-check it + if (resourceRules) { + CFTypeRef rules = CFDictionaryGetValue(resourceRules, CFSTR("rules")); + if (!rules || CFGetTypeID(rules) != CFDictionaryGetTypeID()) + MacOSError::throwMe(errSecCSResourceRulesInvalid); + } + + // finally, ask the DiskRep for its default + if (!resourceRules) + resourceRules.take(rep->defaultResourceRules(state)); + + // 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); + } + + // screen and set the signing time + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + if (state.mSigningTime == CFDateRef(kCFNull)) { + signingTime = 0; // no time at all + } else if (!state.mSigningTime) { + signingTime = now; // default + } else { + CFAbsoluteTime time = CFDateGetAbsoluteTime(state.mSigningTime); + if (time > now) // not allowed to post-date a signature + MacOSError::throwMe(errSecCSBadDictionaryFormat); + signingTime = time; + } + + pagesize = state.mPageSize ? cfNumber(state.mPageSize) : rep->pageSize(state); + + // Timestamping setup + CFRef mTSAuth; // identity for client-side authentication to the Timestamp server +} + + +// +// Collect the resource seal for a program. +// This includes both sealed resources and information about nested code. +// +void SecCodeSigner::Signer::buildResources(std::string root, std::string relBase, CFDictionaryRef rulesDict) +{ + typedef ResourceBuilder::Rule Rule; + + secdebug("codesign", "start building resource directory"); + __block CFRef result = makeCFMutableDictionary(); + + CFDictionaryRef rules = cfget(rulesDict, "rules"); + assert(rules); + + CFDictionaryRef files2 = NULL; + if (!(state.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). + // 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 + "}", rules); + } + // build the modern (V2) resource seal + __block CFRef files = makeCFMutableDictionary(); + ResourceBuilder resourceBuilder(root, relBase, rules2, digestAlgorithm(), strict, MacOSErrorSet()); + ResourceBuilder &resources = resourceBuilder; // (into block) + rep->adjustResources(resources); + resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const char *relpath, Rule *rule) { + CFRef seal; + if (ruleFlags & ResourceBuilder::nested) { + seal.take(signNested(ent, relpath)); + } else if (ent->fts_info == FTS_SL) { + char target[PATH_MAX]; + ssize_t len = ::readlink(ent->fts_accpath, target, sizeof(target)-1); + if (len < 0) + UnixError::check(-1); + target[len] = '\0'; + seal.take(cfmake("{symlink=%s}", target)); + } else { + seal.take(cfmake("{hash=%O}", + CFRef(resources.hashFile(ent->fts_accpath)).get())); + } + if (ruleFlags & ResourceBuilder::optional) + CFDictionaryAddValue(seal, CFSTR("optional"), kCFBooleanTrue); + CFTypeRef hash; + if ((hash = CFDictionaryGetValue(seal, CFSTR("hash"))) && CFDictionaryGetCount(seal) == 1) // simple form + CFDictionaryAddValue(files, CFTempString(relpath).get(), hash); + else + CFDictionaryAddValue(files, CFTempString(relpath).get(), seal.get()); + code->reportProgress(); + }); + CFDictionaryAddValue(result, CFSTR("rules2"), resourceBuilder.rules()); + files2 = files; + CFDictionaryAddValue(result, CFSTR("files2"), files2); + } + + CFDictionaryAddValue(result, CFSTR("rules"), rules); // preserve V1 rules in any case + if (!(state.signingFlags() & kSecCSSignNoV1)) { + // build the legacy (V1) resource seal + __block CFRef files = makeCFMutableDictionary(); + ResourceBuilder resourceBuilder(root, relBase, rules, digestAlgorithm(), strict, MacOSErrorSet()); + ResourceBuilder &resources = resourceBuilder; + rep->adjustResources(resources); // DiskRep-specific adjustments + resources.scan(^(FTSENT *ent, uint32_t ruleFlags, const char *relpath, Rule *rule) { + if (ent->fts_info == FTS_F) { + CFRef hash; + if (files2) // try to get the hash from a previously-made version + 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)); + if (ruleFlags == 0) { // default case - plain hash + cfadd(files, "{%s=%O}", relpath, hash.get()); + secdebug("csresource", "%s added simple (rule %p)", relpath, rule); + } else { // more complicated - use a sub-dictionary + cfadd(files, "{%s={hash=%O,optional=%B}}", + relpath, hash.get(), ruleFlags & ResourceBuilder::optional); + secdebug("csresource", "%s added complex (rule %p)", relpath, rule); + } + } + }); + CFDictionaryAddValue(result, CFSTR("files"), files.get()); + } + + resourceDirectory = result.get(); + resourceDictData.take(makeCFData(resourceDirectory.get())); +} + + +// +// Deal with one piece of nested code +// +CFMutableDictionaryRef SecCodeSigner::Signer::signNested(FTSENT *ent, const char *relpath) +{ + // sign nested code and collect nesting information + try { + SecPointer code = new SecStaticCode(DiskRep::bestGuess(ent->fts_path)); + if (state.signingFlags() & kSecCSSignNestedCode) + this->state.sign(code, state.signingFlags()); + std::string dr = Dumper::dump(code->designatedRequirement()); + return cfmake("{requirement=%s,cdhash=%O}", + Dumper::dump(code->designatedRequirement()).c_str(), + code->cdHash()); + } catch (const CommonError &err) { + CSError::throwMe(err.osStatus(), kSecCFErrorPath, CFTempURL(relpath, false, this->code->resourceBase())); + } +} + + +// +// Sign a Mach-O binary, using liberal dollops of that special Mach-O magic sauce. +// Note that this will deal just fine with non-fat Mach-O binaries, but it will +// treat them as architectural binaries containing (only) one architecture - that +// interpretation is courtesy of the Universal/MachO support classes. +// +void SecCodeSigner::Signer::signMachO(Universal *fat, const Requirement::Context &context) +{ + // Mach-O executable at the core - perform multi-architecture signing + auto_ptr editor(state.mDetached + ? static_cast(new BlobEditor(*fat, *this)) + : new MachOEditor(rep->writer(), *fat, this->digestAlgorithm(), rep->mainExecutablePath())); + assert(editor->count() > 0); + if (!editor->attribute(writerNoGlobal)) // can store architecture-common components + populate(*editor); + + // pass 1: prepare signature blobs and calculate sizes + for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) { + MachOEditor::Arch &arch = *it->second; + arch.source.reset(fat->architecture(it->first)); + + // library validation is not compatible with i386 + if (arch.architecture.cpuType() == CPU_TYPE_I386) { + if (cdFlags & kSecCodeSignatureLibraryValidation) { + MacOSError::throwMe(errSecCSBadLVArch); + } + } + + arch.ireqs(requirements, rep->defaultRequirements(&arch.architecture, state), 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()); + + // add identification blob (made from this architecture) only if we're making a detached signature + if (state.mDetached) { + CFRef identification = MachORep::identificationFor(arch.source.get()); + arch.add(cdIdentificationSlot, BlobWrapper::alloc( + CFDataGetBytePtr(identification), CFDataGetLength(identification))); + } + + // prepare SuperBlob size estimate + size_t cdSize = arch.cdbuilder.size(CodeDirectory::currentVersion); + arch.blobSize = arch.size(cdSize, state.mCMSSize, 0); + } + + editor->allocate(); + + // pass 2: Finish and generate signatures, and write them + for (MachOEditor::Iterator it = editor->begin(); it != editor->end(); ++it) { + 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); + + // complete the SuperBlob + arch.add(cdCodeDirectorySlot, cd); // takes ownership + arch.add(cdSignatureSlot, BlobWrapper::alloc( + CFDataGetBytePtr(signature), CFDataGetLength(signature))); + if (!state.mDryRun) { + EmbeddedSignatureBlob *blob = arch.make(); + editor->write(arch, blob); // takes ownership of blob + } + } + + // done: write edit copy back over the original + if (!state.mDryRun) + editor->commit(); +} + + +// +// Sign a binary that has no notion of architecture. +// That currently means anything that isn't Mach-O format. +// +void SecCodeSigner::Signer::signArchitectureAgnostic(const Requirement::Context &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()); + + // 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); +} + + +// +// Global populate - send components to destination buffers ONCE +// +void SecCodeSigner::Signer::populate(DiskRep::Writer &writer) +{ + if (resourceDirectory && !state.mDryRun) + writer.component(cdResourceDirSlot, resourceDictData); +} + + +// +// Per-architecture populate - send components to per-architecture buffers +// and populate the CodeDirectory for an architecture. In architecture-agnostic +// signing operations, the non-architectural binary is considered one (arbitrary) architecture +// 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 */) +{ + // fill the CodeDirectory + builder.executable(rep->mainExecutablePath(), pagesize, offset, length); + builder.flags(cdFlags); + builder.identifier(identifier); + builder.teamID(teamID); + + if (CFRef data = rep->component(cdInfoSlot)) + builder.specialSlot(cdInfoSlot, data); + if (ireqs) { + CFRef data = makeCFData(*ireqs); + writer.component(cdRequirementsSlot, data); + builder.specialSlot(cdRequirementsSlot, 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); + } + + writer.addDiscretionary(builder); +} + +#include + +// +// Generate the CMS signature for a (finished) CodeDirectory. +// +CFDataRef SecCodeSigner::Signer::signCodeDirectory(const CodeDirectory *cd) +{ + assert(state.mSigner); + CFRef defaultTSContext = NULL; + + // a null signer generates a null signature blob + if (state.mSigner == SecIdentityRef(kCFNull)) + return CFDataCreate(NULL, NULL, 0); + + // generate CMS signature + CFRef cms; + MacOSError::check(CMSEncoderCreate(&cms.aref())); + MacOSError::check(CMSEncoderSetCertificateChainMode(cms, kCMSCertificateChainWithRoot)); + CMSEncoderAddSigners(cms, state.mSigner); + MacOSError::check(CMSEncoderSetHasDetachedContent(cms, true)); + + if (signingTime) { + MacOSError::check(CMSEncoderAddSignedAttributes(cms, kCMSAttrSigningTime)); + MacOSError::check(CMSEncoderSetSigningTime(cms, signingTime)); + } + + MacOSError::check(CMSEncoderUpdateContent(cms, cd, cd->length())); + + // Set up to call Timestamp server if requested + + if (state.mWantTimeStamp) + { + CFRef 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; +} + + +// +// Parse a text of the form +// flag,...,flag +// where each flag is the canonical name of a signable CodeDirectory flag. +// No abbreviations are allowed, and internally set flags are not accepted. +// +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) // not found + MacOSError::throwMe(errSecCSInvalidFlags); + if (comma == string::npos) // last word + break; + } + return flags; +} + + +// +// Generate a unique string from our underlying DiskRep. +// We could get 90%+ of the uniquing benefit by just generating +// a random string here. Instead, we pick the (hex string encoding of) +// the source rep's unique identifier blob. For universal binaries, +// this is the canonical local architecture, which is a bit arbitrary. +// This provides us with a consistent unique string for all architectures +// of a fat binary, *and* (unlike a random string) is reproducible +// for identical inputs, even upon resigning. +// +std::string SecCodeSigner::Signer::uniqueName() const +{ + CFRef 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; +} + + +} // end namespace CodeSigning +} // end namespace Security