]> git.ipfire.org Git - location/libloc.git/blobdiff - src/writer.c
Log blocks that are being fed into the signature
[location/libloc.git] / src / writer.c
index 84331d08bf2335a326631416fc47cffe881af31c..cf0ae328237400da67890dcd92ccb8f810dd9b51 100644 (file)
@@ -14,7 +14,7 @@
        Lesser General Public License for more details.
 */
 
-#include <endian.h>
+#include <assert.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/queue.h>
 #include <time.h>
 
+#ifdef HAVE_ENDIAN_H
+#  include <endian.h>
+#endif
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
 #include <loc/libloc.h>
 #include <loc/as.h>
+#include <loc/compat.h>
+#include <loc/country.h>
+#include <loc/database.h>
 #include <loc/format.h>
 #include <loc/network.h>
 #include <loc/private.h>
@@ -36,14 +48,40 @@ struct loc_writer {
        struct loc_stringpool* pool;
        off_t vendor;
        off_t description;
+       off_t license;
+
+       // Private key to sign any databases
+       EVP_PKEY* private_key;
 
        struct loc_as** as;
        size_t as_count;
 
+       struct loc_country** countries;
+       size_t countries_count;
+
        struct loc_network_tree* networks;
 };
 
-LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer) {
+static int parse_private_key(struct loc_writer* writer, FILE* f) {
+       // Free any previously loaded keys
+       if (writer->private_key)
+               EVP_PKEY_free(writer->private_key);
+
+       // Read the key
+       writer->private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL);
+
+       // Log any errors
+       if (!writer->private_key) {
+               char* error = ERR_error_string(ERR_get_error(), NULL);
+               ERROR(writer->ctx, "Could not parse private key: %s\n", error);
+
+               return -1;
+       }
+
+       return 0;
+}
+
+LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer, FILE* fkey) {
        struct loc_writer* w = calloc(1, sizeof(*w));
        if (!w)
                return -ENOMEM;
@@ -64,6 +102,15 @@ LOC_EXPORT int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer) {
                return r;
        }
 
+       // Load the private key to sign databases
+       if (fkey) {
+               r = parse_private_key(w, fkey);
+               if (r) {
+                       loc_writer_unref(w);
+                       return r;
+               }
+       }
+
        *writer = w;
        return 0;
 }
