From: Alberto Leiva Popper Date: Mon, 17 Dec 2018 23:26:42 +0000 (-0600) Subject: Add validation of manifest hashes X-Git-Tag: v0.0.2~116 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=35ff58c5f901cd26139a92338c8e154371e9c643;p=thirdparty%2FFORT-validator.git Add validation of manifest hashes It seems that the basic tree validation, at least as far as the first iteration is concerned, is done. Except I never managed to understand AS validation at all. It's like there's nothing to do. Of course, there's still a ways to go. I still have to add many little ifs that the project needs to reach strict RFC compliance. Also those 20-octet sequence manifest numbers. WTF. --- diff --git a/src/Makefile.am b/src/Makefile.am index 5bc740af..8da3e429 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,6 +11,7 @@ rpki_validator_SOURCES += base64.h base64.c rpki_validator_SOURCES += common.h common.c rpki_validator_SOURCES += debug.h debug.c rpki_validator_SOURCES += file.h file.c +rpki_validator_SOURCES += hash.h hash.c rpki_validator_SOURCES += line_file.h line_file.c rpki_validator_SOURCES += log.h log.c rpki_validator_SOURCES += resource.h resource.c diff --git a/src/file.c b/src/file.c index c00944b5..064f98cb 100644 --- a/src/file.c +++ b/src/file.c @@ -58,16 +58,16 @@ file_load(const char *file_name, struct file_contents *fc) * error code. */ pr_errno(error, - "File reading error. Error message (apparently)", - file_name); + "File reading error. Error message (apparently)"); free(fc->buffer); fclose(file); return error; } /* - * As far as I can tell from the man page, feof() cannot return - * less bytes that requested like read() does. + * As far as I can tell from the man page, fread() cannot return + * less bytes than requested like read() does. It's either + * "consumed everything", "EOF reached" or error. */ pr_err("Likely programming error: fread() < file size"); pr_err("fr:%zu bs:%zu EOF:%d", fread_result, fc->buffer_size, diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 00000000..51b152b3 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,119 @@ +#include "hash.h" + +#include +#include +#include + +#include "log.h" + +#define ALGORITHM "sha256" +static EVP_MD const *md; + +int +hash_init(void) +{ + md = EVP_get_digestbyname(ALGORITHM); + if (md == NULL) { + printf("Unknown message digest %s\n", ALGORITHM); + return -EINVAL; + } + + return 0; +} + +static int +hash_file(char *filename, unsigned char *result, unsigned int *result_len) +{ + FILE *file; + struct stat stat; + unsigned char *buffer; + __blksize_t buffer_len; + size_t consumed; + EVP_MD_CTX *ctx; + int error; + + file = fopen(filename, "rb"); + if (file == NULL) + return pr_errno(errno, "Could not open file '%s'", filename); + + buffer_len = (fstat(fileno(file), &stat) == 0) ? stat.st_blksize : 1024; + buffer = malloc(buffer_len); + if (buffer == NULL) { + pr_err("Out of memory."); + error = -ENOMEM; + goto end1; + } + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + pr_err("Out of memory."); + error = -ENOMEM; + goto end2; + } + + if (!EVP_DigestInit_ex(ctx, md, NULL)) { + error = crypto_err("EVP_DigestInit_ex() failed"); + goto end3; + } + + do { + consumed = fread(buffer, 1, buffer_len, file); + error = ferror(file); + if (error) { + pr_errno(error, + "File reading error. Error message (apparently)"); + goto end3; + } + + if (!EVP_DigestUpdate(ctx, buffer, consumed)) { + error = crypto_err("EVP_DigestUpdate() failed"); + goto end3; + } + + } while (!feof(file)); + + if (!EVP_DigestFinal_ex(ctx, result, result_len)) + error = crypto_err("EVP_DigestFinal_ex() failed"); + +end3: + EVP_MD_CTX_free(ctx); +end2: + free(buffer); +end1: + if (fclose(file) == -1) + pr_errno(errno, "fclose() failed"); + return error; +} + +/** + * Computes the hash of the file whose name is @filename, and compares it to + * @expected (The "expected" hash). Returns 0 if no errors happened and the + * hashes match. + */ +int +hash_validate(char *filename, BIT_STRING_t *expected) +{ + unsigned char actual[EVP_MAX_MD_SIZE]; + unsigned int actual_len; + int error; + + if (expected->bits_unused != 0) { + pr_err("Hash string has unused bits."); + return -EINVAL; + } + + error = hash_file(filename, actual, &actual_len); + if (error) + return error; + + if (expected->size != actual_len) + goto mismatch; + if (memcmp(expected->buf, actual, actual_len) != 0) + goto mismatch; + + return 0; + +mismatch: + pr_err("File does not match its hash."); + return -EINVAL; +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 00000000..b259de31 --- /dev/null +++ b/src/hash.h @@ -0,0 +1,9 @@ +#ifndef SRC_HASH_H_ +#define SRC_HASH_H_ + +#include + +int hash_init(void); +int hash_validate(char *file, BIT_STRING_t *hash); + +#endif /* SRC_HASH_H_ */ diff --git a/src/log.c b/src/log.c index fa6eeac3..250bc50b 100644 --- a/src/log.c +++ b/src/log.c @@ -191,6 +191,7 @@ crypto_err(const char *format, ...) int error; PR_ERR(args); + fprintf(STDERR, ": "); error = ERR_GET_REASON(ERR_peek_last_error()); if (error) { diff --git a/src/main.c b/src/main.c index 0b73986a..3e9b7dfa 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "common.h" #include "debug.h" +#include "hash.h" #include "log.h" #include "thread_var.h" #include "object/certificate.h" @@ -100,6 +101,10 @@ main(int argc, char **argv) return -EINVAL; } + error = hash_init(); + if (error) + return error; + add_rpki_oids(); thvar_init(); fnstack_store(); diff --git a/src/object/manifest.c b/src/object/manifest.c index 33034dce..464d467f 100644 --- a/src/object/manifest.c +++ b/src/object/manifest.c @@ -4,9 +4,10 @@ #include #include +#include "hash.h" #include "log.h" -#include "asn1/oid.h" #include "thread_var.h" +#include "asn1/oid.h" #include "object/certificate.h" #include "object/crl.h" #include "object/roa.h" @@ -44,8 +45,8 @@ is_hash_algorithm(OBJECT_IDENTIFIER_t *aid, bool *result) static int validate_manifest(struct Manifest *manifest) { - int error; bool is_hash; + int error; /* rfc6486#section-4.2.1 */ @@ -106,13 +107,16 @@ validate_manifest(struct Manifest *manifest) if (error) return error; + /* rfc6486#section-6.6 (I guess) */ error = is_hash_algorithm(&manifest->fileHashAlg, &is_hash); if (error) return error; - if (!is_hash) + if (!is_hash) { + pr_err("The hash algorithm is not SHA256."); return -EINVAL; + } - /* TODO (critical) validate the file hashes */ + /* The file hashes will be validated during the traversal. */ return 0; } @@ -162,6 +166,7 @@ typedef int (*foreach_cb)(char *, void *); static int foreach_file(struct manifest *mft, char *extension, foreach_cb cb, void *arg) { + struct FileAndHash *fah; char *uri; size_t uri_len; char *luri; /* "Local URI". As in "URI that we can easily reference." */ @@ -169,20 +174,30 @@ foreach_file(struct manifest *mft, char *extension, foreach_cb cb, void *arg) int error; for (i = 0; i < mft->obj->fileList.list.count; i++) { + fah = mft->obj->fileList.list.array[i]; + /* * IA5String is just a subset of ASCII, so this cast is fine. * I don't see any guarantees that the string will be * zero-terminated though, so we'll handle that the hard way. */ - uri = (char *) mft->obj->fileList.list.array[i]->file.buf; - uri_len = mft->obj->fileList.list.array[i]->file.size; + uri = (char *) fah->file.buf; + uri_len = fah->file.size; if (file_has_extension(uri, uri_len, extension)) { error = get_relative_file(mft->file_path, uri, uri_len, &luri); if (error) return error; + + error = hash_validate(luri, &fah->hash); + if (error) { + free(luri); + continue; + } + error = cb(luri, arg); + free(luri); if (error) return error;