]> git.ipfire.org Git - thirdparty/FORT-validator.git/commitdiff
Add validation of manifest hashes
authorAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 17 Dec 2018 23:26:42 +0000 (17:26 -0600)
committerAlberto Leiva Popper <ydahhrk@gmail.com>
Mon, 17 Dec 2018 23:33:32 +0000 (17:33 -0600)
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.

src/Makefile.am
src/file.c
src/hash.c [new file with mode: 0644]
src/hash.h [new file with mode: 0644]
src/log.c
src/main.c
src/object/manifest.c

index 5bc740af02cea3f45114635baae23d60e12472ac..8da3e4299adace78df4dd3961af4600e070bec60 100644 (file)
@@ -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
index c00944b531e3e7cb54e629e8585fcde1478bbdfd..064f98cb5c02a03b858da3d35a4c34d11dde9367 100644 (file)
@@ -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 (file)
index 0000000..51b152b
--- /dev/null
@@ -0,0 +1,119 @@
+#include "hash.h"
+
+#include <errno.h>
+#include <openssl/evp.h>
+#include <sys/stat.h>
+
+#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 (file)
index 0000000..b259de3
--- /dev/null
@@ -0,0 +1,9 @@
+#ifndef SRC_HASH_H_
+#define SRC_HASH_H_
+
+#include <libcmscodec/BIT_STRING.h>
+
+int hash_init(void);
+int hash_validate(char *file, BIT_STRING_t *hash);
+
+#endif /* SRC_HASH_H_ */
index fa6eeac386b7113a816778021f8b33d66b6f8a19..250bc50be6fdaa7bc50d67337bdced09997e6824 100644 (file)
--- 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) {
index 0b73986a743a89774bc516f8f140c87910bee966..3e9b7dfa3944a5a05dd78d46ef416a6dbd00f91c 100644 (file)
@@ -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();
index 33034dcea7b81537c64da06642aef8d2d1d59e2a..464d467f17aced0274915e5c002e493cf94b0d13 100644 (file)
@@ -4,9 +4,10 @@
 #include <libcmscodec/GeneralizedTime.h>
 #include <libcmscodec/Manifest.h>
 
+#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;