From 5494d66eebac2b29cf35a557f54244eae7582906 Mon Sep 17 00:00:00 2001 From: "S.J.R. van Schaik" Date: Fri, 7 Jul 2017 15:43:52 +0200 Subject: [PATCH] rots-utils: initial commit --- Makefile | 45 +++++++++++ include/file.h | 3 + include/image.h | 29 +++++++ include/macros.h | 4 + include/pack.h | 3 + include/sign.h | 3 + include/unpack.h | 3 + include/verify.h | 3 + include/x509.h | 9 +++ source/file.c | 21 +++++ source/image.c | 205 +++++++++++++++++++++++++++++++++++++++++++++++ source/main.c | 39 +++++++++ source/pack.c | 104 ++++++++++++++++++++++++ source/sign.c | 196 +++++++++++++++++++++++++++++++++++++++++++++ source/unpack.c | 100 +++++++++++++++++++++++ source/verify.c | 237 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ source/x509.c | 44 +++++++++++ 17 files changed, 1048 insertions(+) create mode 100644 Makefile create mode 100644 include/file.h create mode 100644 include/image.h create mode 100644 include/macros.h create mode 100644 include/pack.h create mode 100644 include/sign.h create mode 100644 include/unpack.h create mode 100644 include/verify.h create mode 100644 include/x509.h create mode 100644 source/file.c create mode 100644 source/image.c create mode 100644 source/main.c create mode 100644 source/pack.c create mode 100644 source/sign.c create mode 100644 source/unpack.c create mode 100644 source/verify.c create mode 100644 source/x509.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..58d69ca --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +BUILD ?= build + +all: $(BUILD)/rots-util + +CFLAGS += -Iinclude +CFLAGS += -Wall -Wundef -Wextra -Wshadow -Wimplicit-function-declaration +CFLAGS += -Wredundant-decls -Wmissing-prototypes -Wstrict-prototypes +CFLAGS += -D_GNU_SOURCE +LIBS = -lcrypto -lssl +CC = gcc +LD = gcc + +obj-y += source/file.o +obj-y += source/image.o +obj-y += source/main.o +obj-y += source/pack.o +obj-y += source/sign.o +obj-y += source/unpack.o +obj-y += source/verify.o +obj-y += source/x509.o + +obj = $(addprefix $(BUILD)/, $(obj-y)) + +# Include the dependencies. +-include $(obj:.o=.d) + +# Set up the toolchain. +.SECONDARY: + +clean: + @echo "CLEAN" + @rm -rf $(BUILD) + +# Rule to compile C source code. +$(BUILD)/%.o: %.c + @echo "CC $<" + @mkdir -p $(dir $@) + @$(CC) -c $< -o $@ $(CFLAGS) -MT $@ -MMD -MP -MF $(@:.o=.d) + +$(BUILD)/rots-util: $(obj) $(LDSCRIPT) + @echo "LD $@" + @mkdir -p $(dir $@) + @$(LD) -o $@ $(CFLAGS) $(LDFLAGS) $(obj) $(LIBS) + +.PHONY: clean diff --git a/include/file.h b/include/file.h new file mode 100644 index 0000000..c405ec6 --- /dev/null +++ b/include/file.h @@ -0,0 +1,3 @@ +#pragma once + +int get_file_size(size_t *size, const char *path); diff --git a/include/image.h b/include/image.h new file mode 100644 index 0000000..092a9ae --- /dev/null +++ b/include/image.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#define ROTS_MAGIC "ROTS-IMG" + +struct rots_hdr { + uint64_t size; +}; + +struct rots_sig_hdr { + char *name; + char *digest; + uint32_t size; +}; + +size_t read_u8(FILE *fp, uint8_t *val); +size_t read_u32(FILE *fp, uint32_t *val); +size_t read_u64(FILE *fp, uint64_t *val); +size_t write_u8(FILE *fp, uint8_t val); +size_t write_u32(FILE *fp, uint32_t val); +size_t write_u64(FILE *fp, uint64_t val); + +int rots_read_hdr(FILE *fp, struct rots_hdr *hdr); +int rots_write_hdr(FILE *fp, struct rots_hdr *hdr); +int rots_read_sig_hdr(FILE *fp, struct rots_sig_hdr *sig_hdr); +int rots_write_sig_hdr(FILE *fp, struct rots_sig_hdr *sig_hdr); +void rots_cleanup_sig_hdr(struct rots_sig_hdr *sig_hdr); diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 0000000..0af228c --- /dev/null +++ b/include/macros.h @@ -0,0 +1,4 @@ +#pragma once + +#define min(x, y) (((x) < (y)) ? (x) : (y)) +#define max(x, y) (((x) > (y)) ? (x) : (y)) diff --git a/include/pack.h b/include/pack.h new file mode 100644 index 0000000..e8df87d --- /dev/null +++ b/include/pack.h @@ -0,0 +1,3 @@ +#pragma once + +int do_pack(int argc, char *argv[]); diff --git a/include/sign.h b/include/sign.h new file mode 100644 index 0000000..25c2ccb --- /dev/null +++ b/include/sign.h @@ -0,0 +1,3 @@ +#pragma once + +int do_sign(int argc, char *argv[]); diff --git a/include/unpack.h b/include/unpack.h new file mode 100644 index 0000000..b67750c --- /dev/null +++ b/include/unpack.h @@ -0,0 +1,3 @@ +#pragma once + +int do_unpack(int argc, char *argv[]); diff --git a/include/verify.h b/include/verify.h new file mode 100644 index 0000000..a59eb11 --- /dev/null +++ b/include/verify.h @@ -0,0 +1,3 @@ +#pragma once + +int do_verify(int argc, char *argv[]); diff --git a/include/x509.h b/include/x509.h new file mode 100644 index 0000000..b31174f --- /dev/null +++ b/include/x509.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include +#include + +X509 *X509_open_cert(const char *path); +char *X509_get_common_name(X509 *cert); diff --git a/source/file.c b/source/file.c new file mode 100644 index 0000000..5f013ef --- /dev/null +++ b/source/file.c @@ -0,0 +1,21 @@ +#include + +#include +#include + +#include + +int get_file_size(size_t *size, const char *path) +{ + struct stat st; + + if (!size || !path) + return -1; + + if (stat(path, &st) < 0) + return -1; + + *size = st.st_size; + + return 0; +} diff --git a/source/image.c b/source/image.c new file mode 100644 index 0000000..af58384 --- /dev/null +++ b/source/image.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include + +#include + +size_t read_u8(FILE *fp, uint8_t *val) +{ + int byte; + size_t shift = 8, nbytes; + + *val = 0; + + for (nbytes = 0; nbytes < sizeof *val; ++nbytes) { + if ((byte = fgetc(fp)) == EOF) + return nbytes; + + shift -= 8; + *val |= (byte & 0xff) << shift; + } + + return nbytes; +} + +size_t read_u32(FILE *fp, uint32_t *val) +{ + int byte; + size_t shift = 32, nbytes; + + *val = 0; + + for (nbytes = 0; nbytes < sizeof *val; ++nbytes) { + if ((byte = fgetc(fp)) == EOF) + return nbytes; + + shift -= 8; + *val |= (byte & 0xff) << shift; + } + + return nbytes; +} + +size_t read_u64(FILE *fp, uint64_t *val) +{ + int byte; + size_t shift = 64, nbytes; + + *val = 0; + + for (nbytes = 0; nbytes < sizeof *val; ++nbytes) { + if ((byte = fgetc(fp)) == EOF) + return nbytes; + + shift -= 8; + *val |= (byte & 0xff) << shift; + } + + return nbytes; +} + +size_t write_u8(FILE *fp, uint8_t val) +{ + size_t shift = 8, nbytes; + + for (nbytes = 0; nbytes < sizeof val; ++nbytes) { + shift -= 8; + + if (fputc((val >> shift) & 0xff, fp) == EOF) + return nbytes; + } + + return nbytes; +} + +size_t write_u32(FILE *fp, uint32_t val) +{ + size_t shift = 32, nbytes; + + for (nbytes = 0; nbytes < sizeof val; ++nbytes) { + shift -= 8; + + if (fputc((val >> shift) & 0xff, fp) == EOF) + return nbytes; + } + + return nbytes; +} + +size_t write_u64(FILE *fp, uint64_t val) +{ + size_t shift = 64, nbytes; + + for (nbytes = 0; nbytes < sizeof val; ++nbytes) { + shift -= 8; + + if (fputc((val >> shift) & 0xff, fp) == EOF) + return nbytes; + } + + return nbytes; +} + +int rots_read_hdr(FILE *fp, struct rots_hdr *hdr) +{ + char magic[8]; + + if (fread(magic, sizeof *magic, sizeof magic, fp) < sizeof magic) + return -1; + + if (memcmp(magic, ROTS_MAGIC, 8) != 0) + return -1; + + if (read_u64(fp, &hdr->size) < sizeof hdr->size) + return -1; + + return 0; +} + +int rots_write_hdr(FILE *fp, struct rots_hdr *hdr) +{ + if (fwrite(ROTS_MAGIC, sizeof(char), 8, fp) < 8) + return -1; + + if (write_u64(fp, hdr->size) < sizeof hdr->size) + return -1; + + return 0; +} + +int rots_read_sig_hdr(FILE *fp, struct rots_sig_hdr *sig_hdr) +{ + uint8_t len; + + if (read_u8(fp, &len) < 1) + return -1; + + if (!(sig_hdr->name = calloc(len + 1, sizeof *sig_hdr->name))) + return -1; + + if (fread(sig_hdr->name, sizeof *sig_hdr->name, len, fp) < len) + goto err_free_name; + + if (read_u8(fp, &len) < 1) + goto err_free_name; + + if (!(sig_hdr->digest = calloc(len + 1, sizeof *sig_hdr->digest))) + goto err_free_name; + + if (fread(sig_hdr->digest, sizeof *sig_hdr->digest, len, fp) < len) + goto err_free_digest; + + if (read_u32(fp, &sig_hdr->size) < sizeof sig_hdr->size) + goto err_free_digest; + + return 0; + +err_free_digest: + free(sig_hdr->digest); +err_free_name: + free(sig_hdr->name); + return -1; +} + +int rots_write_sig_hdr(FILE *fp, struct rots_sig_hdr *sig_hdr) +{ + size_t len; + + len = strlen(sig_hdr->name); + + if (write_u8(fp, len) < 1) + return -1; + + if (fwrite(sig_hdr->name, sizeof *sig_hdr->name, len, fp) < len) + return -1; + + len = strlen(sig_hdr->digest); + + if (write_u8(fp, len) < 1) + return -1; + + if (fwrite(sig_hdr->digest, sizeof *sig_hdr->digest, len, fp) < len) + return -1; + + if (write_u32(fp, sig_hdr->size) < sizeof sig_hdr->size) + return -1; + + return 0; +} + +void rots_cleanup_sig_hdr(struct rots_sig_hdr *sig_hdr) +{ + if (!sig_hdr) + return; + + if (sig_hdr->name) { + free(sig_hdr->name); + sig_hdr->name = NULL; + } + + if (sig_hdr->digest) { + free(sig_hdr->digest); + sig_hdr->digest = NULL; + } +} diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..8d7da45 --- /dev/null +++ b/source/main.c @@ -0,0 +1,39 @@ +#include +#include +#include + +#include +#include +#include +#include + +struct entry_point { + const char *cmd; + int (* main)(int argc, char *argv[]); +}; + +struct entry_point entries[] = { + { "pack", do_pack }, + { "sign", do_sign }, + { "unpack", do_unpack }, + { "verify", do_verify }, + { NULL, NULL }, +}; + +int main(int argc, char *argv[]) +{ + struct entry_point *entry; + + if (argc < 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return -1; + } + + for (entry = entries; entry->cmd; ++entry) { + if (strcmp(entry->cmd, argv[1]) == 0) + return entry->main(argc, argv); + } + + fprintf(stderr, "usage: %s \n", argv[0]); + return -1; +} diff --git a/source/pack.c b/source/pack.c new file mode 100644 index 0000000..d2057b8 --- /dev/null +++ b/source/pack.c @@ -0,0 +1,104 @@ +#include + +#include + +#include +#include +#include + +enum { + OPTION_HELP = 'h', + OPTION_OUTPUT = 'o', +}; + +struct args { + const char *input, *output; +}; + +static int parse_args(struct args *args, int argc, char *argv[]) +{ + struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { "output", required_argument, 0, OPTION_OUTPUT }, + { NULL, 0, 0, 0 }, + }; + int ret; + + while ((ret = getopt_long(argc, (char * const *)argv, "ho:", options, + NULL)) >= 0) { + switch (ret) { + case OPTION_HELP: return -1; + case OPTION_OUTPUT: args->output = optarg; break; + default: break; + } + } + + if (optind >= argc) + return -1; + + ++optind; + + if (optind >= argc) + return -1; + + args->input = argv[optind]; + + return 0; +} + +int do_pack(int argc, char *argv[]) +{ + char data[4096]; + struct rots_hdr hdr; + struct args args; + FILE *input, *output; + size_t nbytes, size; + + if (parse_args(&args, argc, argv) < 0) { + fprintf(stderr, "invalid\n"); + return -1; + } + + if (!(input = fopen(args.input, "rb"))) { + fprintf(stderr, "unable to open '%s' for reading.\n", args.input); + return -1; + } + + if (!(output = fopen(args.output, "wb"))) { + fprintf(stderr, "unable to open '%s' for writing.\n", args.output); + goto err_close_input; + } + + get_file_size(&size, args.input); + hdr.size = size; + + if (rots_write_hdr(output, &hdr) < 0) + goto err_close_output; + + while (size) { + nbytes = fread(data, sizeof *data, sizeof data, input); + + if (nbytes == 0) { + fprintf(stderr, "unable to read the next chunk\n"); + goto err_close_output; + } + + if (fwrite(data, sizeof *data, nbytes, output) < nbytes) { + fprintf(stderr, "unable to write the current chunk\n"); + goto err_close_output; + } + + size -= nbytes; + } + + fclose(output); + fclose(input); + + return 0; + +err_close_output: + fclose(output); +err_close_input: + fclose(input); + return -1; +} diff --git a/source/sign.c b/source/sign.c new file mode 100644 index 0000000..a12b920 --- /dev/null +++ b/source/sign.c @@ -0,0 +1,196 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +enum { + OPTION_HELP = 'h', + OPTION_IMAGE = 'i', + OPTION_DIGEST = 'd', + OPTION_KEY = 'k', + OPTION_CERT = 'c', +}; + +struct args { + const char *image, *digest, *key, *cert; +}; + +static int parse_args(struct args *args, int argc, char *argv[]) +{ + struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { "image", required_argument, 0, OPTION_IMAGE }, + { "digest", required_argument, 0, OPTION_DIGEST }, + { "key", required_argument, 0, OPTION_KEY }, + { "cert", required_argument, 0, OPTION_CERT }, + { NULL, 0, 0, 0 }, + }; + int ret; + + while ((ret = getopt_long(argc, (char * const *)argv, "hi:d:k:", options, + NULL)) >= 0) { + switch (ret) { + case OPTION_HELP: return -1; + case OPTION_IMAGE: args->image = optarg; break; + case OPTION_DIGEST: args->digest = optarg; break; + case OPTION_KEY: args->key = optarg; break; + case OPTION_CERT: args->cert = optarg; break; + default: break; + } + } + + return 0; +} + +static EVP_PKEY *open_priv_key(const char *path) +{ + EVP_PKEY *key; + FILE *fp; + + if (!(fp = fopen(path, "r"))) + return NULL; + + PEM_read_PrivateKey(fp, &key, NULL, NULL); + fclose(fp); + + return key; +} + +static int sign(const char *image, const char *name, const char *digest_name, + EVP_PKEY *key) +{ + char data[512]; + struct rots_hdr hdr; + struct rots_sig_hdr sig_hdr; + const EVP_MD *digest; + FILE *fp; + unsigned char *sig; + EVP_MD_CTX *ctx = NULL; + size_t nbytes, size, sig_len = 0; + + if (!image || !digest_name || !key) + return -1; + + if (!(fp = fopen(image, "r+b"))) + return -1; + + if (rots_read_hdr(fp, &hdr) < 0) + goto err_close_image; + + size = hdr.size; + + if (!(ctx = EVP_MD_CTX_create())) + goto err_close_image; + + if (!(digest = EVP_get_digestbyname(digest_name))) + goto err_destroy_ctx; + + if (!(EVP_DigestSignInit(ctx, NULL, digest, NULL, key))) + goto err_destroy_ctx; + + while (size) { + nbytes = fread(data, sizeof *data, min(size, sizeof data), fp); + + if (nbytes == 0) + return -1; + + if (!(EVP_DigestSignUpdate(ctx, data, nbytes))) + goto err_destroy_ctx; + + size -= nbytes; + } + + if (!(EVP_DigestSignFinal(ctx, NULL, &sig_len))) + goto err_destroy_ctx; + + if (!(sig = malloc(sizeof(char) * sig_len))) + goto err_destroy_ctx; + + if (!(EVP_DigestSignFinal(ctx, sig, &sig_len))) + goto err_free_sig; + + if (fseek(fp, 0, SEEK_END) < 0) + goto err_free_sig; + + sig_hdr.name = name; + sig_hdr.digest = digest_name; + sig_hdr.size = sig_len; + + if (rots_write_sig_hdr(fp, &sig_hdr) < 0) + goto err_free_sig; + + if (fwrite(sig, sizeof *sig, sig_len, fp) < sig_len) + goto err_free_sig; + + free(sig); + EVP_MD_CTX_destroy(ctx); + fclose(fp); + + return 0; + +err_free_sig: + free(sig); +err_destroy_ctx: + EVP_MD_CTX_destroy(ctx); +err_close_image: + fclose(fp); + return -1; +} + +int do_sign(int argc, char *argv[]) +{ + struct args args; + EVP_PKEY *key; + X509 *cert; + char *cn; + + if (parse_args(&args, argc, argv) < 0) { + fprintf(stderr, "invalid\n"); + return -1; + } + + OpenSSL_add_all_algorithms(); + + if (!(key = open_priv_key(args.key))) { + fprintf(stderr, "error: unable to read the private key.\n"); + return -1; + } + + if (!(cert = X509_open_cert(args.cert))) { + fprintf(stderr, "error: unable to read the X509 certificate.\n"); + OPENSSL_free(key); + return -1; + } + + if (!(cn = X509_get_common_name(cert))) { + fprintf(stderr, "error: unable to get the common name.\n"); + OPENSSL_free(cert); + OPENSSL_free(key); + return -1; + } + + if (sign(args.image, cn, args.digest, key) < 0) { + fprintf(stderr, "error: unable to sign the payload.\n"); + OPENSSL_free(cn); + OPENSSL_free(cert); + OPENSSL_free(key); + return -1; + } + + OPENSSL_free(cn); + OPENSSL_free(cert); + OPENSSL_free(key); + + return 0; +} diff --git a/source/unpack.c b/source/unpack.c new file mode 100644 index 0000000..9331161 --- /dev/null +++ b/source/unpack.c @@ -0,0 +1,100 @@ +#include +#include +#include + +#include + +#include +#include +#include + +enum { + OPTION_HELP = 'h', + OPTION_IMAGE = 'i', + OPTION_OUTPUT = 'o', +}; + +struct args { + const char *image, *output; +}; + +static int parse_args(struct args *args, int argc, char *argv[]) +{ + struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { "image", required_argument, NULL, OPTION_IMAGE }, + { "output", required_argument, NULL, OPTION_OUTPUT }, + { NULL, 0, 0, 0 }, + }; + int ret; + + while ((ret = getopt_long(argc, (char * const *)argv, "hi:o:", options, + NULL)) >= 0) { + switch (ret) { + case OPTION_HELP: return -1; + case OPTION_IMAGE: args->image = optarg; break; + case OPTION_OUTPUT: args->output = optarg; break; + default: break; + } + } + + return 0; +} + +int do_unpack(int argc, char *argv[]) +{ + char data[512]; + struct rots_hdr hdr; + FILE *in, *out; + struct args args; + size_t nbytes, size; + + if (parse_args(&args, argc, argv) < 0) { + fprintf(stderr, "invalid\n"); + return -1; + } + + if (!(in = fopen(args.image, "rb"))) { + fprintf(stderr, "error: file '%s' not found.\n", args.image); + return -1; + } + + if (!(out = fopen(args.output, "wb"))) { + fprintf(stderr, "error: cannot open '%s' for writing.\n", args.output); + goto err_close_in; + } + + if (rots_read_hdr(in, &hdr) < 0) { + fprintf(stderr, "error: file '%s' is not a ROTS-image.\n", args.image); + goto err_close_out; + } + + size = hdr.size; + + while (size) { + nbytes = fread(data, sizeof *data, min(size, sizeof data), in); + + if (nbytes == 0) { + fprintf(stderr, "error: cannot read the next chunk.\n"); + goto err_close_out; + } + + if (fwrite(data, sizeof *data, nbytes, out) < nbytes) { + fprintf(stderr, "error: cannot write the current chunk.\n"); + goto err_close_out; + } + + size -= nbytes; + } + + fclose(out); + fclose(in); + + return 0; + +err_close_out: + fclose(out); +err_close_in: + fclose(in); + return -1; +} diff --git a/source/verify.c b/source/verify.c new file mode 100644 index 0000000..500714b --- /dev/null +++ b/source/verify.c @@ -0,0 +1,237 @@ +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +enum { + OPTION_HELP = 'h', + OPTION_IMAGE = 'i', + OPTION_CERTS = 'c', +}; + +struct args { + const char *image, *digest, *certs; +}; + +static int parse_args(struct args *args, int argc, char *argv[]) +{ + struct option options[] = { + { "help", no_argument, NULL, OPTION_HELP }, + { "image", required_argument, 0, OPTION_IMAGE }, + { "certs", required_argument, 0, OPTION_CERTS }, + { NULL, 0, 0, 0 }, + }; + int ret; + + while ((ret = getopt_long(argc, (char * const *)argv, "hi:d:c:", options, + NULL)) >= 0) { + switch (ret) { + case OPTION_HELP: return -1; + case OPTION_IMAGE: args->image = optarg; break; + case OPTION_CERTS: args->certs = optarg; break; + default: break; + } + } + + return 0; +} + +static X509 *X509_find_cert_by_common_name(const char *path, const char *name) +{ + DIR *dir; + struct dirent *dirent; + X509 *cert = NULL; + char *fpath, *cn; + + if (!(dir = opendir(path))) + return NULL; + + while ((dirent = readdir(dir))) { + if (asprintf(&fpath, "%s/%s", path, dirent->d_name) < 0) + continue; + + if (!(cert = X509_open_cert(fpath))) + continue; + + if (!(cn = X509_get_common_name(cert))) { + free(cert); cert = NULL; + continue; + } + + if (strcmp(cn, name) == 0) + break; + } + + closedir(dir); + + return cert; +} + +static int verify(const char *image, const char *digest_name, EVP_PKEY *key, + unsigned char *sig, size_t sig_len) +{ + char data[512]; + struct rots_hdr hdr; + const EVP_MD *digest; + FILE *fp; + EVP_MD_CTX *ctx = NULL; + size_t nbytes, size; + int ret; + + if (!image || !digest_name || !key || !sig || !sig_len) + return -1; + + if (!(fp = fopen(image, "rb"))) + return -1; + + if (rots_read_hdr(fp, &hdr) < 0) + goto err_close_image; + + size = hdr.size; + + if (!(ctx = EVP_MD_CTX_create())) + goto err_close_image; + + if (!(digest = EVP_get_digestbyname(digest_name))) + goto err_destroy_ctx; + + if (!(EVP_DigestVerifyInit(ctx, NULL, digest, NULL, key))) + goto err_destroy_ctx; + + while (size) { + nbytes = fread(data, sizeof *data, min(size, sizeof data), fp); + + if (nbytes == 0) + goto err_destroy_ctx; + + if (!(EVP_DigestVerifyUpdate(ctx, data, nbytes))) + goto err_destroy_ctx; + + size -= nbytes; + } + + if (EVP_DigestVerifyFinal(ctx, sig, sig_len) == 1) { + ret = 0; + } else { + ret = -1; + } + + EVP_MD_CTX_destroy(ctx); + fclose(fp); + return ret; + +err_destroy_ctx: + EVP_MD_CTX_destroy(ctx); +err_close_image: + fclose(fp); + return -1; +} + +static int verify_all(size_t *count, size_t *total, const char *ca_path, const char *image) +{ + struct rots_hdr hdr; + struct rots_sig_hdr sig_hdr; + unsigned char *sig; + FILE *fp; + EVP_PKEY *key; + X509 *cert; + int ret; + + *count = *total = 0; + + if (!(fp = fopen(image, "rb"))) + return -1; + + if (rots_read_hdr(fp, &hdr) < 0) + goto err_close_image; + + if (fseek(fp, hdr.size, SEEK_CUR) < 0) + goto err_close_image; + + while (rots_read_sig_hdr(fp, &sig_hdr) == 0) { + if (!(cert = X509_find_cert_by_common_name(ca_path, sig_hdr.name))) { + rots_cleanup_sig_hdr(&sig_hdr); + fseek(fp, sig_hdr.size, SEEK_CUR); + ++*total; + continue; + } + + if (X509_check_issued(cert, cert) == X509_V_OK) { + printf("self-signed certificate\n"); + } else { + printf("root-signed certificate\n"); + } + + if (!(key = X509_get_pubkey(cert))) { + free(cert); + rots_cleanup_sig_hdr(&sig_hdr); + fseek(fp, sig_hdr.size, SEEK_CUR); + ++*total; + continue; + } + + if (!(sig = malloc(sig_hdr.size * sizeof *sig))) { + free(cert); + rots_cleanup_sig_hdr(&sig_hdr); + goto err_close_image; + } + + if (fread(sig, sizeof *sig, sig_hdr.size, fp) < sig_hdr.size) { + free(sig); + goto err_close_image; + } + + ret = verify(image, sig_hdr.digest, key, sig, sig_hdr.size); + free(sig); + + if (ret == 0) + ++*count; + + ++*total; + + rots_cleanup_sig_hdr(&sig_hdr); + } + + fclose(fp); + return 0; + +err_close_image: + fclose(fp); + return -1; +} + +int do_verify(int argc, char *argv[]) +{ + struct args args; + size_t count, total; + + if (parse_args(&args, argc, argv) < 0) { + fprintf(stderr, "invalid\n"); + return -1; + } + + OpenSSL_add_all_algorithms(); + + if (verify_all(&count, &total, args.certs, args.image) < 0) { + fprintf(stderr, "error: unable to verify the signature(s).\n"); + return -1; + } + + printf("%zu/%zu signatures are correct.\n", count, total); + + return 0; +} diff --git a/source/x509.c b/source/x509.c new file mode 100644 index 0000000..dfe7d20 --- /dev/null +++ b/source/x509.c @@ -0,0 +1,44 @@ +#include +#include + +#include + +X509 *X509_open_cert(const char *path) +{ + X509 *cert; + FILE *fp; + + if (!(fp = fopen(path, "r"))) + return NULL; + + cert = PEM_read_X509(fp, NULL, NULL, NULL); + fclose(fp); + + return cert; +} + +char *X509_get_common_name(X509 *cert) +{ + X509_NAME *subj; + X509_NAME_ENTRY *entry; + ASN1_STRING *data; + unsigned char *cn; + int idx; + + if (!(subj = X509_get_subject_name(cert))) + return NULL; + + if ((idx = X509_NAME_get_index_by_NID(subj, NID_commonName, -1)) < 0) + return NULL; + + if (!(entry = X509_NAME_get_entry(subj, idx))) + return NULL; + + if (!(data = X509_NAME_ENTRY_get_data(entry))) + return NULL; + + if (ASN1_STRING_to_UTF8(&cn, data) == 0) + return NULL; + + return (char *)cn; +}