From: Cameron Katri <me@cameronkatri.com>
Date: Thu, 16 Jun 2022 14:17:00 +0000 (-0400)
Subject: Add support for new version 2 trustcaches
X-Git-Url: https://git.cameronkatri.com/trustcache.git/commitdiff_plain

Add support for new version 2 trustcaches

Thanks to Linus Henze for reversing the new format
https://gist.github.com/LinusHenze/4cd5d7ef057a144cda7234e2c247c056
---

diff --git a/.gitignore b/.gitignore
index 2ee8367..9e8345a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,5 @@ a.out
 *.bin
 trustcache
 .vscode
+*.tc
+linux-sysroot
diff --git a/README.txt b/README.txt
index 12da2b3..3f81ef8 100644
--- a/README.txt
+++ b/README.txt
@@ -28,15 +28,16 @@ DESCRIPTION
              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.
+             a FAT binary will have its hash included.  Versions 0, 1, and 2
+             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:
+             in one of these formats:
 
                    <cdhash> <flags> [<hash_type>]
+                   <cdhash> <flags> [<hash_type>] [<category>]
 
              If the -c is given, only the hashes will be printed.  If -h is
              given, only the header will be printed.  If entrynum is
@@ -57,4 +58,4 @@ HISTORY
      The trustcache utility was written by Cameron Katri
      <me@cameronkatri.com>.
 
-FreeBSD 14.0-CURRENT             May 19, 2022             FreeBSD 14.0-CURRENT
+FreeBSD 14.0-CURRENT             June 16, 2022            FreeBSD 14.0-CURRENT
diff --git a/append.c b/append.c
index 5bc468d..fc0cb85 100644
--- a/append.c
+++ b/append.c
@@ -54,9 +54,10 @@ tcappend(int argc, char **argv)
 	uuid_t uuid;
 	const char *errstr = NULL;
 	uint8_t flags = 0;
+	uint16_t category = 0;
 
 	int ch;
-	while ((ch = getopt(argc, argv, "u:f:")) != -1) {
+	while ((ch = getopt(argc, argv, "u:f:c:")) != -1) {
 		switch (ch) {
 			case 'u':
 				if (strlen(optarg) == 1 && *optarg == '0') {
@@ -75,6 +76,13 @@ tcappend(int argc, char **argv)
 					exit(1);
 				}
 				break;
+			case 'c':
+				category = strtonum(optarg, 0, UINT16_MAX, &errstr);
+				if (errstr != NULL) {
+					fprintf(stderr, "category number is %s: %s\n", errstr, optarg);
+					exit(1);
+				}
+				break;
 		}
 	}
 
@@ -98,10 +106,14 @@ tcappend(int argc, char **argv)
 				append.hashes = calloc(1, sizeof(trust_cache_hash0));
 				for (size_t j = 0; j < CS_CDHASH_LEN; j++)
 					sscanf(argv[i] + 2 * j, "%02hhx", &append.hashes[0][j]);
-			} else {
+			} else if (append.version == 1) {
 				append.entries = calloc(1, sizeof(struct trust_cache_entry1));
 				for (size_t j = 0; j < CS_CDHASH_LEN; j++)
 					sscanf(argv[i] + 2 * j, "%02hhx", &append.entries[0].cdhash[j]);
+			} else if (append.version == 2) {
+				append.entries2 = calloc(1, sizeof(struct trust_cache_entry2));
+				for (size_t j = 0; j < CS_CDHASH_LEN; j++)
+					sscanf(argv[i] + 2 * j, "%02hhx", &append.entries2[0].cdhash[j]);
 			}
 		} else {
 			append = cache_from_tree(argv[i], cache.version);
@@ -122,15 +134,27 @@ tcappend(int argc, char **argv)
 				cache.entries[cache.num_entries + j].flags = flags != 0 ? flags : append.entries[j].flags;
 				memcpy(cache.entries[cache.num_entries + j].cdhash, append.entries[j].cdhash, CS_CDHASH_LEN);
 			}
+		} else if (append.version == 2) {
+			if ((cache.entries2 = realloc(cache.entries, sizeof(struct trust_cache_entry2) *
+							(cache.num_entries + append.num_entries))) == NULL)
+				exit(1);
+			for (uint32_t j = 0; j < append.num_entries; j++) {
+				cache.entries2[cache.num_entries + j].hash_type = append.entries[j].hash_type;
+				cache.entries2[cache.num_entries + j].flags = flags != 0 ? flags : append.entries[j].flags;
+				cache.entries2[cache.num_entries + j].category = category != 0 ? category : append.entries2[j].category;
+				memcpy(cache.entries2[cache.num_entries + j].cdhash, append.entries2[j].cdhash, CS_CDHASH_LEN);
+			}
 		}
 		free(append.hashes);
 		cache.num_entries += append.num_entries;
 	}
 