@@ -77,6 +124,10 @@ LOC_EXPORT struct loc_writer* loc_writer_ref(struct loc_writer* writer) {
 static void loc_writer_free(struct loc_writer* writer) {
        DEBUG(writer->ctx, "Releasing writer at %p\n", writer);
 
+       // Free private key
+       if (writer->private_key)
+               EVP_PKEY_free(writer->private_key);
+
        // Unref all AS
        for (unsigned int i = 0; i < writer->as_count; i++) {
                loc_as_unref(writer->as[i]);
@@ -130,6 +181,20 @@ LOC_EXPORT int loc_writer_set_description(struct loc_writer* writer, const char*
        return 0;
 }
 
+LOC_EXPORT const char* loc_writer_get_license(struct loc_writer* writer) {
+       return loc_stringpool_get(writer->pool, writer->license);
+}
+
+LOC_EXPORT int loc_writer_set_license(struct loc_writer* writer, const char* license) {
+       // Add the string to the string pool
+       off_t offset = loc_stringpool_add(writer->pool, license);
+       if (offset < 0)
+               return offset;
+
+       writer->license = offset;
+       return 0;
+}
+
 static int __loc_as_cmp(const void* as1, const void* as2) {
        return loc_as_cmp(*(struct loc_as**)as1, *(struct loc_as**)as2);
 }
@@ -168,6 +233,32 @@ LOC_EXPORT int loc_writer_add_network(struct loc_writer* writer, struct loc_netw
        return loc_network_tree_add_network(writer->networks, *network);
 }
 
+static int __loc_country_cmp(const void* country1, const void* country2) {
+       return loc_country_cmp(*(struct loc_country**)country1, *(struct loc_country**)country2);
+}
+
+LOC_EXPORT int loc_writer_add_country(struct loc_writer* writer, struct loc_country** country, const char* country_code) {
+       int r = loc_country_new(writer->ctx, country, country_code);
+       if (r)
+               return r;
+
+       // We have a new country to add
+       writer->countries_count++;
+
+       // Make space
+       writer->countries = realloc(writer->countries, sizeof(*writer->countries) * writer->countries_count);
+       if (!writer->countries)
+               return -ENOMEM;
+
+       // Add as last element
+       writer->countries[writer->countries_count - 1] = loc_country_ref(*country);
+
+       // Sort everything
+       qsort(writer->countries, writer->countries_count, sizeof(*writer->countries), __loc_country_cmp);
+
+       return 0;
+}
+
 static void make_magic(struct loc_writer* writer, struct loc_database_magic* magic) {
        // Copy magic bytes
        for (unsigned int i = 0; i < strlen(LOC_DATABASE_MAGIC); i++)
@@ -186,7 +277,7 @@ static void align_page_boundary(off_t* offset, FILE* f) {
 static int loc_database_write_pool(struct loc_writer* writer,
                struct loc_database_header_v0* header, off_t* offset, FILE* f) {
        // Save the offset of the pool section
-       DEBUG(writer->ctx, "Pool starts at %jd bytes\n", *offset);
+       DEBUG(writer->ctx, "Pool starts at %jd bytes\n", (intmax_t)*offset);
        header->pool_offset = htobe32(*offset);
 
        // Write the pool
@@ -201,7 +292,7 @@ static int loc_database_write_pool(struct loc_writer* writer,
 
 static int loc_database_write_as_section(struct loc_writer* writer,
                struct loc_database_header_v0* header, off_t* offset, FILE* f) {
-       DEBUG(writer->ctx, "AS section starts at %jd bytes\n", *offset);
+       DEBUG(writer->ctx, "AS section starts at %jd bytes\n", (intmax_t)*offset);
        header->as_offset = htobe32(*offset);
 
        size_t as_length = 0;
@@ -219,6 +310,8 @@ static int loc_database_write_as_section(struct loc_writer* writer,
        DEBUG(writer->ctx, "AS section has a length of %zu bytes\n", as_length);
        header->as_length = htobe32(as_length);
 
+       align_page_boundary(offset, f);
+
        return 0;
 }
 
@@ -274,7 +367,7 @@ static void free_network(struct network* network) {
 static int loc_database_write_networks(struct loc_writer* writer,
                struct loc_database_header_v0* header, off_t* offset, FILE* f) {
        // Write the network tree
-       DEBUG(writer->ctx, "Network tree starts at %jd bytes\n", *offset);
+       DEBUG(writer->ctx, "Network tree starts at %jd bytes\n", (intmax_t)*offset);
        header->network_tree_offset = htobe32(*offset);
 
        size_t network_tree_length = 0;
@@ -332,6 +425,9 @@ static int loc_database_write_networks(struct loc_writer* writer,
                }
 
                // Prepare what we are writing to disk
+               db_node.zero = htobe32(node->index_zero);
+               db_node.one  = htobe32(node->index_one);
+
                if (loc_network_tree_node_is_leaf(node->node)) {
                        struct loc_network* network = loc_network_tree_node_get_network(node->node);
 
@@ -339,13 +435,10 @@ static int loc_database_write_networks(struct loc_writer* writer,
                        struct network* nw = make_network(network);
                        TAILQ_INSERT_TAIL(&networks, nw, networks);
 
-                       db_node.zero = htobe32(0xffffffff);
-                       db_node.one  = htobe32(network_index++);
-
+                       db_node.network = htobe32(network_index++);
                        loc_network_unref(network);
                } else {
-                       db_node.zero = htobe32(node->index_zero);
-                       db_node.one  = htobe32(node->index_one);
+                       db_node.network = htobe32(0xffffffff);
                }
 
                // Write the current node
@@ -364,7 +457,7 @@ static int loc_database_write_networks(struct loc_writer* writer,
 
        align_page_boundary(offset, f);
 
-       DEBUG(writer->ctx, "Networks data section starts at %jd bytes\n", *offset);
+       DEBUG(writer->ctx, "Networks data section starts at %jd bytes\n", (intmax_t)*offset);
        header->network_data_offset = htobe32(*offset);
 
        // We have now written the entire tree and have all networks
@@ -386,9 +479,124 @@ static int loc_database_write_networks(struct loc_writer* writer,
 
        header->network_data_length = htobe32(network_data_length);
 
+       align_page_boundary(offset, f);
+
+       return 0;
+}
+
+static int loc_database_write_countries(struct loc_writer* writer,
+               struct loc_database_header_v0* header, off_t* offset, FILE* f) {
+       DEBUG(writer->ctx, "Countries section starts at %jd bytes\n", (intmax_t)*offset);
+       header->countries_offset = htobe32(*offset);
+
+       size_t countries_length = 0;
+
+       struct loc_database_country_v0 country;
+       for (unsigned int i = 0; i < writer->countries_count; i++) {
+               // Convert country into database format
+               loc_country_to_database_v0(writer->countries[i], writer->pool, &country);
+
+               // Write to disk
+               *offset += fwrite(&country, 1, sizeof(country), f);
+               countries_length += sizeof(country);
+       }
+
+       DEBUG(writer->ctx, "Countries section has a length of %zu bytes\n", countries_length);
+       header->countries_length = htobe32(countries_length);
+
+       align_page_boundary(offset, f);
+
        return 0;
 }
 
+static int loc_writer_create_signature(struct loc_writer* writer,
+               struct loc_database_header_v0* header, FILE* f) {
+       DEBUG(writer->ctx, "Signing database...\n");
+
+       // Read file from the beginning
+       rewind(f);
+
+       // Create a new context for signing
+       EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
+
+       // Initialise the context
+       int r = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, writer->private_key);
+       if (r != 1) {
+               ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+               goto END;
+       }
+
+       // Read magic
+       struct loc_database_magic magic;
+       fread(&magic, 1, sizeof(magic), f);
+
+       hexdump(writer->ctx, &magic, sizeof(magic));
+
+       // Feed magic into the signature
+       r = EVP_DigestSignUpdate(mdctx, &magic, sizeof(magic));
+       if (r != 1) {
+               ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+               goto END;
+       }
+
+       hexdump(writer->ctx, header, sizeof(*header));
+
+       // Feed the header into the signature
+       r = EVP_DigestSignUpdate(mdctx, header, sizeof(*header));
+       if (r != 1) {
+               ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+               goto END;
+       }
+
+       // Skip header
+       fseek(f, sizeof(*header), SEEK_CUR);
+
+       // Walk through the file in chunks of 64kB
+       char buffer[64 * 1024];
+       while (!feof(f)) {
+               size_t bytes_read = fread(buffer, 1, sizeof(buffer), f);
+
+               if (ferror(f)) {
+                       ERROR(writer->ctx, "Error reading from file: %s\n", strerror(errno));
+                       r = errno;
+                       goto END;
+               }
+
+               hexdump(writer->ctx, buffer, bytes_read);
+
+               r = EVP_DigestSignUpdate(mdctx, buffer, bytes_read);
+               if (r != 1) {
+                       ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+                       goto END;
+               }
+       }
+
+       // Compute the signature
+       size_t signature_length = sizeof(header->signature);
+
+       r = EVP_DigestSignFinal(mdctx,
+               (unsigned char*)header->signature, &signature_length);
+       if (r != 1) {
+               ERROR(writer->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+               goto END;
+       }
+
+       // Save length of the signature
+       header->signature_length = htobe32(signature_length);
+
+       DEBUG(writer->ctx, "Successfully generated signature of %lu bytes\n",
+               signature_length);
+       r = 0;
+
+       // Dump signature
+       hexdump(writer->ctx, header->signature, signature_length);
+
+END:
+       EVP_MD_CTX_free(mdctx);
+
+       return r;
+}
+
 LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
        struct loc_database_magic magic;
        make_magic(writer, &magic);
@@ -397,10 +605,16 @@ LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
        struct loc_database_header_v0 header;
        header.vendor      = htobe32(writer->vendor);
        header.description = htobe32(writer->description);
+       header.license     = htobe32(writer->license);
 
        time_t now = time(NULL);
        header.created_at = htobe64(now);
 
+       // Clear the signature
+       header.signature_length = 0;
+       for (unsigned int i = 0; i < sizeof(header.signature); i++)
+               header.signature[i] = '\0';
+
        int r;
        off_t offset = 0;
 
@@ -427,26 +641,34 @@ LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
        if (r)
                return r;
 
-       align_page_boundary(&offset, f);
-
        // Write all networks
        r = loc_database_write_networks(writer, &header, &offset, f);
        if (r)
                return r;
 
-       align_page_boundary(&offset, f);
+       // Write countries
+       r = loc_database_write_countries(writer, &header, &offset, f);
+       if (r)
+               return r;
 
        // Write pool
        r = loc_database_write_pool(writer, &header, &offset, f);
        if (r)
                return r;
 
+       // Create the signature
+       if (writer->private_key) {
+               r = loc_writer_create_signature(writer, &header, f);
+               if (r)
+                       return r;
+       }
+
        // Write the header
        r = fseek(f, sizeof(magic), SEEK_SET);
        if (r)
                return r;
 
-       offset += fwrite(&header, 1, sizeof(header), f);
+       fwrite(&header, 1, sizeof(header), f);
 
-       return 0;
+       return r;
 }