From: Michael Tremer Date: Tue, 26 Nov 2019 22:29:05 +0000 (+0000) Subject: database: Add scaffolding for checking signatures X-Git-Tag: 0.9.1~112 X-Git-Url: http://git.ipfire.org/?p=location%2Flibloc.git;a=commitdiff_plain;h=b1720435eff7cbb6c137602900aaefd36230e50c database: Add scaffolding for checking signatures The added function computes a SHA512 hash of the database file where the signature is cleared. The actual cryptographic check of the signature is not implemented, yet. Signed-off-by: Michael Tremer --- diff --git a/src/database.c b/src/database.c index dd4ae32..3c1c34a 100644 --- a/src/database.c +++ b/src/database.c @@ -32,6 +32,8 @@ # include #endif +#include + #include #include #include @@ -46,6 +48,8 @@ struct loc_database { struct loc_ctx* ctx; int refcount; + FILE* f; + unsigned int version; time_t created_at; off_t vendor; @@ -101,11 +105,11 @@ struct loc_database_enumerator { unsigned int* networks_visited; }; -static int loc_database_read_magic(struct loc_database* db, FILE* f) { +static int loc_database_read_magic(struct loc_database* db) { struct loc_database_magic magic; // Read from file - size_t bytes_read = fread(&magic, 1, sizeof(magic), f); + size_t bytes_read = fread(&magic, 1, sizeof(magic), db->f); // Check if we have been able to read enough data if (bytes_read < sizeof(magic)) { @@ -132,7 +136,7 @@ static int loc_database_read_magic(struct loc_database* db, FILE* f) { } static int loc_database_read_as_section_v0(struct loc_database* db, - FILE* f, const struct loc_database_header_v0* header) { + const struct loc_database_header_v0* header) { off_t as_offset = be32toh(header->as_offset); size_t as_length = be32toh(header->as_length); @@ -140,7 +144,7 @@ static int loc_database_read_as_section_v0(struct loc_database* db, if (as_length > 0) { db->as_v0 = mmap(NULL, as_length, PROT_READ, - MAP_SHARED, fileno(f), as_offset); + MAP_SHARED, fileno(db->f), as_offset); if (db->as_v0 == MAP_FAILED) return -errno; @@ -154,7 +158,7 @@ static int loc_database_read_as_section_v0(struct loc_database* db, } static int loc_database_read_network_nodes_section_v0(struct loc_database* db, - FILE* f, const struct loc_database_header_v0* header) { + const struct loc_database_header_v0* header) { off_t network_nodes_offset = be32toh(header->network_tree_offset); size_t network_nodes_length = be32toh(header->network_tree_length); @@ -163,7 +167,7 @@ static int loc_database_read_network_nodes_section_v0(struct loc_database* db, if (network_nodes_length > 0) { db->network_nodes_v0 = mmap(NULL, network_nodes_length, PROT_READ, - MAP_SHARED, fileno(f), network_nodes_offset); + MAP_SHARED, fileno(db->f), network_nodes_offset); if (db->network_nodes_v0 == MAP_FAILED) return -errno; @@ -177,7 +181,7 @@ static int loc_database_read_network_nodes_section_v0(struct loc_database* db, } static int loc_database_read_networks_section_v0(struct loc_database* db, - FILE* f, const struct loc_database_header_v0* header) { + const struct loc_database_header_v0* header) { off_t networks_offset = be32toh(header->network_data_offset); size_t networks_length = be32toh(header->network_data_length); @@ -186,7 +190,7 @@ static int loc_database_read_networks_section_v0(struct loc_database* db, if (networks_length > 0) { db->networks_v0 = mmap(NULL, networks_length, PROT_READ, - MAP_SHARED, fileno(f), networks_offset); + MAP_SHARED, fileno(db->f), networks_offset); if (db->networks_v0 == MAP_FAILED) return -errno; @@ -200,7 +204,7 @@ static int loc_database_read_networks_section_v0(struct loc_database* db, } static int loc_database_read_countries_section_v0(struct loc_database* db, - FILE* f, const struct loc_database_header_v0* header) { + const struct loc_database_header_v0* header) { off_t countries_offset = be32toh(header->countries_offset); size_t countries_length = be32toh(header->countries_length); @@ -209,7 +213,7 @@ static int loc_database_read_countries_section_v0(struct loc_database* db, if (countries_length > 0) { db->countries_v0 = mmap(NULL, countries_length, PROT_READ, - MAP_SHARED, fileno(f), countries_offset); + MAP_SHARED, fileno(db->f), countries_offset); if (db->countries_v0 == MAP_FAILED) return -errno; @@ -223,11 +227,11 @@ static int loc_database_read_countries_section_v0(struct loc_database* db, return 0; } -static int loc_database_read_header_v0(struct loc_database* db, FILE* f) { +static int loc_database_read_header_v0(struct loc_database* db) { struct loc_database_header_v0 header; // Read from file - size_t size = fread(&header, 1, sizeof(header), f); + size_t size = fread(&header, 1, sizeof(header), db->f); if (size < sizeof(header)) { ERROR(db->ctx, "Could not read enough data for header\n"); @@ -245,37 +249,37 @@ static int loc_database_read_header_v0(struct loc_database* db, FILE* f) { size_t pool_length = be32toh(header.pool_length); int r = loc_stringpool_open(db->ctx, &db->pool, - f, pool_length, pool_offset); + db->f, pool_length, pool_offset); if (r) return r; // AS section - r = loc_database_read_as_section_v0(db, f, &header); + r = loc_database_read_as_section_v0(db, &header); if (r) return r; // Network Nodes - r = loc_database_read_network_nodes_section_v0(db, f, &header); + r = loc_database_read_network_nodes_section_v0(db, &header); if (r) return r; // Networks - r = loc_database_read_networks_section_v0(db, f, &header); + r = loc_database_read_networks_section_v0(db, &header); if (r) return r; // countries - r = loc_database_read_countries_section_v0(db, f, &header); + r = loc_database_read_countries_section_v0(db, &header); if (r) return r; return 0; } -static int loc_database_read_header(struct loc_database* db, FILE* f) { +static int loc_database_read_header(struct loc_database* db) { switch (db->version) { case 0: - return loc_database_read_header_v0(db, f); + return loc_database_read_header_v0(db); default: ERROR(db->ctx, "Incompatible database version: %u\n", db->version); @@ -286,13 +290,32 @@ static int loc_database_read_header(struct loc_database* db, FILE* f) { static int loc_database_read(struct loc_database* db, FILE* f) { clock_t start = clock(); + int fd = fileno(f); + + // Clone file descriptor + fd = dup(fd); + if (!fd) { + ERROR(db->ctx, "Could not duplicate file descriptor\n"); + return -1; + } + + // Reopen the file so that we can keep our own file handle + db->f = fdopen(fd, "r"); + if (!db->f) { + ERROR(db->ctx, "Could not re-open database file\n"); + return -1; + } + + // Rewind to the start of the file + rewind(db->f); + // Read magic bytes - int r = loc_database_read_magic(db, f); + int r = loc_database_read_magic(db); if (r) return r; // Read the header - r = loc_database_read_header(db, f); + r = loc_database_read_header(db); if (r) return r; @@ -364,6 +387,10 @@ static void loc_database_free(struct loc_database* db) { loc_stringpool_unref(db->pool); + // Close database file + if (db->f) + fclose(db->f); + loc_unref(db->ctx); free(db); } @@ -376,6 +403,97 @@ LOC_EXPORT struct loc_database* loc_database_unref(struct loc_database* db) { return NULL; } +static int loc_database_hash(struct loc_database* db, + unsigned char* hash, unsigned int* length) { + int r = 0; + + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + + // Select SHA512 + const EVP_MD* md = EVP_sha512(); + + // Initialise hash function + EVP_DigestInit_ex(mdctx, md, NULL); + + // Reset file to start + rewind(db->f); + + // Read magic + struct loc_database_magic magic; + fread(&magic, 1, sizeof(magic), db->f); + + // Feed magic into the hash + EVP_DigestUpdate(mdctx, &magic, sizeof(magic)); + + // Read the header + struct loc_database_header_v0 header_v0; + + switch (db->version) { + case 0: + fread(&header_v0, 1, sizeof(header_v0), db->f); + + // Clear signature + for (unsigned int i = 0; i < sizeof(header_v0.signature); i++) { + header_v0.signature[i] = '\0'; + } + + // Feed header into the hash + EVP_DigestUpdate(mdctx, &header_v0, sizeof(header_v0)); + break; + + default: + ERROR(db->ctx, "Cannot compute hash for database with format %d\n", + db->version); + r = -EINVAL; + goto CLEANUP; + } + + // Walk through the file in chunks of 100kB + char buffer[1024 * 100]; + + while (!feof(db->f)) { + size_t bytes_read = fread(buffer, 1, sizeof(buffer), db->f); + + EVP_DigestUpdate(mdctx, buffer, bytes_read); + } + + // Finish hash + EVP_DigestFinal_ex(mdctx, hash, length); + +#ifdef ENABLE_DEBUG + char hash_string[(EVP_MAX_MD_SIZE * 2) + 1]; + + for (unsigned int i = 0; i < *length; i++) { + sprintf(&hash_string[i*2], "%02x", hash[i]); + } + + DEBUG(db->ctx, "Database hash: %s\n", hash_string); +#endif + +CLEANUP: + // Cleanup + EVP_MD_CTX_free(mdctx); + + return r; +} + +LOC_EXPORT int loc_database_verify(struct loc_database* db) { + // Hash + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_length; + + // Compute hash of the database + int r = loc_database_hash(db, hash, &hash_length); + if (r) { + ERROR(db->ctx, "Could not compute hash of the database\n"); + return r; + } + + # warning TODO Check signature against hash + + return 0; +} + LOC_EXPORT time_t loc_database_created_at(struct loc_database* db) { return db->created_at; } diff --git a/src/libloc.sym b/src/libloc.sym index 12f82ba..7fe59f1 100644 --- a/src/libloc.sym +++ b/src/libloc.sym @@ -62,6 +62,7 @@ global: loc_database_new; loc_database_ref; loc_database_unref; + loc_database_verify; # Database Enumerator loc_database_enumerator_new; diff --git a/src/loc/database.h b/src/loc/database.h index b1f0856..47a516a 100644 --- a/src/loc/database.h +++ b/src/loc/database.h @@ -31,6 +31,8 @@ int loc_database_new(struct loc_ctx* ctx, struct loc_database** database, FILE* struct loc_database* loc_database_ref(struct loc_database* db); struct loc_database* loc_database_unref(struct loc_database* db); +int loc_database_verify(struct loc_database* db); + time_t loc_database_created_at(struct loc_database* db); const char* loc_database_get_vendor(struct loc_database* db); const char* loc_database_get_description(struct loc_database* db); diff --git a/src/loc/format.h b/src/loc/format.h index 3679828..3330301 100644 --- a/src/loc/format.h +++ b/src/loc/format.h @@ -72,6 +72,9 @@ struct loc_database_header_v0 { // Add some padding for future extensions char padding[32]; + + // Signature + char signature[4096]; }; struct loc_database_network_node_v0 { diff --git a/src/python/database.c b/src/python/database.c index c31f8cf..f6247cf 100644 --- a/src/python/database.c +++ b/src/python/database.c @@ -71,6 +71,15 @@ static PyObject* Database_repr(DatabaseObject* self) { return PyUnicode_FromFormat("", self->path); } +static PyObject* Database_verify(DatabaseObject* self) { + int r = loc_database_verify(self->db); + + if (r == 0) + Py_RETURN_TRUE; + + Py_RETURN_FALSE; +} + static PyObject* Database_get_description(DatabaseObject* self) { const char* description = loc_database_get_description(self->db); @@ -285,6 +294,12 @@ static struct PyMethodDef Database_methods[] = { METH_VARARGS|METH_KEYWORDS, NULL, }, + { + "verify", + (PyCFunction)Database_verify, + METH_NOARGS, + NULL, + }, { NULL }, }; diff --git a/src/python/location-query.in b/src/python/location-query.in index e047e03..ed35d29 100644 --- a/src/python/location-query.in +++ b/src/python/location-query.in @@ -215,6 +215,11 @@ class CLI(object): % (args.database, e)) sys.exit(1) + # Verify the database + if not db.verify(): + sys.stderr.write("location-query: Could not verify the database\n") + sys.exit(1) + # Call function ret = args.func(db, args) diff --git a/src/writer.c b/src/writer.c index 15dbe08..e303969 100644 --- a/src/writer.c +++ b/src/writer.c @@ -480,6 +480,10 @@ LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) { time_t now = time(NULL); header.created_at = htobe64(now); + // Clear the signature + for (unsigned int i = 0; i < sizeof(header.signature); i++) + header.signature[i] = '\0'; + int r; off_t offset = 0;