]> git.cameronkatri.com Git - trustcache.git/commitdiff
Initial import
authorCameron Katri <me@cameronkatri.com>
Fri, 20 May 2022 14:42:38 +0000 (10:42 -0400)
committerCameron Katri <me@cameronkatri.com>
Fri, 20 May 2022 14:42:38 +0000 (10:42 -0400)
25 files changed:
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.txt [new file with mode: 0644]
append.c [new file with mode: 0644]
cache_from_tree.c [new file with mode: 0644]
create.c [new file with mode: 0644]
info.c [new file with mode: 0644]
machoparse/LICENSE [new file with mode: 0644]
machoparse/cdhash.c [new file with mode: 0644]
machoparse/cdhash.h [new file with mode: 0644]
machoparse/cs_blobs.h [new file with mode: 0644]
machoparse/macho.h [new file with mode: 0644]
sort.c [new file with mode: 0644]
tc.1 [new file with mode: 0644]
tc.c [new file with mode: 0644]
trustcache.h [new file with mode: 0644]
uuid/LICENSE [new file with mode: 0644]
uuid/copy.c [new file with mode: 0644]
uuid/gen_uuid.c [new file with mode: 0644]
uuid/pack.c [new file with mode: 0644]
uuid/parse.c [new file with mode: 0644]
uuid/unpack.c [new file with mode: 0644]
uuid/unparse.c [new file with mode: 0644]
uuid/uuid.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..4c3a795
--- /dev/null
@@ -0,0 +1,4 @@
+a.out
+*.o
+*.bin
+tc
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..2e55a09
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..92a2210
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
index 0000000..41a58c3
--- /dev/null
@@ -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 (file)
index 0000000..8f7796e
--- /dev/null
@@ -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 (file)
index 0000000..6cc559a
--- /dev/null
@@ -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 (file)
index 0000000..562a8b4
--- /dev/null
@@ -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 (file)
index 0000000..858fded
--- /dev/null
@@ -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 (file)
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 (file)
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 (file)
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 (file)
index 0000000..1f8a3cd
--- /dev/null
@@ -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 (file)
index 0000000..8fde7a1
--- /dev/null
@@ -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 (file)
index 0000000..96dd006
--- /dev/null
@@ -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 (file)
index 0000000..8fa7891
--- /dev/null
@@ -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 (file)
index 0000000..f143df8
--- /dev/null
@@ -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 (file)
index 0000000..ce3defd
--- /dev/null
@@ -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 (file)
index 0000000..a2fe0a0
--- /dev/null
@@ -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 (file)
index 0000000..3beca91
--- /dev/null
@@ -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 (file)
index 0000000..a4be9a5
--- /dev/null
@@ -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