sbsign, sbattach, sbverify: add multiple signature support

sbsign will sign an already signed binary (adding a signature at the end)
sbverify has a new mode --list, for listing all the signatures and sbattach
takes a --signum argument for --remove or --detach.

Signed-off-by: James Bottomley <JBottomley@Parallels.com>
diff --git a/src/image.c b/src/image.c
index 519e288..9fdeecd 100644
--- a/src/image.c
+++ b/src/image.c
@@ -129,6 +129,11 @@
 	return 0;
 }
 
+static int align_up(int size, int align)
+{
+	return (size + align - 1) & ~(align - 1);
+}
+
 static int image_pecoff_parse(struct image *image)
 {
 	struct cert_table_header *cert_table;
@@ -224,12 +229,12 @@
 	image->cert_table = cert_table;
 
 	/* if we have a valid cert table header, populate sigbuf as a shadow
-	 * copy of the cert table */
+	 * copy of the cert tables */
 	if (cert_table && cert_table->revision == CERT_TABLE_REVISION &&
 			cert_table->type == CERT_TABLE_TYPE_PKCS &&
 			cert_table->size < size) {
-		image->sigsize = cert_table->size;
-		image->sigbuf = talloc_memdup(image, cert_table + 1,
+		image->sigsize = image->data_dir_sigtable->size;
+		image->sigbuf = talloc_memdup(image, cert_table,
 				image->sigsize);
 	}
 
@@ -239,11 +244,6 @@
 	return 0;
 }
 
-static int align_up(int size, int align)
-{
-	return (size + align - 1) & ~(align - 1);
-}
-
 static int cmp_regions(const void *p1, const void *p2)
 {
 	const struct region *r1 = p1, *r2 = p2;
@@ -482,48 +482,106 @@
 
 int image_add_signature(struct image *image, void *sig, int size)
 {
-	/* we only support one signature at present */
+	struct cert_table_header *cth;
+	int tot_size = size + sizeof(*cth);
+	int aligned_size = align_up(tot_size, 8);
+	void *start;
+
 	if (image->sigbuf) {
-		fprintf(stderr, "warning: overwriting existing signature\n");
-		talloc_free(image->sigbuf);
+		fprintf(stderr, "Image was already signed; adding additional signature\n");
+		image->sigbuf = talloc_realloc(image, image->sigbuf, uint8_t,
+					       image->sigsize + aligned_size);
+		start = image->sigbuf + image->sigsize;
+		image->sigsize += aligned_size;
+	} else {
+		fprintf(stderr, "Signing Unsigned original image\n");
+		start = image->sigbuf = talloc_array(image, uint8_t, aligned_size);
+		image->sigsize = aligned_size;
 	}
-	image->sigbuf = sig;
-	image->sigsize = size;
+	cth = start;
+	start += sizeof(*cth);
+	memset(cth, 0 , sizeof(*cth));
+	cth->size = tot_size;
+	cth->revision = CERT_TABLE_REVISION;
+	cth->type = CERT_TABLE_TYPE_PKCS;
+	memcpy(start, sig, size);
+	if (aligned_size != tot_size)
+		memset(start + size, 0, aligned_size - tot_size);
+
 	return 0;
 }
 
-void image_remove_signature(struct image *image)
+int image_get_signature(struct image *image, int signum,
+			uint8_t **buf, size_t *size)
 {
-	if (image->sigbuf)
-		talloc_free(image->sigbuf);
-	image->sigbuf = NULL;
-	image->sigsize = 0;
+	struct cert_table_header *header;
+	void *addr = (void *)image->sigbuf;
+	int i;
+
+	if (!image->sigbuf) {
+		fprintf(stderr, "No signature table present\n");
+		return -1;
+	}
+
+	header = addr;
+	for (i = 0; i < signum; i++) {
+		addr += align_up(header->size, 8);
+		header = addr;
+	}
+	if (addr >= ((void *)image->sigbuf +
+		     image->sigsize))
+		return -1;
+
+	*buf = (void *)(header + 1);
+	*size = header->size - sizeof(*header);
+	return 0;
+}
+
+int image_remove_signature(struct image *image, int signum)
+{
+	uint8_t *buf;
+	size_t size, aligned_size;
+	int rc = image_get_signature(image, signum, &buf, &size);
+
+	if (rc)
+		return rc;
+
+	buf -= sizeof(struct cert_table_header);
+	size += sizeof(struct cert_table_header);
+	aligned_size = align_up(size, 8);
+
+	/* is signature at the end? */
+	if (buf + aligned_size >= (uint8_t *)image->sigbuf + image->sigsize) {
+		/* only one signature? */
+		if (image->sigbuf == buf) {
+			talloc_free(image->sigbuf);
+			image->sigbuf = NULL;
+			image->sigsize = 0;
+			return 0;
+		}
+	} else {
+		/* sig is in the middle ... just copy the rest over it */
+		memmove(buf, buf + aligned_size, image->sigsize -
+			((void *)buf - image->sigbuf) - aligned_size);
+	}
+	image->sigsize -= aligned_size;
+	image->sigbuf = talloc_realloc(image, image->sigbuf, uint8_t,
+				       image->sigsize);
+	return 0;
+
 }
 
 int image_write(struct image *image, const char *filename)
 {
-	struct cert_table_header cert_table_header;
-	int fd, rc, len, padlen;
+	int fd, rc;
 	bool is_signed;
-	uint8_t pad[8];
 
 	is_signed = image->sigbuf && image->sigsize;
-	padlen = 0;
 
 	/* optionally update the image to contain signature data */
 	if (is_signed) {
-		cert_table_header.size = image->sigsize +
-						sizeof(cert_table_header);
-		cert_table_header.revision = CERT_TABLE_REVISION;
-		cert_table_header.type = CERT_TABLE_TYPE_PKCS;
-
-		len = sizeof(cert_table_header) + image->sigsize;
-
-		/* pad to sizeof(pad)-byte boundary */
-		padlen = align_up(len, sizeof(pad)) - len;
-
 		image->data_dir_sigtable->addr = image->data_size;
-		image->data_dir_sigtable->size = len + padlen;
+		image->data_dir_sigtable->size = image->sigsize;
 	} else {
 		image->data_dir_sigtable->addr = 0;
 		image->data_dir_sigtable->size = 0;
@@ -541,25 +599,24 @@
 	if (!is_signed)
 		goto out;
 
-	rc = write_all(fd, &cert_table_header, sizeof(cert_table_header));
-	if (!rc)
-		goto out;
-
 	rc = write_all(fd, image->sigbuf, image->sigsize);
 	if (!rc)
 		goto out;
 
-	if (padlen) {
-		memset(pad, 0, sizeof(pad));
-		rc = write_all(fd, pad, padlen);
-	}
-
 out:
 	close(fd);
 	return !rc;
 }
 
-int image_write_detached(struct image *image, const char *filename)
+int image_write_detached(struct image *image, int signum, const char *filename)
 {
-	return fileio_write_file(filename, image->sigbuf, image->sigsize);
+	uint8_t *sig;
+	size_t len;
+	int rc;
+
+	rc = image_get_signature(image, signum, &sig, &len);
+
+	if (rc)
+		return rc;
+	return fileio_write_file(filename, sig, len);
 }
diff --git a/src/image.h b/src/image.h
index d68d002..37d1925 100644
--- a/src/image.h
+++ b/src/image.h
@@ -107,9 +107,11 @@
 
 int image_hash_sha256(struct image *image, uint8_t digest[]);
 int image_add_signature(struct image *, void *sig, int size);
-void image_remove_signature(struct image *image);
+int image_get_signature(struct image *image, int signum,
+			uint8_t **buf, size_t *size);
+int image_remove_signature(struct image *image, int signum);
 int image_write(struct image *image, const char *filename);
-int image_write_detached(struct image *image, const char *filename);
+int image_write_detached(struct image *image, int signum, const char *filename);
 
 #endif /* IMAGE_H */
 
diff --git a/src/sbattach.c b/src/sbattach.c
index 012a422..dd03faf 100644
--- a/src/sbattach.c
+++ b/src/sbattach.c
@@ -64,6 +64,7 @@
 	{ "remove", no_argument, NULL, 'r' },
 	{ "help", no_argument, NULL, 'h' },
 	{ "version", no_argument, NULL, 'V' },
+	{ "signum", required_argument, NULL, 's' },
 	{ NULL, 0, NULL, 0 },
 };
 
@@ -80,7 +81,9 @@
 		"\t--detach <sigfile>  copy the boot image's signature table\n"
 		"\t                     to <sigfile>\n"
 		"\t--remove            remove the boot image's signature\n"
-		"\t                     table from the original file\n",
+		"\t                     table from the original file\n"
+	        "\t--signum            signature to operate on (defaults to\n"
+	        "\t                     first)\n",
 		toolname, toolname, toolname);
 }
 
