From: Cameron Katri Date: Fri, 20 May 2022 14:42:38 +0000 (-0400) Subject: Initial import X-Git-Tag: v1.0~8 X-Git-Url: https://git.cameronkatri.com/trustcache.git/commitdiff_plain/aa035f73ce081b3f07247bd15860d72355a096b2 Initial import --- aa035f73ce081b3f07247bd15860d72355a096b2 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: + + [] + + 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 . + +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 +#include +#include +#include +#include +#include + +#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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#if COMMONCRYPTO +# include +# 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 +#endif + +#if __APPLE__ +# include +# define bswap32(x) OSSwapInt32(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +#elif __has_include() +# include +#else +# include +#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 +#include +#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 + +/* 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 + +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 + +#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 [] +.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 +#include +#include +#include +#include +#include +#include +#include + +#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 + +#if __APPLE__ +# include +# define htole32(x) OSSwapHostToLittleInt32(x) +# define le32toh(x) OSSwapLittleToHostInt32(x) +#elif __has_include() +# include +#else +# include +#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 +#include + +#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 +#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 +#include +#include +#include + +#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 +#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 + +#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 + +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