aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCameron Katri <me@cameronkatri.com>2022-05-20 10:42:38 -0400
committerCameron Katri <me@cameronkatri.com>2022-05-20 10:42:38 -0400
commitaa035f73ce081b3f07247bd15860d72355a096b2 (patch)
tree170a83f5aa816f936df5ae906f20da3fb6b9404e
downloadtrustcache-aa035f73ce081b3f07247bd15860d72355a096b2.tar.gz
trustcache-aa035f73ce081b3f07247bd15860d72355a096b2.tar.zst
trustcache-aa035f73ce081b3f07247bd15860d72355a096b2.zip
Initial import
-rw-r--r--.gitignore4
-rw-r--r--LICENSE22
-rw-r--r--Makefile40
-rw-r--r--README.txt49
-rw-r--r--append.c131
-rw-r--r--cache_from_tree.c86
-rw-r--r--create.c130
-rw-r--r--info.c162
-rw-r--r--machoparse/LICENSE19
-rw-r--r--machoparse/cdhash.c455
-rw-r--r--machoparse/cdhash.h34
-rw-r--r--machoparse/cs_blobs.h248
-rw-r--r--machoparse/macho.h73
-rw-r--r--sort.c48
-rw-r--r--tc.1120
-rw-r--r--tc.c98
-rw-r--r--trustcache.h51
-rw-r--r--uuid/LICENSE27
-rw-r--r--uuid/copy.c45
-rw-r--r--uuid/gen_uuid.c51
-rw-r--r--uuid/pack.c69
-rw-r--r--uuid/parse.c79
-rw-r--r--uuid/unpack.c62
-rw-r--r--uuid/unparse.c60
-rw-r--r--uuid/uuid.h23
25 files changed, 2186 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4c3a795
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+a.out
+*.o
+*.bin
+tc
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9b3bd58
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2022 Cameron Katri. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..364764d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+OBJS = tc.o
+OBJS += append.o create.o info.o
+OBJS += machoparse/cdhash.o cache_from_tree.o sort.o
+OBJS += uuid/gen_uuid.o uuid/pack.o uuid/unpack.o uuid/parse.o uuid/unparse.o uuid/copy.o
+
+PREFIX ?= ~/.local
+BINDIR ?= $(PREFIX)/bin
+MANDIR ?= $(PREFIX)/share/man
+
+ifeq ($(shell uname -s),Darwin)
+ COMMONCRYPTO ?= 1
+endif
+
+ifeq ($(COMMONCRYPTO),1)
+ CFLAGS += -DCOMMONCRYPTO
+else
+ LIBS += -lcrypto
+endif
+
+all: tc
+
+install: tc tc.1
+ install -d $(BINDIR)
+ install -m 755 tc $(BINDIR)/
+ install -d $(MANDIR)/man1/
+ install -m 644 tc.1 $(MANDIR)/man1/
+
+uninstall:
+ rm -i $(BINDIR)/tc $(MANDIR)/man1/tc.1
+
+tc: $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) -o $@ $(LIBS)
+
+README.txt: tc.1
+ mandoc $^ | col -bx > $@
+
+clean:
+ rm -f tc $(OBJS)
+
+.PHONY: all clean install uninstall
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..2e55a09
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,49 @@
+TC(1) General Commands Manual TC(1)
+
+NAME
+ tc – Create and interact with trustcaches
+
+SYNOPSIS
+ tc append [-u uuid | 0] infile file ...
+ tc create [-u uuid] [-v version] outfile file ...
+ tc info [-c] [-h] [-e entrynum] file
+
+DESCRIPTION
+ The tc utility is used to get info about and modify Apple trustcaches.
+
+ The following commands are supported by tc:
+
+ append [-u uuid | 0] infile file ...
+ Modify the trustcache at infile to include each signed Mach-O in
+ the specified path. uuid is used to specify a custom uuid to be
+ used. If it is 0, the uuid will be left the same, otherwise, it
+ will be regenerated.
+
+ create [-u uuid] [-v version] outfile file ...
+ Create a trustcache at outfile. Each Mach-O found in the
+ specified inputs will be scanned for a code signature and hashed.
+ Any malformed or unsigned Mach-O will be ignored. Each slice of
+ a FAT binary will have its hash included. Versions 0 and 1 are
+ supported, if not specified, 1 is assumed. If uuid is specified,
+ that will be used instead of a randomly generated one.
+
+ info [-c] [-h] [-e entrynum] file
+ Print information about file. The output for each hash will be
+ in the format:
+
+ <cdhash> <flags> [<hash_type>]
+
+ If the -c is given, only the hashes will be printed. If -h is
+ given, only the header will be printed. If entrynum is
+ specified, only that entry will be printed.
+
+EXIT STATUS
+ The tc utility exits 0 on success, and >0 if an error occurs.
+
+SEE ALSO
+ cryptex-dump-trust-cache(1), cryptex-generate-trust-cache(1)
+
+HISTORY
+ The tc utility was written by Cameron Katri <me@cameronkatri.com>.
+
+FreeBSD 14.0-CURRENT May 19, 2022 FreeBSD 14.0-CURRENT
diff --git a/append.c b/append.c
new file mode 100644
index 0000000..475200e
--- /dev/null
+++ b/append.c
@@ -0,0 +1,131 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "trustcache.h"
+#include "uuid/uuid.h"
+
+int
+tcappend(int argc, char **argv)
+{
+ if (argc < 3)
+ return -1;
+
+ int keepuuid = 0;
+ uuid_t uuid;
+
+ int ch;
+ while ((ch = getopt(argc, argv, "u:")) != -1) {
+ switch (ch) {
+ case 'u':
+ if (strlen(optarg) == 1 && *optarg == '0') {
+ keepuuid = 1;
+ } else {
+ if (uuid_parse(optarg, uuid) != 0) {
+ fprintf(stderr, "Failed to parse %s as a UUID\n", optarg);
+ } else
+ keepuuid = 2;
+ }
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ FILE *f = NULL;
+ struct trust_cache cache = opentrustcache(argv[0]);
+ struct trust_cache append = {
+ .version = cache.version,
+ .num_entries = 0
+ };
+
+ for (int i = 1; i < argc; i++) {
+ append = cache_from_tree(argv[i], cache.version);
+ if (append.version == 0) {
+ if ((cache.hashes = realloc(cache.hashes, sizeof(trust_cache_hash0) *
+ (cache.num_entries + append.num_entries))) == NULL)
+ exit(1);
+ for (int j = 0; j < append.num_entries; j++) {
+ memcpy(cache.hashes[cache.num_entries + j], append.hashes[j], CS_CDHASH_LEN);
+ }
+ } else if (append.version == 1) {
+ if ((cache.entries = realloc(cache.entries, sizeof(struct trust_cache_entry1) *
+ (cache.num_entries + append.num_entries))) == NULL)
+ exit(1);
+ for (int j = 0; j < append.num_entries; j++) {
+ cache.entries[cache.num_entries + j].hash_type = append.entries[j].hash_type;
+ cache.entries[cache.num_entries + j].flags = append.entries[j].flags;
+ memcpy(cache.entries[cache.num_entries + j].cdhash, append.entries[j].cdhash, CS_CDHASH_LEN);
+ }
+ }
+ free(append.hashes);
+ cache.num_entries += append.num_entries;
+ }
+
+ if (cache.version == 1)
+ mergesort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
+ else if (cache.version == 0)
+ mergesort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp);
+
+ switch (keepuuid) {
+ case 0:
+ uuid_generate(cache.uuid);
+ break;
+ case 1:
+ break;
+ case 2:
+ uuid_copy(cache.uuid, uuid);
+ break;
+ }
+
+ if ((f = fopen(argv[0], "wb")) == NULL) {
+ fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
+ return 1;
+ }
+
+ cache.version = htole32(cache.version);
+ cache.num_entries = htole32(cache.num_entries);
+ fwrite(&cache, sizeof(struct trust_cache) - sizeof(struct trust_cache_entry1*), 1, f);
+ cache.version = le32toh(cache.version);
+ cache.num_entries = le32toh(cache.num_entries);
+
+ for (int i = 0; i < cache.num_entries; i++) {
+ if (cache.version == 0)
+ fwrite(&cache.hashes[i], sizeof(trust_cache_hash0), 1, f);
+ else if (cache.version == 1)
+ fwrite(&cache.entries[i], sizeof(struct trust_cache_entry1), 1, f);
+ }
+
+ return 0;
+}
diff --git a/cache_from_tree.c b/cache_from_tree.c
new file mode 100644
index 0000000..92a2210
--- /dev/null
+++ b/cache_from_tree.c
@@ -0,0 +1,86 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ftw.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "trustcache.h"
+#include "machoparse/cdhash.h"
+
+static struct trust_cache cache = {};
+
+int
+tccallback(const char *path, const struct stat *sb, int typeflag, struct FTW *ftw)
+{
+ if (!S_ISREG(sb->st_mode))
+ return 0;
+
+ struct cdhashes c = {};
+ c.count = 0;
+ find_cdhash(path, sb, &c);
+
+ if (c.count == 0)
+ return 0;
+
+ for (int i = 0; i < c.count; i++) {
+ if (cache.version == 0) {
+ if ((cache.hashes = realloc(cache.hashes, sizeof(trust_cache_hash0) * (cache.num_entries + 1))) == NULL)
+ exit(1);
+ memcpy(cache.hashes[cache.num_entries], c.h[i].cdhash, CS_CDHASH_LEN);
+ } else if (cache.version == 1) {
+ if ((cache.entries = realloc(cache.entries, sizeof(struct trust_cache_entry1) * (cache.num_entries + 1))) == NULL)
+ exit(1);
+ cache.entries[cache.num_entries].hash_type = c.h[i].hash_type;
+ cache.entries[cache.num_entries].flags = 0;
+ memcpy(cache.entries[cache.num_entries].cdhash, c.h[i].cdhash, CS_CDHASH_LEN);
+ }
+ cache.num_entries++;
+ }
+
+ free(c.h);
+
+ return 0;
+}
+
+struct trust_cache
+cache_from_tree(const char *path, uint32_t version)
+{
+ struct trust_cache ret = {};
+ cache.version = version;
+ cache.num_entries = 0;
+ ret.version = version;
+
+ if (nftw(path, tccallback, 20, 0) == -1) {
+ perror("nftw");
+ return ret;
+ }
+
+ ret.num_entries = cache.num_entries;
+ ret.hashes = cache.hashes;
+ return ret;
+}
diff --git a/create.c b/create.c
new file mode 100644
index 0000000..a19d7f3
--- /dev/null
+++ b/create.c
@@ -0,0 +1,130 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "trustcache.h"
+#include "uuid/uuid.h"
+
+int
+tccreate(int argc, char **argv)
+{
+ struct trust_cache cache = {
+ .version = 1,
+ .uuid = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
+ .num_entries = 0,
+ .entries = NULL,
+ }, append = {};
+
+ uuid_generate(cache.uuid);
+
+ int ch;
+ while ((ch = getopt(argc, argv, "u:v:")) != -1) {
+ switch (ch) {
+ case 'u':
+ if (uuid_parse(optarg, cache.uuid) != 0)
+ fprintf(stderr, "Failed to parse %s as a UUID\n", optarg);
+ break;
+ case 'v':
+ if (strlen(optarg) != 1 || (optarg[0] != '0' && optarg[0] != '1')) {
+ fprintf(stderr, "Unsupported trustcache version %s\n", optarg);
+ return 1;
+ }
+ if (optarg[0] == '0')
+ cache.version = 0;
+ else if (optarg[0] == '1')
+ cache.version = 1;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ return -1;
+ }
+
+ for (int i = 1; i < argc; i++) {
+ append = cache_from_tree(argv[i], cache.version);
+ if (append.version == 0) {
+ if ((cache.hashes = realloc(cache.hashes, sizeof(trust_cache_hash0) *
+ (cache.num_entries + append.num_entries))) == NULL)
+ exit(1);
+ for (int j = 0; j < append.num_entries; j++) {
+ memcpy(cache.hashes[cache.num_entries + j], append.hashes[j], CS_CDHASH_LEN);
+ }
+ } else if (append.version == 1) {
+ if ((cache.entries = realloc(cache.entries, sizeof(struct trust_cache_entry1) *
+ (cache.num_entries + append.num_entries))) == NULL)
+ exit(1);
+ for (int j = 0; j < append.num_entries; j++) {
+ cache.entries[cache.num_entries + j].hash_type = append.entries[j].hash_type;
+ cache.entries[cache.num_entries + j].flags = append.entries[j].flags;
+ memcpy(cache.entries[cache.num_entries + j].cdhash, append.entries[j].cdhash, CS_CDHASH_LEN);
+ }
+ }
+ free(append.hashes);
+ cache.num_entries += append.num_entries;
+ }
+
+ if (cache.version == 1)
+ mergesort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
+ else if (cache.version == 0)
+ mergesort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp);
+
+ FILE *f = NULL;
+ if ((f = fopen(argv[0], "wb")) == NULL) {
+ fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
+ return 1;
+ }
+
+ cache.version = htole32(cache.version);
+ cache.num_entries = htole32(cache.num_entries);
+ fwrite(&cache, sizeof(struct trust_cache) - sizeof(struct trust_cache_entry1*), 1, f);
+ cache.version = le32toh(cache.version);
+ cache.num_entries = le32toh(cache.num_entries);
+
+ for (int i = 0; i < cache.num_entries; i++) {
+ if (cache.version == 1)
+ fwrite(&cache.entries[i], sizeof(struct trust_cache_entry1), 1, f);
+ else if (cache.version == 0)
+ fwrite(&cache.hashes[i], sizeof(trust_cache_hash0), 1, f);
+ }
+
+ fclose(f);
+
+ free(cache.entries);
+
+ return 0;
+}
diff --git a/info.c b/info.c
new file mode 100644
index 0000000..c676586
--- /dev/null
+++ b/info.c
@@ -0,0 +1,162 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "trustcache.h"
+
+void print_header(struct trust_cache cache);
+void print_hash(uint8_t cdhash[CS_CDHASH_LEN], bool newline);
+void print_entry(struct trust_cache_entry1 entry);
+void print_entries(struct trust_cache cache);
+
+int
+tcinfo(int argc, char **argv)
+{
+ struct trust_cache cache;
+ bool headeronly = false, onlyhash = false;
+ int entrynum = -1;
+ const char *errstr = NULL;
+
+ int ch;
+ while ((ch = getopt(argc, argv, "che:")) != -1) {
+ switch (ch) {
+ case 'h':
+ headeronly = true;
+ break;
+ case 'e':
+ entrynum = strtonum(optarg, 1, INT_MAX, &errstr);
+ if (errstr != NULL) {
+ fprintf(stderr, "entry number is %s: %s\n", errstr, optarg);
+ exit(1);
+ }
+ break;
+ case 'c':
+ onlyhash = true;
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ return -1;
+ }
+
+ cache = opentrustcache(argv[0]);
+
+ if (entrynum == -1 && !onlyhash)
+ print_header(cache);
+ if (!headeronly) {
+ if (onlyhash) {
+ for (int i = 0; i < cache.num_entries; i++) {
+ if (cache.version == 0)
+ print_hash(cache.hashes[i], true);
+ else
+ print_hash(cache.entries[i].cdhash, true);
+ }
+ goto done;
+ }
+ if (entrynum != -1) {
+ if (entrynum > cache.num_entries) {
+ fprintf(stderr, "no entry %i\n", entrynum);
+ exit(1);
+ }
+ if (cache.version == 1) {
+ print_entry(cache.entries[entrynum - 1]);
+ } else if (cache.version == 0) {
+ print_hash(cache.hashes[entrynum - 1], true);
+ }
+ } else {
+ print_entries(cache);
+ }
+ }
+
+done:
+ free(cache.entries);
+
+ return 0;
+}
+
+void
+print_header(struct trust_cache cache)
+{
+ printf("version = %i\n", cache.version);
+ char out[37];
+ uuid_unparse(cache.uuid, out);
+ printf("uuid = %s\n", out);
+ printf("entry count = %i\n", cache.num_entries);
+}
+
+void
+print_entries(struct trust_cache cache)
+{
+ for (int i = 0; i < cache.num_entries; i++) {
+ if (cache.version == 0)
+ print_hash(cache.hashes[i], true);
+ else if (cache.version == 1)
+ print_entry(cache.entries[i]);
+ }
+}
+
+void
+print_entry(struct trust_cache_entry1 entry)
+{
+ print_hash(entry.cdhash, false);
+
+ switch (entry.flags) {
+ case CS_TRUST_CACHE_AMFID:
+ printf(" CS_TRUST_CACHE_AMFID ");
+ break;
+ case CS_TRUST_CACHE_ANE:
+ printf(" CS_TRUST_CACHE_ANE ");
+ break;
+ case CS_TRUST_CACHE_AMFID|CS_TRUST_CACHE_ANE:
+ printf(" CS_TRUST_CACHE_AMFID|CS_TRUST_CACHE_ANE ");
+ break;
+ default:
+ printf(" [none] ");
+ break;
+ }
+
+ printf("[%i]\n", entry.hash_type);
+}
+
+void
+print_hash(uint8_t cdhash[CS_CDHASH_LEN], bool newline)
+{
+ for (int j = 0; j < CS_CDHASH_LEN; j++) {
+ printf("%02x", cdhash[j]);
+ }
+ if (newline)
+ printf("\n");
+}
diff --git a/machoparse/LICENSE b/machoparse/LICENSE
new file mode 100644
index 0000000..41a58c3
--- /dev/null
+++ b/machoparse/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018 Brandon Azad
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/machoparse/cdhash.c b/machoparse/cdhash.c
new file mode 100644
index 0000000..8f7796e
--- /dev/null
+++ b/machoparse/cdhash.c
@@ -0,0 +1,455 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2018 Brandon Azad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/*
+ * This is forked from https://github.com/bazad/blanket/blob/master/amfidupe/cdhash.c
+ *
+ * Notable changes:
+ * 1. 32bit binary support
+ * 2. Endianness handling
+ * 3. FAT support
+ */
+
+/*
+ * Cdhash computation
+ * ------------------
+ *
+ * The amfid patch needs to be able to compute the cdhash of a binary.
+ * This code is heavily based on the implementation in Ian Beer's triple_fetch project [1] and on
+ * the source of XNU [2].
+ *
+ * [1]: https://bugs.chromium.org/p/project-zero/issues/detail?id=1247
+ * [2]: https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/kern/ubc_subr.c.auto.html
+ *
+ */
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#if COMMONCRYPTO
+# include <CommonCrypto/CommonCrypto.h>
+# define SHA1 CC_SHA1
+# define SHA_DIGEST_LENGTH CC_SHA1_DIGEST_LENGTH
+# define SHA256 CC_SHA256
+# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
+# define SHA384 CC_SHA384
+# define SHA384_DIGEST_LENGTH CC_SHA384_DIGEST_LENGTH
+#else
+# include <openssl/sha.h>
+#endif
+
+#if __APPLE__
+# include <libkern/OSByteOrder.h>
+# define bswap32(x) OSSwapInt32(x)
+# define be32toh(x) OSSwapBigToHostInt32(x)
+#elif __has_include(<endian.h>)
+# include <endian.h>
+#else
+# include <sys/endian.h>
+#endif
+
+#include "cdhash.h"
+#include "cs_blobs.h"
+#include "macho.h"
+
+#define ERROR(x, ...)
+#define DEBUG_TRACE(x, y, ...)
+
+static uint32_t
+swap(const void *h, const void *h2, const uint32_t s) {
+ uint32_t magic = *((uint32_t*)(h == NULL ? h2 : h));
+ if (magic == MH_CIGAM || magic == MH_CIGAM_64)
+ return bswap32(s);
+ else
+ return s;
+}
+
+// Check whether the file looks like a Mach-O file.
+static bool
+macho_identify(const struct mach_header_64 *mh, const struct mach_header *mh32, size_t size) {
+ uint32_t magic = mh != NULL ? mh->magic : mh32->magic;
+ // Check the file size and magic.
+ if (size < 0x1000 || (magic != MH_MAGIC_64 &&
+ magic != MH_CIGAM_64 && magic != MH_MAGIC &&
+ magic != MH_CIGAM)) {
+ return false;
+ }
+ return true;
+}
+
+// Get the next load command in a Mach-O file.
+static const void *
+macho_next_load_command(const struct mach_header_64 *mh, const struct mach_header *mh32, size_t size, const void *lc) {
+ const struct load_command *next = lc;
+
+ if (next == NULL) {
+ if (mh != NULL)
+ next = (const struct load_command *)(mh + 1);
+ else
+ next = (const struct load_command *)(mh32 + 1);
+ } else {
+ next = (const struct load_command *)((uint8_t *)next + swap(mh, mh32, next->cmdsize));
+ }
+ if (mh != NULL) {
+ if ((uintptr_t)next >= (uintptr_t)(mh + 1) + swap(mh, mh32, mh->sizeofcmds)) {
+ next = NULL;
+ }
+ } else {
+ if ((uintptr_t)next >= (uintptr_t)(mh32 + 1) + swap(mh, mh32, mh32->sizeofcmds)) {
+ next = NULL;
+ }
+ }
+ return next;
+}
+
+// Find the next load command in a Mach-O file matching the given type.
+static const void *
+macho_find_load_command(const struct mach_header_64 *mh, const struct mach_header *mh32, size_t size,
+ uint32_t command, const void *lc) {
+ const struct load_command *loadcmd = lc;
+ for (;;) {
+ loadcmd = macho_next_load_command(mh, mh32, size, loadcmd);
+ if (loadcmd == NULL || swap(mh, mh32, loadcmd->cmd) == command) {
+ return loadcmd;
+ }
+ }
+}
+
+// Validate a CS_CodeDirectory and return its true length.
+static size_t
+cs_codedirectory_validate(CS_CodeDirectory *cd, size_t size) {
+ // Make sure we at least have a CS_CodeDirectory. There's an end_earliest parameter, but
+ // XNU doesn't seem to use it in cs_validate_codedirectory().
+ if (size < sizeof(*cd)) {
+ ERROR("CS_CodeDirectory is too small\n");
+ return 0;
+ }
+ // Validate the magic.
+ uint32_t magic = be32toh(cd->magic);
+ if (magic != CSMAGIC_CODEDIRECTORY) {
+ ERROR("CS_CodeDirectory has incorrect magic\n");
+ return 0;
+ }
+ // Validate the length.
+ uint32_t length = be32toh(cd->length);
+ if (length > size) {
+ ERROR("CS_CodeDirectory has invalid length\n");
+ return 0;
+ }
+ return length;
+}
+
+// Validate a CS_SuperBlob and return its true length.
+static size_t
+cs_superblob_validate(CS_SuperBlob *sb, size_t size) {
+ // Make sure we at least have a CS_SuperBlob.
+ if (size < sizeof(*sb)) {
+ ERROR("CS_SuperBlob is too small\n");
+ return 0;
+ }
+ // Validate the magic.
+ uint32_t magic = be32toh(sb->magic);
+ if (magic != CSMAGIC_EMBEDDED_SIGNATURE) {
+ ERROR("CS_SuperBlob has incorrect magic\n");
+ return 0;
+ }
+ // Validate the length.
+ uint32_t length = be32toh(sb->length);
+ if (length > size) {
+ ERROR("CS_SuperBlob has invalid length\n");
+ return 0;
+ }
+ uint32_t count = be32toh(sb->count);
+ // Validate the count.
+ CS_BlobIndex *index = &sb->index[count];
+ if (count >= 0x10000 || (uintptr_t)index > (uintptr_t)sb + size) {
+ ERROR("CS_SuperBlob has invalid count\n");
+ return 0;
+ }
+ return length;
+}
+
+// Compute the cdhash of a code directory using SHA1.
+static void
+cdhash_sha1(CS_CodeDirectory *cd, size_t length, void *cdhash) {
+ uint8_t digest[SHA_DIGEST_LENGTH];
+ SHA1((void*)cd, length, digest);
+ memcpy(cdhash, digest, CS_CDHASH_LEN);
+}
+
+// Compute the cdhash of a code directory using SHA256.
+static void
+cdhash_sha256(CS_CodeDirectory *cd, size_t length, void *cdhash) {
+ uint8_t digest[SHA256_DIGEST_LENGTH];
+ SHA256((void*)cd, length, digest);
+ memcpy(cdhash, digest, CS_CDHASH_LEN);
+}
+
+// Compute the cdhash of a code directory using SHA384.
+static void
+cdhash_sha384(CS_CodeDirectory *cd, size_t length, void *cdhash) {
+ uint8_t digest[SHA384_DIGEST_LENGTH];
+ SHA384((void*)cd, length, digest);
+ memcpy(cdhash, digest, CS_CDHASH_LEN);
+}
+
+// Compute the cdhash from a CS_CodeDirectory.
+static bool
+cs_codedirectory_cdhash(CS_CodeDirectory *cd, size_t size, struct hashes *cdhash) {
+ size_t length = be32toh(cd->length);
+ switch (cd->hashType) {
+ case CS_HASHTYPE_SHA1:
+ DEBUG_TRACE(2, "Using SHA1\n");
+ cdhash_sha1(cd, length, cdhash->cdhash);
+ cdhash->hash_type = CS_HASHTYPE_SHA1;
+ return true;
+ case CS_HASHTYPE_SHA256:
+ DEBUG_TRACE(2, "Using SHA256\n");
+ cdhash_sha256(cd, length, cdhash->cdhash);
+ cdhash->hash_type = CS_HASHTYPE_SHA256;
+ return true;
+ case CS_HASHTYPE_SHA384:
+ DEBUG_TRACE(2, "Using SHA384\n");
+ cdhash_sha384(cd, length, cdhash->cdhash);
+ cdhash->hash_type = CS_HASHTYPE_SHA384;
+ return true;
+ }
+ ERROR("Unsupported hash type %d\n", cd->hashType);
+ return false;
+}
+
+// Get the rank of a code directory.
+static unsigned
+cs_codedirectory_rank(CS_CodeDirectory *cd) {
+ // The supported hash types, ranked from least to most preferred. From XNU's
+ // bsd/kern/ubc_subr.c.
+ static uint32_t ranked_hash_types[] = {
+ CS_HASHTYPE_SHA1,
+ CS_HASHTYPE_SHA256_TRUNCATED,
+ CS_HASHTYPE_SHA256,
+ CS_HASHTYPE_SHA384,
+ };
+ // Define the rank of the code directory as its index in the array plus one.
+ for (unsigned i = 0; i < sizeof(ranked_hash_types) / sizeof(ranked_hash_types[0]); i++) {
+ if (ranked_hash_types[i] == cd->hashType) {
+ return (i + 1);
+ }
+ }
+ return 0;
+}
+
+// Compute the cdhash from a CS_SuperBlob.
+static bool
+cs_superblob_cdhash(CS_SuperBlob *sb, size_t size, void *cdhash) {
+ // Iterate through each index searching for the best code directory.
+ CS_CodeDirectory *best_cd = NULL;
+ unsigned best_cd_rank = 0;
+ size_t best_cd_size = 0;
+ uint32_t count = be32toh(sb->count);
+ for (size_t i = 0; i < count; i++) {
+ CS_BlobIndex *index = &sb->index[i];
+ uint32_t type = be32toh(index->type);
+ uint32_t offset = be32toh(index->offset);
+ // Validate the offset.
+ if (offset > size) {
+ ERROR("CS_SuperBlob has out-of-bounds CS_BlobIndex\n");
+ return false;
+ }
+ // Look for a code directory.
+ if (type == CSSLOT_CODEDIRECTORY ||
+ (CSSLOT_ALTERNATE_CODEDIRECTORIES <= type && type < CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT)) {
+ CS_CodeDirectory *cd = (CS_CodeDirectory *)((uint8_t *)sb + offset);
+ size_t cd_size = cs_codedirectory_validate(cd, size - offset);
+ if (cd_size == 0) {
+ return false;
+ }
+ DEBUG_TRACE(2, "CS_CodeDirectory { hashType = %u }\n", cd->hashType);
+ // Rank the code directory to see if it's better than our previous best.
+ unsigned cd_rank = cs_codedirectory_rank(cd);
+ if (cd_rank > best_cd_rank) {
+ best_cd = cd;
+ best_cd_rank = cd_rank;
+ best_cd_size = cd_size;
+ }
+ }
+ }
+ // If we didn't find a code directory, error.
+ if (best_cd == NULL) {
+ ERROR("CS_SuperBlob does not have a code directory\n");
+ return false;
+ }
+ // Hash the code directory.
+ return cs_codedirectory_cdhash(best_cd, best_cd_size, cdhash);
+}
+
+// Compute the cdhash from a csblob.
+static bool
+csblob_cdhash(CS_GenericBlob *blob, size_t size, void *cdhash) {
+ // Make sure we at least have a CS_GenericBlob.
+ if (size < sizeof(*blob)) {
+ ERROR("CSBlob is too small\n");
+ return false;
+ }
+ uint32_t magic = be32toh(blob->magic);
+ uint32_t length = be32toh(blob->length);
+ DEBUG_TRACE(2, "CS_GenericBlob { %08x, %u }, size = %zu\n", magic, length, size);
+ // Make sure the length is sensible.
+ if (length > size) {
+ ERROR("CSBlob has invalid length\n");
+ return false;
+ }
+ // Handle the blob.
+ bool ok;
+ switch (magic) {
+ case CSMAGIC_EMBEDDED_SIGNATURE:
+ ok = cs_superblob_validate((CS_SuperBlob *)blob, length);
+ if (!ok) {
+ return false;
+ }
+ return cs_superblob_cdhash((CS_SuperBlob *)blob, length, cdhash);
+ case CSMAGIC_CODEDIRECTORY:
+ ok = cs_codedirectory_validate((CS_CodeDirectory *)blob, length);
+ if (!ok) {
+ return false;
+ }
+ return cs_codedirectory_cdhash((CS_CodeDirectory *)blob, length, cdhash);
+ }
+ ERROR("Unrecognized CSBlob magic 0x%08x\n", magic);
+ return false;
+}
+
+// Compute the cdhash for a Mach-O file.
+static bool
+compute_cdhash_macho(const struct mach_header_64 *mh, const struct mach_header *mh32, size_t size, struct hashes *cdhash) {
+ // Find the code signature command.
+ const struct linkedit_data_command *cs_cmd =
+ macho_find_load_command(mh, mh32, size, LC_CODE_SIGNATURE, NULL);
+ if (cs_cmd == NULL) {
+ ERROR("No code signature\n");
+ return false;
+ }
+ const uint8_t *cs_data, *cs_end;
+ // Check that the code signature is in-bounds.
+ if (mh != NULL) {
+ cs_data = (const uint8_t *)mh + swap(mh, NULL, cs_cmd->dataoff);
+ cs_end = cs_data + swap(mh, NULL, cs_cmd->datasize);
+ if (!((uint8_t *)mh < cs_data && cs_data < cs_end && cs_end <= (uint8_t *)mh + size)) {
+ ERROR("Invalid code signature\n");
+ return false;
+ }
+ } else {
+ cs_data = (const uint8_t *)mh32 + swap(mh32, NULL, cs_cmd->dataoff);
+ cs_end = cs_data + swap(mh32, NULL, cs_cmd->datasize);
+ if (!((uint8_t *)mh32 < cs_data && cs_data < cs_end && cs_end <= (uint8_t *)mh32 + size)) {
+ ERROR("Invalid code signature\n");
+ return false;
+ }
+ }
+ // Check that the code signature data looks correct.
+ return csblob_cdhash((CS_GenericBlob *)cs_data, cs_end - cs_data, cdhash);
+}
+
+bool
+compute_cdhash(const void *file, size_t size, struct hashes *cdhash) {
+ // Try to compute the cdhash for a Mach-O file.
+ const struct mach_header_64 *mh = file;
+ const struct mach_header *mh32 = file;
+ if (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64) {
+ mh32 = NULL;
+ } else {
+ mh = NULL;
+ }
+
+ if (macho_identify(mh, mh32, size)) {
+ //if (!macho_validate(mh, mh32, size)) {
+ // ERROR("Bad Mach-O file\n");
+ // return false;
+ //}
+ return compute_cdhash_macho(mh, mh32, size, cdhash);
+ }
+ // What is it?
+ ERROR("Unrecognized file format\n");
+ return false;
+}
+
+void
+compute_cdhashes(const void *file, size_t size, struct cdhashes *h) {
+ const struct fat_header *fh = NULL;
+ if (*((uint32_t*)file) == FAT_MAGIC || *((uint32_t*)file) == FAT_CIGAM)
+ fh = file;
+
+ if (fh != NULL) {
+ struct fat_arch *fa = (struct fat_arch *)(fh + 1);
+ h->h = malloc(sizeof(struct hashes) * be32toh(fh->nfat_arch));
+ for (uint32_t i = 0; i < be32toh(fh->nfat_arch); i++) {
+ if (compute_cdhash(file + be32toh(fa->offset), be32toh(fa->size), &h->h[h->count])) {
+ h->count++;
+ } else {
+ // If any slice is not signed we will just skip the whole binary
+ h->count = 0;
+ return;
+ }
+ fa++;
+ }
+ } else {
+ h->h = malloc(sizeof(struct hashes));
+ h->count = compute_cdhash(file, size, &h->h[0]);
+ }
+}
+
+int
+find_cdhash(const char *path, const struct stat *sb, struct cdhashes *h) {
+ int success = 0;
+ size_t fileoff = 0;
+
+ int fd;
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ ERROR("Could not open \"%s\"\n", path);
+ goto fail_0;
+ }
+ size_t size = sb->st_size;
+ // Map the file into memory.
+ DEBUG_TRACE(2, "Mapping %s size %zu offset %zu\n", path, size, fileoff);
+ size -= fileoff;
+ uint8_t *file = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, fileoff);
+ if (file == MAP_FAILED) {
+ ERROR("Could not map \"%s\"\n", path);
+ goto fail_1;
+ }
+ DEBUG_TRACE(3, "file[0] = %lx\n", *(uint64_t *)file);
+ // Compute the cdhash.
+ compute_cdhashes(file, size, h);
+ success = true;
+
+ munmap(file, size);
+fail_1:
+ close(fd);
+fail_0:
+ return success;
+}
diff --git a/machoparse/cdhash.h b/machoparse/cdhash.h
new file mode 100644
index 0000000..6cc559a
--- /dev/null
+++ b/machoparse/cdhash.h
@@ -0,0 +1,34 @@
+#ifndef BLANKET__AMFID__CDHASH_H_
+#define BLANKET__AMFID__CDHASH_H_
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include "cs_blobs.h"
+
+struct hashes {
+ uint32_t hash_type;
+ uint8_t cdhash[20];
+};
+
+struct cdhashes {
+ int count;
+ struct hashes *h;
+};
+
+/*
+ * compute_cdhash
+ *
+ * Description:
+ * Compute the cdhash of a Mach-O file.
+ *
+ * Parameters:
+ * file The contents of the Mach-O file.
+ * size The size of the Mach-O file.
+ * cdhash out On return, contains the cdhash of the file. Must be
+ * CS_CDHASH_LEN bytes.
+ */
+//bool compute_cdhash(const void *file, size_t size, struct cdhash *cdhash);
+
+int find_cdhash(const char *path, const struct stat *sb, struct cdhashes *h);
+
+#endif
diff --git a/machoparse/cs_blobs.h b/machoparse/cs_blobs.h
new file mode 100644
index 0000000..562a8b4
--- /dev/null
+++ b/machoparse/cs_blobs.h
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2017 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_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. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ *
+ * 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_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+#ifndef _KERN_CODESIGN_H_
+#define _KERN_CODESIGN_H_
+
+#include <stdint.h>
+
+/* code signing attributes of a process */
+#define CS_VALID 0x00000001 /* dynamically valid */
+#define CS_ADHOC 0x00000002 /* ad hoc signed */
+#define CS_GET_TASK_ALLOW 0x00000004 /* has get-task-allow entitlement */
+#define CS_INSTALLER 0x00000008 /* has installer entitlement */
+
+#define CS_FORCED_LV 0x00000010 /* Library Validation required by Hardened System Policy */
+#define CS_INVALID_ALLOWED 0x00000020 /* (macOS Only) Page invalidation allowed by task port policy */
+
+#define CS_HARD 0x00000100 /* don't load invalid pages */
+#define CS_KILL 0x00000200 /* kill process if it becomes invalid */
+#define CS_CHECK_EXPIRATION 0x00000400 /* force expiration checking */
+#define CS_RESTRICT 0x00000800 /* tell dyld to treat restricted */
+
+#define CS_ENFORCEMENT 0x00001000 /* require enforcement */
+#define CS_REQUIRE_LV 0x00002000 /* require library validation */
+#define CS_ENTITLEMENTS_VALIDATED 0x00004000 /* code signature permits restricted entitlements */
+#define CS_NVRAM_UNRESTRICTED 0x00008000 /* has com.apple.rootless.restricted-nvram-variables.heritable entitlement */
+
+#define CS_RUNTIME 0x00010000 /* Apply hardened runtime policies */
+#define CS_LINKER_SIGNED 0x00020000 /* Automatically signed by the linker */
+
+#define CS_ALLOWED_MACHO (CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | \
+ CS_RESTRICT | CS_ENFORCEMENT | CS_REQUIRE_LV | CS_RUNTIME | CS_LINKER_SIGNED)
+
+#define CS_EXEC_SET_HARD 0x00100000 /* set CS_HARD on any exec'ed process */
+#define CS_EXEC_SET_KILL 0x00200000 /* set CS_KILL on any exec'ed process */
+#define CS_EXEC_SET_ENFORCEMENT 0x00400000 /* set CS_ENFORCEMENT on any exec'ed process */
+#define CS_EXEC_INHERIT_SIP 0x00800000 /* set CS_INSTALLER on any exec'ed process */
+
+#define CS_KILLED 0x01000000 /* was killed by kernel for invalidity */
+#define CS_NO_UNTRUSTED_HELPERS 0x02000000 /* kernel did not load a non-platform-binary dyld or Rosetta runtime */
+#define CS_DYLD_PLATFORM CS_NO_UNTRUSTED_HELPERS /* old name */
+#define CS_PLATFORM_BINARY 0x04000000 /* this is a platform binary */
+#define CS_PLATFORM_PATH 0x08000000 /* platform binary by the fact of path (osx only) */
+
+#define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */
+#define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */
+#define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code (will go away with rdar://problem/28322552) */
+#define CS_DATAVAULT_CONTROLLER 0x80000000 /* has Data Vault controller entitlement */
+
+#define CS_ENTITLEMENT_FLAGS (CS_GET_TASK_ALLOW | CS_INSTALLER | CS_DATAVAULT_CONTROLLER | CS_NVRAM_UNRESTRICTED)
+
+/* executable segment flags */
+
+#define CS_EXECSEG_MAIN_BINARY 0x1 /* executable segment denotes main binary */
+#define CS_EXECSEG_ALLOW_UNSIGNED 0x10 /* allow unsigned pages (for debugging) */
+#define CS_EXECSEG_DEBUGGER 0x20 /* main binary is debugger */
+#define CS_EXECSEG_JIT 0x40 /* JIT enabled */
+#define CS_EXECSEG_SKIP_LV 0x80 /* OBSOLETE: skip library validation */
+#define CS_EXECSEG_CAN_LOAD_CDHASH 0x100 /* can bless cdhash for execution */
+#define CS_EXECSEG_CAN_EXEC_CDHASH 0x200 /* can execute blessed cdhash */
+
+/*
+ * Magic numbers used by Code Signing
+ */
+enum {
+ CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */
+ CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */
+ CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */
+ CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */
+ CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */
+ CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */
+ CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* embedded DER encoded entitlements */
+ CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */
+ CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */
+
+ CS_SUPPORTSSCATTER = 0x20100,
+ CS_SUPPORTSTEAMID = 0x20200,
+ CS_SUPPORTSCODELIMIT64 = 0x20300,
+ CS_SUPPORTSEXECSEG = 0x20400,
+ CS_SUPPORTSRUNTIME = 0x20500,
+ CS_SUPPORTSLINKAGE = 0x20600,
+
+ CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */
+ CSSLOT_INFOSLOT = 1,
+ CSSLOT_REQUIREMENTS = 2,
+ CSSLOT_RESOURCEDIR = 3,
+ CSSLOT_APPLICATION = 4,
+ CSSLOT_ENTITLEMENTS = 5,
+ CSSLOT_DER_ENTITLEMENTS = 7,
+
+ CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */
+ CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */
+ CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */
+
+ CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */
+ CSSLOT_IDENTIFICATIONSLOT = 0x10001,
+ CSSLOT_TICKETSLOT = 0x10002,
+
+ CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */
+ CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */
+
+ CS_HASHTYPE_SHA1 = 1,
+ CS_HASHTYPE_SHA256 = 2,
+ CS_HASHTYPE_SHA256_TRUNCATED = 3,
+ CS_HASHTYPE_SHA384 = 4,
+
+ CS_SHA1_LEN = 20,
+ CS_SHA256_LEN = 32,
+ CS_SHA256_TRUNCATED_LEN = 20,
+
+ CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */
+ CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */
+
+/*
+ * Currently only to support Legacy VPN plugins, and Mac App Store
+ * but intended to replace all the various platform code, dev code etc. bits.
+ */
+ CS_SIGNER_TYPE_UNKNOWN = 0,
+ CS_SIGNER_TYPE_LEGACYVPN = 5,
+ CS_SIGNER_TYPE_MAC_APP_STORE = 6,
+
+ CS_SUPPL_SIGNER_TYPE_UNKNOWN = 0,
+ CS_SUPPL_SIGNER_TYPE_TRUSTCACHE = 7,
+ CS_SUPPL_SIGNER_TYPE_LOCAL = 8,
+};
+
+#define KERNEL_HAVE_CS_CODEDIRECTORY 1
+#define KERNEL_CS_CODEDIRECTORY_HAVE_PLATFORM 1
+
+/*
+ * C form of a CodeDirectory.
+ */
+typedef struct __CodeDirectory {
+ uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */
+ uint32_t length; /* total length of CodeDirectory blob */
+ uint32_t version; /* compatibility version */
+ uint32_t flags; /* setup and mode flags */
+ uint32_t hashOffset; /* offset of hash slot element at index zero */
+ uint32_t identOffset; /* offset of identifier string */
+ uint32_t nSpecialSlots; /* number of special hash slots */
+ uint32_t nCodeSlots; /* number of ordinary (code) hash slots */
+ uint32_t codeLimit; /* limit to main image signature range */
+ uint8_t hashSize; /* size of each hash in bytes */
+ uint8_t hashType; /* type of hash (cdHashType* constants) */
+ uint8_t platform; /* platform identifier; zero if not platform binary */
+ uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */
+ uint32_t spare2; /* unused (must be zero) */
+
+ char end_earliest[0];
+
+ /* Version 0x20100 */
+ uint32_t scatterOffset; /* offset of optional scatter vector */
+ char end_withScatter[0];
+
+ /* Version 0x20200 */
+ uint32_t teamOffset; /* offset of optional team identifier */
+ char end_withTeam[0];
+
+ /* Version 0x20300 */
+ uint32_t spare3; /* unused (must be zero) */
+ uint64_t codeLimit64; /* limit to main image signature range, 64 bits */
+ char end_withCodeLimit64[0];
+
+ /* Version 0x20400 */
+ uint64_t execSegBase; /* offset of executable segment */
+ uint64_t execSegLimit; /* limit of executable segment */
+ uint64_t execSegFlags; /* executable segment flags */
+ char end_withExecSeg[0];
+ /* Version 0x20500 */
+ uint32_t runtime;
+ uint32_t preEncryptOffset;
+ char end_withPreEncryptOffset[0];
+
+ /* Version 0x20600 */
+ uint8_t linkageHashType;
+ uint8_t linkageTruncated;
+ uint16_t spare4;
+ uint32_t linkageOffset;
+ uint32_t linkageSize;
+ char end_withLinkage[0];
+
+
+ /* followed by dynamic content as located by offset fields above */
+} CS_CodeDirectory
+__attribute__ ((aligned(1)));
+
+/*
+ * Structure of an embedded-signature SuperBlob
+ */
+
+typedef struct __BlobIndex {
+ uint32_t type; /* type of entry */
+ uint32_t offset; /* offset of entry */
+} CS_BlobIndex
+__attribute__ ((aligned(1)));
+
+typedef struct __SC_SuperBlob {
+ uint32_t magic; /* magic number */
+ uint32_t length; /* total length of SuperBlob */
+ uint32_t count; /* number of index entries following */
+ CS_BlobIndex index[]; /* (count) entries */
+ /* followed by Blobs in no particular order as indicated by offsets in index */
+} CS_SuperBlob
+__attribute__ ((aligned(1)));
+
+#define KERNEL_HAVE_CS_GENERICBLOB 1
+typedef struct __SC_GenericBlob {
+ uint32_t magic; /* magic number */
+ uint32_t length; /* total length of blob */
+ char data[];
+} CS_GenericBlob
+__attribute__ ((aligned(1)));
+
+typedef struct __SC_Scatter {
+ uint32_t count; // number of pages; zero for sentinel (only)
+ uint32_t base; // first page number
+ uint64_t targetOffset; // offset in target
+ uint64_t spare; // reserved
+} SC_Scatter
+__attribute__ ((aligned(1)));
+
+
+#endif /* _KERN_CODESIGN_H */
diff --git a/machoparse/macho.h b/machoparse/macho.h
new file mode 100644
index 0000000..858fded
--- /dev/null
+++ b/machoparse/macho.h
@@ -0,0 +1,73 @@
+#include <sys/types.h>
+
+typedef int cpu_type_t;
+typedef int cpu_subtype_t;
+
+struct mach_header {
+ uint32_t magic;
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t filetype;
+ uint32_t ncmds;
+ uint32_t sizeofcmds;
+ uint32_t flags;
+};
+
+
+#define MH_MAGIC 0xfeedface
+#define MH_CIGAM 0xcefaedfe
+
+struct mach_header_64 {
+ uint32_t magic;
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t filetype;
+ uint32_t ncmds;
+ uint32_t sizeofcmds;
+ uint32_t flags;
+ uint32_t reserved;
+};
+
+#define MH_MAGIC_64 0xfeedfacf
+#define MH_CIGAM_64 0xcffaedfe
+
+struct load_command {
+ uint32_t cmd;
+ uint32_t cmdsize;
+};
+
+#define LC_CODE_SIGNATURE 0x1d
+
+struct linkedit_data_command {
+ uint32_t cmd;
+ uint32_t cmdsize;
+ uint32_t dataoff;
+ uint32_t datasize;
+};
+
+struct fat_header {
+ uint32_t magic;
+ uint32_t nfat_arch;
+};
+
+#define FAT_MAGIC 0xcafebabe
+#define FAT_CIGAM 0xbebafeca
+
+struct fat_arch {
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t offset;
+ uint32_t size;
+ uint32_t align;
+};
+
+#define FAT_MAGIC_64 0xcafebabf
+#define FAT_CIGAM_64 0xbfbafeca
+
+struct fat_arch_64 {
+ cpu_type_t cputype;
+ cpu_subtype_t cpusubtype;
+ uint32_t offset;
+ uint32_t size;
+ uint32_t align;
+};
diff --git a/sort.c b/sort.c
new file mode 100644
index 0000000..2b51d65
--- /dev/null
+++ b/sort.c
@@ -0,0 +1,48 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "trustcache.h"
+
+int
+ent_cmp(const void * vp1, const void * vp2)
+{
+ const struct trust_cache_entry1 *pc1 = vp1;
+ const struct trust_cache_entry1 *pc2 = vp2;
+
+ return memcmp(pc1->cdhash, pc2->cdhash, CS_CDHASH_LEN);
+}
+
+int
+hash_cmp(const void * vp1, const void * vp2)
+{
+ const trust_cache_hash0 *pc1 = vp1;
+ const trust_cache_hash0 *pc2 = vp2;
+
+ return memcmp(pc1, pc2, CS_CDHASH_LEN);
+}
diff --git a/tc.1 b/tc.1
new file mode 100644
index 0000000..19df3de
--- /dev/null
+++ b/tc.1
@@ -0,0 +1,120 @@
+.\"-
+.\" Copyright (c) 2022 Cameron Katri. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd May 19, 2022
+.Dt TC 1
+.Os
+.Sh NAME
+.Nm tc
+.Nd Create and interact with trustcaches
+.Sh SYNOPSIS
+.Nm
+.Cm append
+.Op Fl u Ar uuid | 0
+.Ar infile
+.Ar
+.Nm
+.Cm create
+.Op Fl u Ar uuid
+.Op Fl v Ar version
+.Ar outfile
+.Ar
+.Nm
+.Cm info
+.Op Fl c
+.Op Fl h
+.Op Fl e Ar entrynum
+.Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility is used to get info about and modify Apple trustcaches.
+.Pp
+The following commands are supported by
+.Nm :
+.Bl -tag -width create
+.It Xo
+.Cm append
+.Op Fl u Ar uuid | 0
+.Ar infile
+.Ar
+.Xc
+Modify the trustcache at
+.Ar infile
+to include each signed Mach-O in the specified path.
+.Ar uuid
+is used to specify a custom uuid to be used.
+If it is
+.Ar 0 ,
+the uuid will be left the same, otherwise, it will be regenerated.
+.It Xo
+.Cm create
+.Op Fl u Ar uuid
+.Op Fl v Ar version
+.Ar outfile
+.Ar
+.Xc
+Create a trustcache at
+.Ar outfile .
+Each Mach-O found in the specified inputs will be scanned for
+a code signature and hashed.
+Any malformed or unsigned Mach-O will be ignored.
+Each slice of a FAT binary will have its hash included.
+Versions 0 and 1 are supported, if not specified, 1 is assumed.
+If
+.Ar uuid
+is specified, that will be used instead of a randomly generated one.
+.It Xo
+.Cm info
+.Op Fl c
+.Op Fl h
+.Op Fl e Ar entrynum
+.Ar file
+.Xc
+Print information about
+.Ar file .
+The output for each hash will be in the format:
+.Pp
+.Dl <cdhash> <flags> [<hash_type>]
+.Pp
+If the
+.Fl c
+is given, only the hashes will be printed.
+If
+.Fl h
+is given, only the header will be printed.
+If
+.Ar entrynum
+is specified, only that entry will be printed.
+.El
+.Sh EXIT STATUS
+.Ex -std
+.Sh SEE ALSO
+.Xr cryptex-dump-trust-cache 1 ,
+.Xr cryptex-generate-trust-cache 1
+.Sh HISTORY
+The
+.Nm
+utility was written by
+.An Cameron Katri Aq Mt me@cameronkatri.com .
diff --git a/tc.c b/tc.c
new file mode 100644
index 0000000..f6cc759
--- /dev/null
+++ b/tc.c
@@ -0,0 +1,98 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2022 Cameron Katri. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CAMERON KATRI AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CAMERON KATRI OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+#include "trustcache.h"
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 2) {
+help:
+ fprintf(stderr, "Usage: tc append [-u uuid | 0] infile file ...\n"
+ " tc create [-u uuid] [-v version] outfile file ...\n"
+ " tc info [-c] [-h] [-e entrynum] file\n\n"
+ "See tc(1) for more information\n");
+ exit(1);
+ }
+
+ int ret = 1;
+
+ if (strcmp(argv[1], "info") == 0)
+ ret = tcinfo(argc - 1, argv + 1);
+ else if (strcmp(argv[1], "create") == 0)
+ ret = tccreate(argc - 1, argv + 1);
+ else if (strcmp(argv[1], "append") == 0)
+ ret = tcappend(argc - 1, argv + 1);
+ else
+ fprintf(stderr, "Unknown subcommand %s\n", argv[1]);
+
+ if (ret == -1)
+ goto help;
+
+ return ret;
+}
+
+struct trust_cache
+opentrustcache(const char *path)
+{
+ FILE *f;
+ struct trust_cache cache;
+
+ if ((f = fopen(path, "r")) == NULL) {
+ fprintf(stderr, "%s: %s\n", path, strerror(errno));
+ exit(1);
+ }
+
+ fread(&cache, sizeof(struct trust_cache) - sizeof(struct trust_cache_entry1*), 1, f);
+ cache.version = le32toh(cache.version);
+ cache.num_entries = le32toh(cache.num_entries);
+
+ if (cache.version == 0) {
+ if ((cache.hashes = calloc(cache.num_entries, sizeof(trust_cache_hash0))) == NULL)
+ exit(EX_OSERR);
+ fread(cache.hashes, sizeof(trust_cache_hash0), cache.num_entries, f);
+ } else if (cache.version == 1) {
+ if ((cache.entries = calloc(cache.num_entries, sizeof(struct trust_cache_entry1))) == NULL)
+ exit(EX_OSERR);
+ fread(cache.entries, sizeof(struct trust_cache_entry1), cache.num_entries, f);
+ } else {
+ fprintf(stderr, "%s: Unsupported version %i\n", path, cache.version);
+ exit(1);
+ }
+
+ fclose(f);
+ return cache;
+}
diff --git a/trustcache.h b/trustcache.h
new file mode 100644
index 0000000..1f8a3cd
--- /dev/null
+++ b/trustcache.h
@@ -0,0 +1,51 @@
+#ifndef _TRUSTCACHE_H_
+#define _TRUSTCACHE_H_
+
+#include <sys/types.h>
+
+#if __APPLE__
+# include <libkern/OSByteOrder.h>
+# define htole32(x) OSSwapHostToLittleInt32(x)
+# define le32toh(x) OSSwapLittleToHostInt32(x)
+#elif __has_include(<endian.h>)
+# include <endian.h>
+#else
+# include <sys/endian.h>
+#endif
+
+#include "machoparse/cs_blobs.h"
+#include "uuid/uuid.h"
+
+struct trust_cache_entry1 {
+ uint8_t cdhash[CS_CDHASH_LEN];
+ uint8_t hash_type;
+ uint8_t flags;
+};
+
+typedef uint8_t trust_cache_hash0[CS_CDHASH_LEN];
+
+struct trust_cache {
+ uint32_t version;
+ uuid_t uuid;
+ uint32_t num_entries;
+ union {
+ struct trust_cache_entry1 *entries;
+ trust_cache_hash0 *hashes;
+ };
+} __attribute__((__packed__));
+
+// flags
+#define CS_TRUST_CACHE_AMFID 0x1
+#define CS_TRUST_CACHE_ANE 0x2
+
+struct trust_cache opentrustcache(const char *path);
+struct trust_cache cache_from_tree(const char *path, uint32_t version);
+
+int tcinfo(int argc, char **argv);
+int tccreate(int argc, char **argv);
+int tcappend(int argc, char **argv);
+
+int ent_cmp(const void * vp1, const void * vp2);
+int hash_cmp(const void * vp1, const void * vp2);
+
+#endif
diff --git a/uuid/LICENSE b/uuid/LICENSE
new file mode 100644
index 0000000..8fde7a1
--- /dev/null
+++ b/uuid/LICENSE
@@ -0,0 +1,27 @@
+Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, and the entire permission notice in its entirety,
+ including the disclaimer of warranties.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
diff --git a/uuid/copy.c b/uuid/copy.c
new file mode 100644
index 0000000..96dd006
--- /dev/null
+++ b/uuid/copy.c
@@ -0,0 +1,45 @@
+/*
+ * copy.c --- copy UUIDs
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include "uuid.h"
+
+void uuid_copy(uuid_t dst, const uuid_t src)
+{
+ unsigned char *cp1;
+ const unsigned char *cp2;
+ int i;
+
+ for (i=0, cp1 = dst, cp2 = src; i < 16; i++)
+ *cp1++ = *cp2++;
+}
diff --git a/uuid/gen_uuid.c b/uuid/gen_uuid.c
new file mode 100644
index 0000000..8fa7891
--- /dev/null
+++ b/uuid/gen_uuid.c
@@ -0,0 +1,51 @@
+/*
+ * gen_uuid.c --- generate a DCE-compatible uuid
+ *
+ * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <unistd.h>
+#include <sys/random.h>
+
+#include "uuid.h"
+
+void uuid_generate(uuid_t out)
+{
+ uuid_t buf;
+ struct uuid uu;
+
+ getentropy(buf, sizeof(buf));
+ uuid_unpack(buf, &uu);
+
+ uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000;
+ uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000;
+ uuid_pack(&uu, out);
+}
diff --git a/uuid/pack.c b/uuid/pack.c
new file mode 100644
index 0000000..f143df8
--- /dev/null
+++ b/uuid/pack.c
@@ -0,0 +1,69 @@
+/*
+ * Internal routine for packing UUID's
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuid.h"
+
+void uuid_pack(const struct uuid *uu, uuid_t ptr)
+{
+ uint32_t tmp;
+ unsigned char *out = ptr;
+
+ tmp = uu->time_low;
+ out[3] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[2] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[1] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[0] = (unsigned char) tmp;
+
+ tmp = uu->time_mid;
+ out[5] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[4] = (unsigned char) tmp;
+
+ tmp = uu->time_hi_and_version;
+ out[7] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[6] = (unsigned char) tmp;
+
+ tmp = uu->clock_seq;
+ out[9] = (unsigned char) tmp;
+ tmp >>= 8;
+ out[8] = (unsigned char) tmp;
+
+ memcpy(out+10, uu->node, 6);
+}
+
diff --git a/uuid/parse.c b/uuid/parse.c
new file mode 100644
index 0000000..ce3defd
--- /dev/null
+++ b/uuid/parse.c
@@ -0,0 +1,79 @@
+/*
+ * parse.c --- UUID parsing
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "uuid.h"
+
+int uuid_parse(const char *in, uuid_t uu)
+{
+ struct uuid uuid;
+ int i;
+ const char *cp;
+ char buf[3];
+
+ if (strlen(in) != 36)
+ return -1;
+ for (i=0, cp = in; i <= 36; i++,cp++) {
+ if ((i == 8) || (i == 13) || (i == 18) ||
+ (i == 23)) {
+ if (*cp == '-')
+ continue;
+ else
+ return -1;
+ }
+ if (i== 36)
+ if (*cp == 0)
+ continue;
+ if (!isxdigit(*cp))
+ return -1;
+ }
+ uuid.time_low = strtoul(in, NULL, 16);
+ uuid.time_mid = strtoul(in+9, NULL, 16);
+ uuid.time_hi_and_version = strtoul(in+14, NULL, 16);
+ uuid.clock_seq = strtoul(in+19, NULL, 16);
+ cp = in+24;
+ buf[2] = 0;
+ for (i=0; i < 6; i++) {
+ buf[0] = *cp++;
+ buf[1] = *cp++;
+ uuid.node[i] = strtoul(buf, NULL, 16);
+ }
+
+ uuid_pack(&uuid, uu);
+ return 0;
+}
diff --git a/uuid/unpack.c b/uuid/unpack.c
new file mode 100644
index 0000000..a2fe0a0
--- /dev/null
+++ b/uuid/unpack.c
@@ -0,0 +1,62 @@
+/*
+ * Internal routine for unpacking UUID
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <string.h>
+#include "uuid.h"
+
+void uuid_unpack(const uuid_t in, struct uuid *uu)
+{
+ const uint8_t *ptr = in;
+ uint32_t tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_low = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_mid = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->time_hi_and_version = tmp;
+
+ tmp = *ptr++;
+ tmp = (tmp << 8) | *ptr++;
+ uu->clock_seq = tmp;
+
+ memcpy(uu->node, ptr, 6);
+}
diff --git a/uuid/unparse.c b/uuid/unparse.c
new file mode 100644
index 0000000..3beca91
--- /dev/null
+++ b/uuid/unparse.c
@@ -0,0 +1,60 @@
+/*
+ * unparse.c -- convert a UUID to string
+ *
+ * Copyright (C) 1996, 1997 Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF
+ * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * %End-Header%
+ */
+
+#include <stdio.h>
+
+#include "uuid.h"
+
+void uuid_unparse(const uuid_t uu, char *out)
+{
+ const uint8_t *uuid_array = (const uint8_t *)uu;
+ int uuid_index;
+ static const char *fmt = "0123456789ABCDEF";
+
+ for ( uuid_index = 0; uuid_index < sizeof(uuid_t); ++uuid_index ) {
+ // insert '-' after the 4th, 6th, 8th, and 10th uuid byte
+ switch (uuid_index) {
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ *out++ = '-';
+ break;
+ }
+ // insert uuid byte as two hex characters
+ *out++ = fmt[*uuid_array >> 4];
+ *out++ = fmt[*uuid_array++ & 0xF];
+ }
+ *out = 0;
+}
diff --git a/uuid/uuid.h b/uuid/uuid.h
new file mode 100644
index 0000000..a4be9a5
--- /dev/null
+++ b/uuid/uuid.h
@@ -0,0 +1,23 @@
+#ifndef _UUID_H_
+#define _UUID_H_
+
+#include <sys/types.h>
+
+typedef unsigned char uuid_t[16];
+
+struct uuid {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint16_t clock_seq;
+ uint8_t node[6];
+};
+
+void uuid_generate(uuid_t out);
+void uuid_unpack(const uuid_t in, struct uuid *uu);
+void uuid_pack(const struct uuid *uu, uuid_t ptr);
+int uuid_parse(const char *in, uuid_t uu);
+void uuid_unparse(const uuid_t uu, char *out);
+void uuid_copy(uuid_t dst, const uuid_t src);
+
+#endif