@@ -89,9 +92,9 @@
 	printf("%s %s\n", toolname, VERSION);
 }
 
-static int detach_sig(struct image *image, const char *sig_filename)
+static int detach_sig(struct image *image, int signum, const char *sig_filename)
 {
-	return image_write_detached(image, sig_filename);
+	return image_write_detached(image, signum, sig_filename);
 }
 
 static int attach_sig(struct image *image, const char *image_filename,
@@ -137,11 +140,18 @@
 	return rc;
 }
 
-static int remove_sig(struct image *image, const char *image_filename)
+static int remove_sig(struct image *image, int signum,
+		      const char *image_filename)
 {
 	int rc;
 
-	image_remove_signature(image);
+	rc = image_remove_signature(image, signum);
+
+	if (rc) {
+		fprintf(stderr, "Error, image has no signature at %d\n",
+			signum + 1);
+		return rc;
+	}
 
 	rc = image_write(image, image_filename);
 	if (rc)
@@ -163,7 +173,7 @@
 	struct image *image;
 	enum action action;
 	bool remove;
-	int c, rc;
+	int c, rc, signum = 0;
 
 	action = ACTION_NONE;
 	sig_filename = NULL;
@@ -171,7 +181,7 @@
 
 	for (;;) {
 		int idx;
-		c = getopt_long(argc, argv, "a:d:rhV", options, &idx);
+		c = getopt_long(argc, argv, "a:d:s:rhV", options, &idx);
 		if (c == -1)
 			break;
 
@@ -186,6 +196,10 @@
 			action = (c == 'a') ? ACTION_ATTACH : ACTION_DETACH;
 			sig_filename = optarg;
 			break;
+		case 's':
+			/* humans count from 1 not zero */
+			signum = atoi(optarg) - 1;
+			break;
 		case 'r':
 			remove = true;
 			break;
@@ -236,13 +250,13 @@
 		rc = attach_sig(image, image_filename, sig_filename);
 
 	else if (action == ACTION_DETACH)
-		rc = detach_sig(image, sig_filename);
+		rc = detach_sig(image, signum, sig_filename);
 
 	if (rc)
 		goto out;
 
 	if (remove)
-		rc = remove_sig(image, image_filename);
+		rc = remove_sig(image, signum, image_filename);
 
 out:
 	talloc_free(image);
diff --git a/src/sbsign.c b/src/sbsign.c
index 58c6894..b5d2aaa 100644
--- a/src/sbsign.c
+++ b/src/sbsign.c
@@ -223,9 +223,15 @@
 
 	image_add_signature(ctx->image, buf, sigsize);
 
-	if (ctx->detached)
-		image_write_detached(ctx->image, ctx->outfilename);
-	else
+	if (ctx->detached) {
+		int i;
+		uint8_t *buf;
+		size_t len;
+
+		for (i = 0; !image_get_signature(ctx->image, i, &buf, &len); i++)
+			;
+		image_write_detached(ctx->image, i - 1, ctx->outfilename);
+	} else
 		image_write(ctx->image, ctx->outfilename);
 
 	talloc_free(ctx);
diff --git a/src/sbverify.c b/src/sbverify.c
index 4c4b2c6..84b300d 100644
--- a/src/sbverify.c
+++ b/src/sbverify.c
@@ -65,7 +65,7 @@
 
 static struct option options[] = {
 	{ "cert", required_argument, NULL, 'c' },
-	{ "no-verify", no_argument, NULL, 'n' },
+	{ "list", no_argument, NULL, 'l' },
 	{ "detached", required_argument, NULL, 'd' },
 	{ "verbose", no_argument, NULL, 'v' },
 	{ "help", no_argument, NULL, 'h' },
@@ -79,7 +79,7 @@
 		"Verify a UEFI secure boot image.\n\n"
 		"Options:\n"
 		"\t--cert <certfile>  certificate (x509 certificate)\n"
-		"\t--no-verify        don't perform certificate verification\n"
+		"\t--list             list all signatures (but don't verify)\n"
 		"\t--detached <file>  read signature from <file>, instead of\n"
 		"\t                    looking for an embedded signature\n",
 			toolname);
@@ -157,23 +157,6 @@
 	}
 }
 
