#include <TargetConditionals.h>
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
#include "SecurityCommands.h"
#include <AssertMacros.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecCMS.h>
#include <Security/SecPolicyPriv.h>
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonDigestSPI.h>
#include <utilities/SecCFRelease.h>
enum {
CSMAGIC_REQUIREMENT = 0xfade0c00,
CSMAGIC_REQUIREMENTS = 0xfade0c01,
CSMAGIC_CODEDIRECTORY = 0xfade0c02,
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0,
CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1,
CSSLOT_CODEDIRECTORY = 0,
};
typedef struct __BlobIndex {
uint32_t type;
uint32_t offset;
} CS_BlobIndex;
typedef struct __SuperBlob {
uint32_t magic;
uint32_t length;
uint32_t count;
CS_BlobIndex index[];
} CS_SuperBlob;
typedef struct __CodeDirectory {
uint32_t magic;
uint32_t length;
uint32_t version;
uint32_t flags;
uint32_t hashOffset;
uint32_t identOffset;
uint32_t nSpecialSlots;
uint32_t nCodeSlots;
uint32_t codeLimit;
uint8_t hashSize;
uint8_t hashType;
uint8_t spare1;
uint8_t pageSize;
uint32_t spare2;
} CS_CodeDirectory;
#if 0
static void debug_data(uint8_t *data, size_t length)
{
uint32_t i, j;
for (i = 0; i < length; i+=16) {
fprintf(stderr, "%p ", (void*)(data+i));
for (j = 0; (j < 16) && (j+i < length); j++) {
uint8_t byte = *(uint8_t*)(data+i+j);
fprintf(stderr, "%.02x %c|", byte, isprint(byte) ? byte : '?');
}
fprintf(stderr, "\n");
}
}
static void write_data(const char *path, uint8_t *data, size_t length)
{
int fd = open(path, O_RDWR|O_TRUNC|O_CREAT, 0644);
require(fd>0, out);
write(fd, data, length);
close(fd);
out:
return;
}
#endif
static void fprint_digest(FILE *file, unsigned char *digest, size_t length) {
size_t ix;
for (ix = 0; ix < length; ++ix) {
fprintf(file, "%02x", digest[ix]);
}
}
static CFMutableDictionaryRef lc_code_sig(uint8_t *lc_code_signature, size_t lc_code_signature_len)
{
CFMutableDictionaryRef code_signature =
CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
require(code_signature, out);
CS_SuperBlob *sb = (CS_SuperBlob*)lc_code_signature;
require(ntohl(sb->magic) == CSMAGIC_EMBEDDED_SIGNATURE, out);
uint32_t count;
for (count = 0; count < ntohl(sb->count); count++) {
uint32_t offset = ntohl(sb->index[count].offset);
uint8_t *bytes = lc_code_signature + offset;
uint32_t magic = ntohl(*(uint32_t*)bytes);
uint32_t length = ntohl(*(uint32_t*)(bytes+4));
switch(magic) {
case 0xfade0c01: break;
case 0xfade0c02: {
const CS_CodeDirectory *cd = (const CS_CodeDirectory *)bytes;
CFDataRef codedir = CFDataCreate(kCFAllocatorDefault, bytes, length);
require(codedir, out);
CFDictionarySetValue(code_signature, CFSTR("CodeDirectory"), codedir);
CFRelease(codedir);
require_string(ntohl(cd->version) >= 0x20001, out, "incompatible version");
require_string(ntohl(cd->version) <= 0x2F000, out, "incompatible version");
require_string(cd->hashSize == 20, out, "unexpected hash size");
require_string(cd->hashType == 1, out, "unexpected hash type");
uint32_t hash_offset = ntohl(cd->hashOffset);
uint32_t entitlement_slot = 5;
if (ntohl(cd->nSpecialSlots) >= entitlement_slot) {
CFDataRef message = CFDataCreate(kCFAllocatorDefault, bytes+hash_offset-entitlement_slot*cd->hashSize, cd->hashSize);
require(message, out);
CFDictionarySetValue(code_signature, CFSTR("EntitlementsCDHash"), message);
CFRelease(message);
} else
fprintf(stderr, "no entitlements slot yet\n");
}
break;
case 0xfade0b01: if (length != 8) {
CFDataRef message = CFDataCreate(kCFAllocatorDefault, bytes+8, length-8);
require(message, out);
CFDictionarySetValue(code_signature, CFSTR("SignedData"), message);
CFRelease(message);
}
break;
case 0xfade7171:
{
unsigned char digest[CC_SHA1_DIGEST_LENGTH];
CCDigest(kCCDigestSHA1, bytes, length, digest);
CFDataRef message = CFDataCreate(kCFAllocatorDefault, digest, sizeof(digest));
require(message, out);
CFDictionarySetValue(code_signature, CFSTR("EntitlementsHash"), message);
CFRelease(message);
message = CFDataCreate(kCFAllocatorDefault, bytes+8, length-8);
require(message, out);
CFDictionarySetValue(code_signature, CFSTR("Entitlements"), message);
CFRelease(message);
break;
}
default:
fprintf(stderr, "Skipping block with magic: 0x%x\n", magic);
break;
}
}
return code_signature;
out:
if (code_signature) CFRelease(code_signature);
return NULL;
}
static FILE *
open_bundle(const char * path, const char * mode)
{
char full_path[1024] = {};
CFStringRef path_cfstring = NULL;
CFURLRef path_url = NULL;
CFBundleRef bundle = NULL;
CFURLRef exec = NULL;
path_cfstring = CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, path);
require_quiet(path_cfstring, out);
path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstring, kCFURLPOSIXPathStyle, true);
require_quiet(path_url, out);
bundle = CFBundleCreate(kCFAllocatorDefault, path_url);
require_quiet(bundle, out);
exec = CFBundleCopyExecutableURL(bundle);
require(exec, out);
require(CFURLGetFileSystemRepresentation(exec, true, (uint8_t*)full_path, sizeof(full_path)), out);
out:
CFReleaseSafe(path_cfstring);
CFReleaseSafe(path_url);
CFReleaseSafe(bundle);
CFReleaseSafe(exec);
return fopen(full_path, "r");
}
static CFMutableDictionaryRef load_code_signature(FILE *binary, size_t slice_offset)
{
bool signature_found = false;
CFMutableDictionaryRef result = NULL;
struct load_command lc;
do {
require(1 == fread(&lc, sizeof(lc), 1, binary), out);
if (lc.cmd == LC_CODE_SIGNATURE) {
struct { uint32_t offset; uint32_t size; } sig;
require(1 == fread(&sig, sizeof(sig), 1, binary), out);
require_noerr(fseek(binary, slice_offset+sig.offset, SEEK_SET), out);
size_t length = sig.size;
uint8_t *data = malloc(length);
require(length && data, out);
require(1 == fread(data, length, 1, binary), out);
signature_found = true;
result = lc_code_sig(data, length);
free(data);
break;
}
require_noerr(fseek(binary, lc.cmdsize-sizeof(lc), SEEK_CUR), out);
} while(lc.cmd || lc.cmdsize);
out:
if (!signature_found)
fprintf(stderr, "No LC_CODE_SIGNATURE segment found\n");
return result;
}
static CF_RETURNS_RETAINED CFArrayRef load_code_signatures(const char *path)
{
bool fully_parsed_binary = false;
CFMutableDictionaryRef result = NULL;
CFMutableArrayRef results = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
FILE *binary = open_bundle(path, "r");
if (!binary) binary = fopen(path, "r");
require(binary, out);
struct mach_header header;
require(1 == fread(&header, sizeof(header), 1, binary), out);
if ((header.magic == MH_MAGIC) || (header.magic == MH_MAGIC_64)) {
if (header.magic == MH_MAGIC_64)
fseek(binary, sizeof(struct mach_header_64) - sizeof(struct mach_header), SEEK_CUR);
result = load_code_signature(binary, 0 );
require(result, out);
CFStringRef type = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("CPU type: (%d,%d)"), header.cputype, header.cpusubtype);
CFDictionarySetValue(result, CFSTR("ARCH"), type);
CFRelease(type);
CFArrayAppendValue(results, result);
}
else
{
struct fat_header fat;
require(!fseek(binary, 0L, SEEK_SET), out);
require(1 == fread(&fat, sizeof(fat), 1, binary), out);
require(ntohl(fat.magic) == FAT_MAGIC, out);
uint32_t slice, slices = ntohl(fat.nfat_arch);
struct fat_arch *archs = calloc(slices, sizeof(struct fat_arch));
require(slices == fread(archs, sizeof(struct fat_arch), slices, binary), out);
for (slice = 0; slice < slices; slice++) {
uint32_t slice_offset = ntohl(archs[slice].offset);
require(!fseek(binary, slice_offset, SEEK_SET), out);
require(1 == fread(&header, sizeof(header), 1, binary), out);
require((header.magic == MH_MAGIC) || (header.magic == MH_MAGIC_64), out);
if (header.magic == MH_MAGIC_64)
fseek(binary, sizeof(struct mach_header_64) - sizeof(struct mach_header), SEEK_CUR);
result = load_code_signature(binary, slice_offset);
require(result, out);
CFStringRef type = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("CPU type: (%d,%d)"), header.cputype, header.cpusubtype);
CFDictionarySetValue(result, CFSTR("ARCH"), type);
CFRelease(type);
CFArrayAppendValue(results, result);
CFRelease(result);
}
}
fully_parsed_binary = true;
out:
if (!fully_parsed_binary) {
if (results) {
CFRelease(results);
results = NULL;
}
}
if (binary)
fclose(binary);
return results;
}
extern int codesign_util(int argc, char * const *argv)
{
int result = 1, verbose = 0;
char ch;
while ((ch = getopt(argc, argv, "v")) != -1)
{
switch (ch)
{
case 'v':
verbose++;
break;
default:
return SHOW_USAGE_MESSAGE;
}
}
argc -= optind;
argv += optind;
if (argc != 1)
return SHOW_USAGE_MESSAGE;
CFArrayRef sigs = load_code_signatures(argv[0]);
require(sigs, out);
if (verbose >= 2)
CFShow(sigs);
CFIndex i, count = CFArrayGetCount(sigs);
for (i = 0; i < count; i++) {
CFDictionaryRef signature = CFArrayGetValueAtIndex(sigs, i);
CFDataRef code_dir = CFDictionaryGetValue(signature, CFSTR("CodeDirectory"));
const CS_CodeDirectory *cd = (CS_CodeDirectory *)CFDataGetBytePtr(code_dir);
CFDataRef signed_data = CFDictionaryGetValue(signature, CFSTR("SignedData"));
CFDataRef entitlements = CFDictionaryGetValue(signature, CFSTR("Entitlements"));
CFDataRef entitlements_cd_hash = CFDictionaryGetValue(signature, CFSTR("EntitlementsCDHash"));
CFDataRef entitlements_hash = CFDictionaryGetValue(signature, CFSTR("EntitlementsHash"));
CFStringRef arch = CFDictionaryGetValue(signature, CFSTR("ARCH"));
CFShow(arch);
SecPolicyRef policy = SecPolicyCreateiPhoneApplicationSigning();
if (signed_data) {
if (SecCMSVerify(signed_data, code_dir, policy, NULL, NULL)) {
fprintf(stderr, "Failed to verify signature\n");
result = -1;
} else
fprintf(stderr, "Signature ok\n");
} else
fprintf(stderr, "Ad-hoc signed binary\n");
if (entitlements_cd_hash) {
if (entitlements_hash && entitlements_cd_hash && CFEqual(entitlements_hash, entitlements_cd_hash))
fprintf(stderr, "Entitlements ok\n");
else
fprintf(stderr, "Entitlements modified\n");
}
if (verbose >= 2) {
fprintf(stderr, "magic: 0x%x length: %u(%lu)\n", ntohl(cd->magic), ntohl(cd->length), CFDataGetLength(code_dir));
fprintf(stderr, "code directory version/flags: 0x%x/0x%x special/code hash slots: %u/%u\n"
"codelimit: %u hash size/type: %u/%u hash/ident offset: %u/%u\n",
ntohl(cd->version), ntohl(cd->flags), ntohl(cd->nSpecialSlots), ntohl(cd->nCodeSlots),
ntohl(cd->codeLimit), cd->hashSize, cd->hashType, ntohl(cd->hashOffset), ntohl(cd->identOffset));
fprintf(stderr, "ident: '%s'\n", CFDataGetBytePtr(code_dir) + ntohl(cd->identOffset));
uint32_t ix;
uint8_t *hashes = (uint8_t *)CFDataGetBytePtr(code_dir) + ntohl(cd->hashOffset);
for (ix = 0; ix < ntohl(cd->nSpecialSlots); ++ix) {
fprint_digest(stderr, hashes, cd->hashSize);
fprintf(stderr, "\n");
hashes += cd->hashSize;
}
}
if (verbose >= 1) {
if (entitlements)
fprintf(stderr, "Entitlements\n%.*s", (int)CFDataGetLength(entitlements)-8, CFDataGetBytePtr(entitlements)+8);
}
if (verbose >= 2) {
if (entitlements_hash) {
fprintf(stderr, "digest: ");
fprint_digest(stderr, (uint8_t *)CFDataGetBytePtr(entitlements_hash), CC_SHA1_DIGEST_LENGTH);
fprintf(stderr, "\n");
}
}
CFReleaseNull(policy);
}
CFReleaseSafe(sigs);
return result;
out:
return -1;
}
#endif // TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR