#include "codesign.h"
#include <Security/CodeSigning.h>
#include <Security/CSCommonPriv.h>
#include <Security/SecIdentitySearchPriv.h>
#include <Security/SecPolicyPriv.h>
#include <security_utilities/cfutilities.h>
#include <security_utilities/cfmunge.h>
#include <security_codesigning/reqdumper.h>
#include <security_codesigning/cdbuilder.h>
#include <security_codesigning/reqparser.h>
#include <security_codesigning/renum.h>
#include <Security/CMSEncoder.h>
#include <cstdio>
#include <cmath>
#include <getopt.h>
#include <sys/codesign.h>
#include <sys/param.h> // MAXPATHLEN
using namespace UnixPlusPlus;
unsigned verbose = 0; bool force = false; bool continueOnError = false;
int exitcode = exitSuccess;
static const HashType hashTypes[] = {
{ "sha1", kSecCodeSignatureHashSHA1, SHA1::digestLength },
{ "sha256", kSecCodeSignatureHashSHA256, 256 / 8 },
{ "skein160x256", kSecCodeSignatureHashPrestandardSkein160x256, 160 / 8 },
{ "skein256x512", kSecCodeSignatureHashPrestandardSkein256x512, 256 / 8 },
{ NULL }
};
const HashType *findHashType(const char *hashName)
{
int length = strlen(hashName);
const HashType *match = NULL;
for (const HashType *h = hashTypes; h->name; h++)
if (!strncmp(hashName, h->name, length)) if (match)
fail("%s: ambiguous hash specification (%s or %s)",
hashName, match->name, h->name);
else
match = h;
if (match)
return match;
fail("%s: unknown hash specification", hashName);
}
const HashType *findHashType(uint32_t hashCode)
{
for (const HashType *h = hashTypes; h->name; h++)
if (h->code == hashCode)
return h;
return NULL;
}
CFTypeRef readRequirement(const string &source, SecCSFlags flags)
{
CFTypeRef result;
ErrorCheck check;
if (source[0] == '=') { check(SecRequirementsCreateWithString(CFTempString(source.substr(1)), flags, &result, check));
return result;
}
FILE *f;
if (source == "-") {
f = stdin;
} else if (!(f = fopen(source.c_str(), "r"))) {
perror(source.c_str());
fail("invalid requirement specification");
}
int first = getc(f);
ungetc(first, f);
if (first == kSecCodeMagicByte) { BlobCore *blob = BlobCore::readBlob(f);
switch (blob->magic()) {
case kSecCodeMagicRequirement:
MacOSError::check(SecRequirementCreateWithData(CFTempData(*blob), kSecCSDefaultFlags, (SecRequirementRef *)&result));
break;
case kSecCodeMagicRequirementSet:
result = makeCFData(*blob);
break;
default:
fail((source + ": not a recognized requirement file").c_str());
}
::free(blob);
} else { char buffer[10240]; int length = fread(buffer, 1, sizeof(buffer) - 1, f);
buffer[length] = '\0';
check(SecRequirementsCreateWithString(CFTempString(buffer), flags, &result, check));
}
if (f != stdin)
fclose(f);
return result;
}
void ErrorCheck::throwError()
{
assert(mError);
throw Error(mError);
}
string keychainPath(CFTypeRef whatever)
{
CFRef<SecKeychainRef> keychain;
if (CFGetTypeID(whatever) == SecKeychainGetTypeID()) {
keychain = SecKeychainRef(whatever);
} else if (CFGetTypeID(whatever) == SecIdentityGetTypeID()) {
CFRef<SecCertificateRef> cert;
MacOSError::check(SecIdentityCopyCertificate(SecIdentityRef(whatever), &cert.aref()));
MacOSError::check(SecKeychainItemCopyKeychain(cert.as<SecKeychainItemRef>(), &keychain.aref()));
} else { switch (OSStatus rc = SecKeychainItemCopyKeychain(SecKeychainItemRef(whatever), &keychain.aref())) {
case noErr:
break;
case errSecNoSuchKeychain:
return "(nowhere)";
default:
MacOSError::throwMe(rc);
}
}
char path[MAXPATHLEN];
UInt32 length = sizeof(path);
MacOSError::check(SecKeychainGetPath(keychain, &length, path));
return path;
}
static SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match, SecPolicyRef policy);
SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match)
{
if (!strcmp(match, "-"))
return SecIdentityRef(kCFNull);
CFRef<SecIdentityRef> preference;
switch (OSStatus rc = SecIdentityCopyPreference(CFTempString(match), 0, NULL, &preference.aref())) {
case noErr:
if (preference)
return preference.yield();
break;
case errSecItemNotFound:
break;
default:
MacOSError::throwMe(rc);
}
CFRef<SecPolicyRef> policy;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
&CSSMOID_APPLE_TP_CODE_SIGNING, &policy.aref()));
if (SecIdentityRef identity = findIdentity(keychain, match, policy))
return identity;
MacOSError::check(SecPolicyCopy(CSSM_CERT_X_509v3,
&CSSMOID_APPLE_X509_BASIC, &policy.aref()));
if (SecIdentityRef identity = findIdentity(keychain, match, policy)) {
#if !defined(NDEBUG)
if (getenv("CODESIGN_ANYCERT")) {
note(1, "Using unqualified identity for test - signature will not verify");
return identity;
}
#endif //NDEBUG
CFRef<SecCertificateRef> cert;
MacOSError::check(SecIdentityCopyCertificate(identity, &cert.aref()));
CFRef<CFStringRef> name;
MacOSError::check(SecCertificateCopyCommonName(cert, &name.aref()));
fail("%s: this identity cannot be used for signing code", cfString(name).c_str());
}
fail("%s: no identity found", match);
}
static SecIdentityRef findIdentity(SecKeychainRef keychain, const char *match, SecPolicyRef policy)
{
CFRef<SecIdentitySearchRef> search;
MacOSError::check(SecIdentitySearchCreateWithPolicy(policy, NULL,
CSSM_KEYUSE_SIGN, keychain, false, &search.aref()));
CFRef<CFDataRef> certHash;
const char hexDigits[] = "0123456789abcdefABCDEF";
if (strlen(match) == 2 * SHA1::digestLength && strspn(match, hexDigits) == 2 * SHA1::digestLength) {
SHA1::Digest digest;
stringHash(match, digest);
certHash = CFDataCreate(NULL, digest, sizeof(digest));
}
CFRef<CFStringRef> cfmatch = makeCFString(match);
CFRef<SecIdentityRef> bestMatch;
CFRef<CFStringRef> bestMatchName;
bool exactMatch = false;
CSSM_DATA bestMatchData;
for (;;) {
CFRef<SecIdentityRef> candidate;
switch (OSStatus rc = SecIdentitySearchCopyNext(search, &candidate.aref())) {
case noErr:
{
CFRef<SecCertificateRef> cert;
MacOSError::check(SecIdentityCopyCertificate(candidate, &cert.aref()));
if (certHash) {
CFRef<CFDataRef> hash = certificateHash(cert);
if (CFEqual(hash, certHash))
return candidate.yield();
}
CFRef<CFStringRef> name;
CSSM_DATA data;
MacOSError::check(SecCertificateCopyCommonName(cert, &name.aref()));
MacOSError::check(SecCertificateGetData(cert, &data));
if (!strcmp(match, "*")) { note(1, "Using identity \"%s\"", cfString(name).c_str());
return candidate.yield();
}
if (!name) continue;
CFRange find = CFStringFind(name, cfmatch,
kCFCompareCaseInsensitive | kCFCompareNonliteral);
if (find.location == kCFNotFound)
continue; bool isExact = find.location == 0 && find.length == CFStringGetLength(name);
if (bestMatch) { if (exactMatch && !isExact) continue;
if (exactMatch == isExact) { if (bestMatchData.Length == data.Length
&& !memcmp(bestMatchData.Data, data.Data, data.Length)) { note(2, "%s: found in both %s and %s (this is all right)",
match, keychainPath(bestMatch).c_str(), keychainPath(cert).c_str());
continue;
}
string firstKeychain = keychainPath(bestMatch);
string newKeychain = keychainPath(cert);
if (firstKeychain == newKeychain)
fail("%s: ambiguous (matches \"%s\" and \"%s\" in %s)",
match, cfString(name).c_str(), cfString(bestMatchName).c_str(),
newKeychain.c_str());
else
fail("%s: ambiguous (matches \"%s\" in %s and \"%s\" in %s)",
match, cfString(name).c_str(), firstKeychain.c_str(),
cfString(bestMatchName).c_str(), newKeychain.c_str());
}
}
bestMatch = candidate;
bestMatchName = name;
bestMatchData = data;
exactMatch = isExact;
break;
}
case errSecItemNotFound:
return bestMatch.yield();
default:
MacOSError::check(rc);
}
}
}
uint32_t parseOptionTable(const char *arg, const SecCodeDirectoryFlagTable *options)
{
uint32_t flags = 0;
std::string text = arg;
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 = options; item->name; item++)
if (item->signable && !strncmp(word.c_str(), item->name, word.size())) {
flags |= item->value;
break;
}
if (!item->name) MacOSError::throwMe(errSecCSInvalidFlags);
if (comma == string::npos) break;
}
return flags;
}
uint32_t parseCdFlags(const char *arg)
{
if (isdigit(arg[0])) { char *remain;
uint32_t flags = strtol(arg, &remain, 0);
if (remain[0])
fail("%s: invalid flag(s)", arg);
return flags;
} else {
return parseOptionTable(arg, kSecCodeDirectoryFlagTable);
}
}
CF_EXPORT CFStringRef const kCFDateFormatterIsLenientKey;
CFDateRef parseDate(const char *string)
{
if (!string || !strcasecmp(string, "none"))
return CFDateRef(kCFNull);
CFRef<CFLocaleRef> userLocale = CFLocaleCopyCurrent();
CFRef<CFDateFormatterRef> formatter = CFDateFormatterCreate(NULL, userLocale,
kCFDateFormatterMediumStyle, kCFDateFormatterMediumStyle);
CFDateFormatterSetProperty(formatter, kCFDateFormatterIsLenientKey, kCFBooleanTrue);
CFRef<CFDateRef> date = CFDateFormatterCreateDateFromString(NULL, formatter, CFTempString(string), NULL);
if (!date)
fail("%s: invalid date/time", string);
return date.yield();
}
std::string cleanPath(const char *path)
{
char answer[PATH_MAX];
if (const char *r = realpath(path, answer))
return r;
perror(path);
exit(1);
}
std::string hashString(const SHA1::Byte *hash)
{
char s[2 * SHA1::digestLength + 1];
for (unsigned n = 0; n < SHA1::digestLength; n++)
sprintf(&s[2*n], "%2.2x", hash[n]);
return s;
}
std::string hashString(SHA1 &hash)
{
SHA1::Digest digest;
hash.finish(digest);
return hashString(digest);
}
void stringHash(const char *string, SHA1::Digest digest)
{
for (unsigned n = 0; n < SHA1::digestLength; n++)
sscanf(string+2*n, "%2hhx", digest+n);
}
CFDataRef certificateHash(SecCertificateRef cert)
{
CFRef<CFDataRef> certData = SecCertificateCopyData(cert);
SHA1 hash;
hash(CFDataGetBytePtr(certData), CFDataGetLength(certData));
SHA1::Digest digest;
hash.finish(digest);
return CFDataCreate(NULL, digest, sizeof(digest));
}
void fail(const char *format, ...)
{
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
if (continueOnError)
throw Fail(exitFailure);
else
exit(exitFailure);
}
void note(unsigned level, const char *format, ...)
{
if (verbose >= level) {
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
}
static void diagnose1(const char *type, CFTypeRef value);
static void diagnose1(const char *context, OSStatus rc);
void diagnose(const char *context , int stop )
{
try {
throw;
} catch (const ErrorCheck::Error &err) {
diagnose(context, err.error);
} catch (const MacOSError &err) {
diagnose1(context, err.osStatus());
} catch (const UnixError &err) {
errno = err.error;
perror(context);
} catch (const Fail &failure) {
} catch (...) {
if (context)
fprintf(stderr, "%s: ", context);
fprintf(stderr, "unknown exception\n");
}
if (stop)
exit(stop);
}
void diagnose(const char *context, CFErrorRef err)
{
diagnose(context, CFErrorGetCode(err), CFErrorCopyUserInfo(err));
}
static void diagnose1(const char *context, OSStatus rc)
{
if (rc >= errSecErrnoBase && rc < errSecErrnoLimit) {
errno = rc - errSecErrnoBase;
perror(context);
} else
cssmPerror(context, rc);
}
void diagnose(const char *context, OSStatus rc, CFDictionaryRef info)
{
diagnose1(context, rc);
if (CFTypeRef path = CFDictionaryGetValue(info, kSecCFErrorPath))
fprintf(stderr, "In subcomponent: %s\n", cfString(CFURLRef(path)).c_str());
if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorArchitecture))
fprintf(stderr, "In architecture: %s\n", cfString(CFStringRef(detail)).c_str());
if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorRequirementSyntax))
fprintf(stderr, "Requirement syntax error(s):\n%s", cfString(CFStringRef(detail)).c_str());
if (verbose) {
if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceAdded))
diagnose1("resource added", detail);
if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceAltered))
diagnose1("resource modified", detail);
if (CFTypeRef detail = CFDictionaryGetValue(info, kSecCFErrorResourceMissing))
diagnose1("resource missing", detail);
}
}
static void diagnose1(const char *type, CFTypeRef value)
{
if (CFGetTypeID(value) == CFArrayGetTypeID()) {
CFArrayRef array = CFArrayRef(value);
CFIndex size = CFArrayGetCount(array);
for (CFIndex n = 0; n < size; n++)
diagnose1(type, CFArrayGetValueAtIndex(array, n));
} else
printf("%s: %s\n", type, cfString(value, noErr).c_str());
}
void writeFileList(CFArrayRef list, const char *destination, const char *mode)
{
FILE *out;
if (!strcmp(destination, "-")) {
out = stdout;
} else if (!(out = fopen(destination, mode))) {
perror(destination);
exit(1);
}
CFIndex count = CFArrayGetCount(list);
for (CFIndex n = 0; n < count; n++)
fprintf(out, "%s\n", cfString(CFURLRef(CFArrayGetValueAtIndex(list, n))).c_str());
if (strcmp(destination, "-"))
fclose(out);
}
void writeDictionary(CFDictionaryRef dict, const char *destination, const char *mode)
{
FILE *out;
if (!strcmp(destination, "-")) {
out = stdout;
} else if (!(out = fopen(destination, mode))) {
perror(destination);
exit(1);
}
if (dict) {
CFRef<CFDataRef> data = makeCFData(dict);
fwrite(CFDataGetBytePtr(data), 1, CFDataGetLength(data), out);
}
if (strcmp(destination, "-"))
fclose(out);
}
void writeData(CFDataRef data, const char *destination, const char *mode)
{
FILE *out;
if (!strcmp(destination, "-")) {
out = stdout;
} else if (!(out = fopen(destination, mode))) {
perror(destination);
exit(1);
}
if (data)
fwrite(CFDataGetBytePtr(data), 1, CFDataGetLength(data), out);
if (strcmp(destination, "-"))
fclose(out);
}
SecStaticCodeRef staticCodePath(const char *target, const Architecture &arch, const char *version)
{
CFRef<CFMutableDictionaryRef> attributes;
if (arch || version) {
attributes = makeCFMutableDictionary();
if (arch)
cfadd(attributes, "{%O=%d,%O=%d}",
kSecCodeAttributeArchitecture, arch.cpuType(),
kSecCodeAttributeSubarchitecture, arch.cpuSubtype());;
if (version)
cfadd(attributes, "{%O=%s}", kSecCodeAttributeBundleVersion, version);
}
SecStaticCodeRef code;
MacOSError::check(SecStaticCodeCreateWithPathAndAttributes(CFTempURL(cleanPath(target)), kSecCSDefaultFlags,
attributes, &code));
return code;
}
static SecCodeRef descend(SecCodeRef host, CFRef<CFMutableDictionaryRef> attrs, string path);
static void parsePath(CFMutableDictionaryRef attrs, string form);
static void parseAttribute(CFMutableDictionaryRef attrs, string form);
SecCodeRef dynamicCodePath(const char *target)
{
if (!isdigit(target[0]))
return NULL;
char *path;
int pid = strtol(target, &path, 10);
return descend(NULL,
cfmake<CFMutableDictionaryRef>("{%O=%d}", kSecGuestAttributePid, pid),
path);
}
static SecCodeRef descend(SecCodeRef host, CFRef<CFMutableDictionaryRef> attrs, string path)
{
string::size_type colon = path.find(':');
if (colon == string::npos) parsePath(attrs, path);
else
parsePath(attrs, path.substr(0, colon));
CFRef<SecCodeRef> guest;
MacOSError::check(SecCodeCopyGuestWithAttributes(host, attrs, kSecCSDefaultFlags, &guest.aref()));
if (colon == string::npos)
return guest.yield();
else
return descend(guest, makeCFMutableDictionary(), path.substr(colon + 1));
}
static void parsePath(CFMutableDictionaryRef attrs, string form)
{
for (string::size_type comma = form.find(','); comma != string::npos; comma = form.find(',')) {
parseAttribute(attrs, form.substr(0, comma));
form = form.substr(comma + 1);
}
parseAttribute(attrs, form);
}
static void parseAttribute(CFMutableDictionaryRef attrs, string form)
{
if (form.empty()) return;
string::size_type eq = form.find('=');
CFRef<CFStringRef> key;
if (eq == string::npos) {
key = kSecGuestAttributeCanonical;
} else {
key.take(makeCFString(form.substr(0, eq)));
form = form.substr(eq + 1);
}
if (isdigit(form[0])) CFDictionaryAddValue(attrs, key, CFTempNumber(strtol(form.c_str(), NULL, 0)));
else CFDictionaryAddValue(attrs, key, CFTempString(form));
}