-static int load_image_signature_data(struct image *image,
-		uint8_t **buf, size_t *len)
-{
-	struct cert_table_header *header;
-
-	if (!image->data_dir_sigtable->addr
-			|| !image->data_dir_sigtable->size) {
-		fprintf(stderr, "No signature table present\n");
-		return -1;
-	}
-
-	header = (void *)image->buf + image->data_dir_sigtable->addr;
-	*buf = (void *)(header + 1);
-	*len = header->size - sizeof(*header);
-	return 0;
-}
-
 static int load_detached_signature_data(struct image *image,
 		const char *filename, uint8_t **buf, size_t *len)
 {
@@ -217,7 +200,7 @@
 {
 	const char *detached_sig_filename, *image_filename;
 	enum verify_status status;
-	int rc, c, flags, verify;
+	int rc, c, flags, list;
 	const uint8_t *tmp_buf;
 	struct image *image;
 	X509_STORE *certs;
@@ -227,10 +210,11 @@
 	bool verbose;
 	BIO *idcbio;
 	PKCS7 *p7;
+	int sig_count = 0;
 
 	status = VERIFY_FAIL;
 	certs = X509_STORE_new();
-	verify = 1;
+	list = 0;
 	verbose = false;
 	detached_sig_filename = NULL;
 
@@ -244,7 +228,7 @@
 
 	for (;;) {
 		int idx;
-		c = getopt_long(argc, argv, "c:d:nvVh", options, &idx);
+		c = getopt_long(argc, argv, "c:d:lvVh", options, &idx);
 		if (c == -1)
 			break;
 
@@ -257,8 +241,8 @@
 		case 'd':
 			detached_sig_filename = optarg;
 			break;
-		case 'n':
-			verify = 0;
+		case 'l':
+			list = 1;
 			break;
 		case 'v':
 			verbose = true;
@@ -286,56 +270,76 @@
 		return EXIT_FAILURE;
 	}
 
-	if (detached_sig_filename)
-		rc = load_detached_signature_data(image, detached_sig_filename,
-				&sig_buf, &sig_size);
-	else
-		rc = load_image_signature_data(image, &sig_buf, &sig_size);
+	for (;;) {
+		if (detached_sig_filename) {
+			if (sig_count++)
+				break;
 
-	if (rc) {
-		fprintf(stderr, "Unable to read signature data from %s\n",
-				detached_sig_filename ? : image_filename);
-		goto out;
+			rc = load_detached_signature_data(image, detached_sig_filename,
+							  &sig_buf, &sig_size);
+		} else
+			rc = image_get_signature(image, sig_count++, &sig_buf, &sig_size);
+
+		if (rc) {
+			if (sig_count == 0) {
+				fprintf(stderr, "Unable to read signature data from %s\n",
+					detached_sig_filename ? : image_filename);
+			}
+			break;
+		}
+
+		tmp_buf = sig_buf;
+		if (verbose || list)
+			printf("signature %d\n", sig_count);
+		p7 = d2i_PKCS7(NULL, &tmp_buf, sig_size);
+		if (!p7) {
+			fprintf(stderr, "Unable to parse signature data\n");
+			ERR_print_errors_fp(stderr);
+			break;
+		}
+
+		if (verbose || list) {
+			print_signature_info(p7);
+			//print_certificate_store_certs(certs);
+		}
+
+		if (list)
+			continue;
+
+		idcbio = BIO_new(BIO_s_mem());
+		idc = IDC_get(p7, idcbio);
+		if (!idc) {
+			fprintf(stderr, "Unable to get IDC from PKCS7\n");
+			break;
+		}
+
+		rc = IDC_check_hash(idc, image);
+		if (rc) {
+			fprintf(stderr, "Image fails hash check\n");
+			break;
+		}
+
+		flags = PKCS7_BINARY;
+
+		X509_STORE_set_verify_cb_func(certs, x509_verify_cb);
+		rc = PKCS7_verify(p7, NULL, certs, idcbio, NULL, flags);
+		if (rc) {
+			if (verbose)
+				printf("PKCS7 verification passed\n");
+
+			status = VERIFY_OK;
+		} else if (verbose) {
+			printf("PKCS7 verification failed\n");
+			ERR_print_errors_fp(stderr);
+		}
+
 	}
 
-	tmp_buf = sig_buf;
-	p7 = d2i_PKCS7(NULL, &tmp_buf, sig_size);
-	if (!p7) {
-		fprintf(stderr, "Unable to parse signature data\n");
-		ERR_print_errors_fp(stderr);
-		goto out;
-	}
-
-	if (verbose) {
-		print_signature_info(p7);
-		print_certificate_store_certs(certs);
-	}
-
-	idcbio = BIO_new(BIO_s_mem());
-	idc = IDC_get(p7, idcbio);
-	if (!idc)
-		goto out;
-
-	rc = IDC_check_hash(idc, image);
-	if (rc)
-		goto out;
-
-	flags = PKCS7_BINARY;
-	if (!verify)
-		flags |= PKCS7_NOVERIFY;
-
-	X509_STORE_set_verify_cb_func(certs, x509_verify_cb);
-	rc = PKCS7_verify(p7, NULL, certs, idcbio, NULL, flags);
-	if (!rc) {
-		printf("PKCS7 verification failed\n");
-		ERR_print_errors_fp(stderr);
-		goto out;
-	}
-
-	status = VERIFY_OK;
-
-out:
 	talloc_free(image);
+
+	if (list)
+		exit(EXIT_SUCCESS);
+
 	if (status == VERIFY_OK)
 		printf("Signature verification OK\n");
 	else