-	if (cache.version == 1)
-		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
-	else if (cache.version == 0)
+	if (cache.version == 0)
 		qsort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp);
+	else if (cache.version == 1)
+		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
+	else if (cache.version == 2)
+		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries2), ent_cmp);
 
 	switch (keepuuid) {
 		case 0:
diff --git a/cache_from_tree.c b/cache_from_tree.c
index 610653c..723fbac 100644
--- a/cache_from_tree.c
+++ b/cache_from_tree.c
@@ -60,6 +60,12 @@ tccallback(const char *path, const struct stat *sb, __attribute__((unused)) int
 			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);
+		} else if (cache.version == 2) {
+			if ((cache.entries2 = realloc(cache.entries, sizeof(struct trust_cache_entry2) * (cache.num_entries + 1))) == NULL)
+				exit(1);
+			cache.entries2[cache.num_entries].hash_type = c.h[i].hash_type;
+			cache.entries2[cache.num_entries].category = 0;
+			memcpy(cache.entries2[cache.num_entries].cdhash, c.h[i].cdhash, CS_CDHASH_LEN);
 		}
 		cache.num_entries++;
 	}
diff --git a/create.c b/create.c
index c88abc4..7c73a05 100644
--- a/create.c
+++ b/create.c
@@ -56,7 +56,7 @@ tccreate(int argc, char **argv)
 					fprintf(stderr, "Failed to parse %s as a UUID\n", optarg);
 				break;
 			case 'v':
-				if (strlen(optarg) != 1 || (optarg[0] != '0' && optarg[0] != '1')) {
+				if (strlen(optarg) != 1 || (optarg[0] != '0' && optarg[0] != '1' && optarg[0] != '2')) {
 					fprintf(stderr, "Unsupported trustcache version %s\n", optarg);
 					return 1;
 				}
@@ -64,6 +64,8 @@ tccreate(int argc, char **argv)
 					cache.version = 0;
 				else if (optarg[0] == '1')
 					cache.version = 1;
+				else if (optarg[0] == '2')
+					cache.version = 2;
 				break;
 		}
 	}
@@ -92,15 +94,27 @@ tccreate(int argc, char **argv)
 				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);
 			}
+		} else if (append.version == 2) {
+			if ((cache.entries2 = realloc(cache.entries2, sizeof(struct trust_cache_entry2) *
+							(cache.num_entries + append.num_entries))) == NULL)
+				exit(1);
+			for (uint32_t j = 0; j < append.num_entries; j++) {
+				cache.entries2[cache.num_entries + j].hash_type = append.entries2[j].hash_type;
+				cache.entries2[cache.num_entries + j].flags = append.entries2[j].flags;
+				cache.entries2[cache.num_entries + j].category = append.entries2[j].category;
+				memcpy(cache.entries2[cache.num_entries + j].cdhash, append.entries2[j].cdhash, CS_CDHASH_LEN);
+			}
 		}
 		free(append.hashes);
 		cache.num_entries += append.num_entries;
 	}
 
-	if (cache.version == 1)
-		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
-	else if (cache.version == 0)
+	if (cache.version == 0)
 		qsort(cache.hashes, cache.num_entries, sizeof(*cache.hashes), hash_cmp);
+	else if (cache.version == 1)
+		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries), ent_cmp);
+	else if (cache.version == 2)
+		qsort(cache.entries, cache.num_entries, sizeof(*cache.entries2), ent_cmp);
 
 	if (writetrustcache(cache, argv[0]) == -1)
 		return 1;
diff --git a/info.c b/info.c
index 4f74d62..b4249b1 100644
--- a/info.c
+++ b/info.c
@@ -77,8 +77,10 @@ tcinfo(int argc, char **argv)
 			for (uint32_t i = 0; i < cache.num_entries; i++) {
 				if (cache.version == 0)
 					print_hash(cache.hashes[i], true);
-				else
+				else if (cache.version == 1)
 					print_hash(cache.entries[i].cdhash, true);
+				else if (cache.version == 2)
+					print_hash(cache.entries2[i].cdhash, true);
 			}
 			goto done;
 		}
@@ -87,10 +89,12 @@ tcinfo(int argc, char **argv)
 				fprintf(stderr, "no entry %i\n", entrynum);
 				exit(1);
 			}
-			if (cache.version == 1) {
-				print_entry(cache.entries[entrynum - 1]);
-			} else if (cache.version == 0) {
+			if (cache.version == 0) {
 				print_hash(cache.hashes[entrynum - 1], true);
+			} else if (cache.version == 1) {
+				print_entry(cache.entries[entrynum - 1]);
+			} else if (cache.version == 2) {
+				print_entry2(cache.entries2[entrynum - 1]);
 			}
 		} else {
 			print_entries(cache);
@@ -121,6 +125,8 @@ print_entries(struct trust_cache cache)
 			print_hash(cache.hashes[i], true);
 		else if (cache.version == 1)
 			print_entry(cache.entries[i]);
+		else if (cache.version == 2)
+			print_entry2(cache.entries2[i]);
 	}
 }
 
@@ -150,6 +156,32 @@ print_entry(struct trust_cache_entry1 entry)
 	printf("[%i]\n", entry.hash_type);
 }
 
+void
+print_entry2(struct trust_cache_entry2 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;
+		case 0:
+			printf(" [none] ");
+			break;
+		default:
+			printf(" [%i] ", entry.flags);
+			break;
+	}
+
+	printf("[%i] [%i]\n", entry.hash_type, entry.category);
+}
+
 void
 print_hash(uint8_t cdhash[CS_CDHASH_LEN], bool newline)
 {
diff --git a/remove.c b/remove.c
index 5f31848..4f30f8a 100644
--- a/remove.c
+++ b/remove.c
@@ -89,6 +89,13 @@ tcremove(int argc, char **argv)
 					numremoved++;
 					continue;
 				}
+			} else if (cache.version == 2) {
+				if (memcmp(cache.entries2[j].cdhash, hash, CS_CDHASH_LEN) == 0) {
+					memmove(&cache.entries2[j], &cache.entries2[j + 1], (cache.num_entries - j - 1) * sizeof(struct trust_cache_entry2));
+					cache.num_entries--;
+					numremoved++;
+					continue;
+				}
 			}
 			j++;
 		}
diff --git a/trustcache.1 b/trustcache.1
index 68ec6e5..f5aaf5f 100644
--- a/trustcache.1
+++ b/trustcache.1
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd May 19, 2022
+.Dd June 16, 2022
 .Dt TRUSTCACHE 1
 .Os
 .Sh NAME
@@ -95,7 +95,7 @@ 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.
+Versions 0, 1, and 2 are supported, if not specified, 1 is assumed.
 If
 .Ar uuid
 is specified, that will be used instead of a randomly generated one.
@@ -108,9 +108,10 @@ is specified, that will be used instead of a randomly generated one.
 .Xc
 Print information about
 .Ar file .
-The output for each hash will be in the format:
+The output for each hash will be in one of these formats:
 .Pp
 .Dl <cdhash> <flags> [<hash_type>]
+.Dl <cdhash> <flags> [<hash_type>] [<category>]
 .Pp
 If the
 .Fl c
diff --git a/trustcache.c b/trustcache.c
index cb6c7b0..f9de404 100644
--- a/trustcache.c
+++ b/trustcache.c
@@ -91,6 +91,10 @@ opentrustcache(const char *path)
 		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 if (cache.version == 2) {
+		if ((cache.entries = calloc(cache.num_entries, sizeof(struct trust_cache_entry2))) == NULL)
+			exit(EX_OSERR);
+		fread(cache.entries, sizeof(struct trust_cache_entry2), cache.num_entries, f);
 	} else {
 		fprintf(stderr, "%s: Unsupported version %i\n", path, cache.version);
 		exit(1);
@@ -120,6 +124,8 @@ writetrustcache(struct trust_cache cache, const char *path)
 			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);
+		else if (cache.version == 2)
+			fwrite(&cache.entries2[i], sizeof(struct trust_cache_entry2), 1, f);
 	}
 
 	fclose(f);
diff --git a/trustcache.h b/trustcache.h
index 3b1696a..2ac1746 100644
--- a/trustcache.h
+++ b/trustcache.h
@@ -17,11 +17,18 @@
 #include "machoparse/cs_blobs.h"
 #include "uuid/uuid.h"
 
+struct trust_cache_entry2 {
+	uint8_t cdhash[CS_CDHASH_LEN];
+	uint8_t hash_type;
+	uint8_t flags;
+	uint16_t category;
+} __attribute__((__packed__));
+
 struct trust_cache_entry1 {
 	uint8_t cdhash[CS_CDHASH_LEN];
 	uint8_t hash_type;
 	uint8_t flags;
-};
+} __attribute__((__packed__));
 
 typedef uint8_t trust_cache_hash0[CS_CDHASH_LEN];
 
@@ -30,6 +37,7 @@ struct trust_cache {
 	uuid_t uuid;
 	uint32_t num_entries;
 	union {
+		struct trust_cache_entry2 *entries2;
 		struct trust_cache_entry1 *entries;
 		trust_cache_hash0 *hashes;
 	};
@@ -54,6 +62,7 @@ int hash_cmp(const void * vp1, const void * vp2);
 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_entry2(struct trust_cache_entry2 entry);
 void print_entries(struct trust_cache cache);
 
 #endif