]> git.ipfire.org Git - people/ms/libloc.git/commitdiff
Implement signing/verifying databases
authorMichael Tremer <michael.tremer@ipfire.org>
Thu, 28 Nov 2019 15:03:16 +0000 (15:03 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Thu, 28 Nov 2019 15:03:16 +0000 (15:03 +0000)
Databases can now carry a builtin signature which can be verified
when the database is being loaded.

Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
17 files changed:
Makefile.am
examples/private-key.pem [new file with mode: 0644]
examples/public-key.pem [new file with mode: 0644]
examples/python/create-database.py
src/database.c
src/loc/database.h
src/loc/format.h
src/loc/private.h
src/loc/writer.h
src/python/database.c
src/python/location-query.in
src/python/writer.c
src/test-as.c
src/test-country.c
src/test-database.c
src/test-network.c
src/writer.c

index 2e6e0cbc46f6808e62bb7879693e89e34bc36653..b181e4cc39f553ff7ceabcc6eae4d4a8d80007f7 100644 (file)
@@ -81,6 +81,8 @@ update-po:
        $(MAKE) -C po update-po
 
 EXTRA_DIST += \
+       examples/private-key.pem \
+       examples/public-key.pem \
        examples/python/create-database.py \
        examples/python/read-database.py
 
@@ -258,7 +260,8 @@ EXTRA_DIST += \
 
 TESTS_CFLAGS = \
        $(AM_CFLAGS) \
-       -DLIBLOC_PRIVATE
+       -DLIBLOC_PRIVATE \
+       -DABS_SRCDIR=\"$(abs_srcdir)\"
 
 TESTS = \
        src/test-libloc \
@@ -273,7 +276,9 @@ CLEANFILES += \
        testdata.db
 
 testdata.db: examples/python/create-database.py
-       PYTHONPATH=$(abs_builddir)/src/python/.libs $(PYTHON) $< $@
+       PYTHONPATH=$(abs_builddir)/src/python/.libs \
+       ABS_SRCDIR="$(abs_srcdir)" \
+               $(PYTHON) $< $@
 
 check_PROGRAMS = \
        src/test-libloc \
diff --git a/examples/private-key.pem b/examples/private-key.pem
new file mode 100644 (file)
index 0000000..febe142
--- /dev/null
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILuXwOyh5xQM3KnGb50wk00Lc4I7Hl0wnTCLAbFKpFJQoAoGCCqGSM49
+AwEHoUQDQgAE9RG8+mTIvluN0b/gEyVtcffqHxaOlYDyxzPKaWZPS+3UcNN4lwm2
+F4Tt2oNCqcuGl5hsZFNV7GJbf17h3F8/vA==
+-----END EC PRIVATE KEY-----
diff --git a/examples/public-key.pem b/examples/public-key.pem
new file mode 100644 (file)
index 0000000..709563b
--- /dev/null
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9RG8+mTIvluN0b/gEyVtcffqHxaO
+lYDyxzPKaWZPS+3UcNN4lwm2F4Tt2oNCqcuGl5hsZFNV7GJbf17h3F8/vA==
+-----END PUBLIC KEY-----
index d2c3b61a9922bde8b18a1daa7a5cec84f92aa508..86205e0dc41e11b5376271c7d181b805b21140ae 100644 (file)
@@ -1,32 +1,38 @@
 #!/usr/bin/python3
 
 import location
+import os
 import sys
 
-w = location.Writer()
+ABS_SRCDIR = os.environ.get("ABS_SRCDIR", ".")
 
-# Set the vendor
-w.vendor = "IPFire Project"
+private_key_path = os.path.join(ABS_SRCDIR, "examples/private-key.pem")
 
-# Set a description
-w.description = "This is a geo location database"
+with open(private_key_path, "r") as pkey:
+    w = location.Writer(pkey)
 
-# Set a license
-w.license = "CC"
+    # Set the vendor
+    w.vendor = "IPFire Project"
 
-# Add an AS
-a = w.add_as(204867)
-a.name = "Lightning Wire Labs GmbH"
+    # Set a description
+    w.description = "This is a geo location database"
 
-print(a)
+    # Set a license
+    w.license = "CC"
 
-# Add a network
-n = w.add_network("2a07:1c44:5800::/40")
-n.country_code = "DE"
-n.asn = a.number
+    # Add an AS
+    a = w.add_as(204867)
+    a.name = "Lightning Wire Labs GmbH"
 
-print(n)
+    print(a)
 
-# Write the database to disk
-for f in sys.argv[1:]:
-    w.write(f)
+    # Add a network
+    n = w.add_network("2a07:1c44:5800::/40")
+    n.country_code = "DE"
+    n.asn = a.number
+
+    print(n)
+
+    # Write the database to disk
+    for f in sys.argv[1:]:
+        w.write(f)
index 3c1c34af45a1e24052f45478fa35c0afb00a92c8..9fd7c708d5bad517120709346223c433c08941ad 100644 (file)
@@ -32,7 +32,9 @@
 #  include <endian.h>
 #endif
 
+#include <openssl/err.h>
 #include <openssl/evp.h>
+#include <openssl/pem.h>
 
 #include <loc/libloc.h>
 #include <loc/as.h>
@@ -56,6 +58,9 @@ struct loc_database {
        off_t description;
        off_t license;
 
+       char* signature;
+       size_t signature_length;
+
        // ASes in the database
        struct loc_database_as_v0* as_v0;
        size_t as_count;
@@ -244,6 +249,23 @@ static int loc_database_read_header_v0(struct loc_database* db) {
        db->description = be32toh(header.description);
        db->license     = be32toh(header.license);
 
+       // Read signature
+       db->signature_length = be32toh(header.signature_length);
+       if (db->signature_length) {
+               // Check for a plausible signature length
+               if (db->signature_length > LOC_SIGNATURE_MAX_LENGTH) {
+                       ERROR(db->ctx, "Signature too long: %ld\n", db->signature_length);
+                       return -EINVAL;
+               }
+
+               DEBUG(db->ctx, "Reading signature of %ld bytes\n",
+                       db->signature_length);
+
+               db->signature = malloc(db->signature_length);
+               for (unsigned int i = 0; i < db->signature_length; i++)
+                       db->signature[i] = header.signature[i];
+       }
+
        // Open pool
        off_t pool_offset  = be32toh(header.pool_offset);
        size_t pool_length = be32toh(header.pool_length);
@@ -387,6 +409,10 @@ static void loc_database_free(struct loc_database* db) {
 
        loc_stringpool_unref(db->pool);
 
+       // Free signature
+       if (db->signature)
+               free(db->signature);
+
        // Close database file
        if (db->f)
                fclose(db->f);
@@ -403,17 +429,28 @@ 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) {
+LOC_EXPORT int loc_database_verify(struct loc_database* db, FILE* f) {
+       // Cannot do this when no signature is available
+       if (!db->signature) {
+               DEBUG(db->ctx, "No signature available to verify\n");
+               return 1;
+       }
+
+       // Load public key
+       EVP_PKEY* pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL);
+       if (!pkey) {
+               char* error = ERR_error_string(ERR_get_error(), NULL);
+               ERROR(db->ctx, "Could not parse public key: %s\n", error);
+
+               return -1;
+       }
+
        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);
+       EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey);
 
        // Reset file to start
        rewind(db->f);
@@ -423,7 +460,7 @@ static int loc_database_hash(struct loc_database* db,
        fread(&magic, 1, sizeof(magic), db->f);
 
        // Feed magic into the hash
-       EVP_DigestUpdate(mdctx, &magic, sizeof(magic));
+       EVP_DigestVerifyUpdate(mdctx, &magic, sizeof(magic));
 
        // Read the header
        struct loc_database_header_v0 header_v0;
@@ -438,7 +475,7 @@ static int loc_database_hash(struct loc_database* db,
                        }
 
                        // Feed header into the hash
-                       EVP_DigestUpdate(mdctx, &header_v0, sizeof(header_v0));
+                       EVP_DigestVerifyUpdate(mdctx, &header_v0, sizeof(header_v0));
                        break;
 
                default:
@@ -448,52 +485,36 @@ static int loc_database_hash(struct loc_database* db,
                        goto CLEANUP;
        }
 
-       // Walk through the file in chunks of 100kB
-       char buffer[1024 * 100];
+       // Walk through the file in chunks of 64kB
+       char buffer[64 * 1024];
 
        while (!feof(db->f)) {
                size_t bytes_read = fread(buffer, 1, sizeof(buffer), db->f);
 
-               EVP_DigestUpdate(mdctx, buffer, bytes_read);
+               EVP_DigestVerifyUpdate(mdctx, buffer, bytes_read);
        }
 
-       // Finish hash
-       EVP_DigestFinal_ex(mdctx, hash, length);
-
-#ifdef ENABLE_DEBUG
-       char hash_string[(EVP_MAX_MD_SIZE * 2) + 1];
+       // Finish
+       r = EVP_DigestVerifyFinal(mdctx,
+               (unsigned char*)db->signature, db->signature_length);
 
-       for (unsigned int i = 0; i < *length; i++) {
-               sprintf(&hash_string[i*2], "%02x", hash[i]);
+       if (r == 0) {
+               DEBUG(db->ctx, "The signature is valid\n");
+       } else if (r == 1) {
+               DEBUG(db->ctx, "The signature is invalid\n");
+       } else {
+               ERROR(db->ctx, "Error verifying the signature: %s\n",
+                       ERR_error_string(ERR_get_error(), NULL));
        }
 
-       DEBUG(db->ctx, "Database hash: %s\n", hash_string);
-#endif
-
 CLEANUP:
        // Cleanup
        EVP_MD_CTX_free(mdctx);
+       EVP_PKEY_free(pkey);
 
        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;
 }
index 47a516a8264942c7b406599771dd454738cee6c7..f42b43256a1eed8129d0f5d7f3987a96a2182bad 100644 (file)
@@ -31,7 +31,7 @@ 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);
+int loc_database_verify(struct loc_database* db, FILE* f);
 
 time_t loc_database_created_at(struct loc_database* db);
 const char* loc_database_get_vendor(struct loc_database* db);
index 333030163c2ddece2a470536850b1fdd3f28d103..3762c5eef94aaf547e5245c221c2bcbe1a4e8060 100644 (file)
@@ -30,6 +30,8 @@
 
 #define LOC_DATABASE_PAGE_SIZE  4096
 
+#define LOC_SIGNATURE_MAX_LENGTH       4096
+
 struct loc_database_magic {
        char magic[7];
 
@@ -70,11 +72,12 @@ struct loc_database_header_v0 {
        uint32_t pool_offset;
        uint32_t pool_length;
 
+       // Signature
+       uint32_t signature_length;
+       char signature[LOC_SIGNATURE_MAX_LENGTH];
+
        // Add some padding for future extensions
        char padding[32];
-
-       // Signature
-       char signature[4096];
 };
 
 struct loc_database_network_node_v0 {
index 728ced2107313ccab8ca7959c59eed0cad2b1b37..70cdbb9d960ccfce4c7f701d3d9e85222f8dae5e 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <netinet/in.h>
 #include <stdbool.h>
+#include <stdio.h>
 #include <syslog.h>
 
 #include <loc/libloc.h>
index d99969c576e8d4012af9a103fc78f7251abe423d..96d14acc9b818f0528d13d2dbbb592323134ef29 100644 (file)
@@ -26,7 +26,7 @@
 
 struct loc_writer;
 
-int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer);
+int loc_writer_new(struct loc_ctx* ctx, struct loc_writer** writer, FILE* fkey);
 
 struct loc_writer* loc_writer_ref(struct loc_writer* writer);
 struct loc_writer* loc_writer_unref(struct loc_writer* writer);
index f6247cfbc87cdbffd06f44b24ad414b72f9c8b49..9bd938ca74823e2f488785d47214917d4869a425 100644 (file)
@@ -71,8 +71,27 @@ static PyObject* Database_repr(DatabaseObject* self) {
        return PyUnicode_FromFormat("<Database %s>", self->path);
 }
 
-static PyObject* Database_verify(DatabaseObject* self) {
-       int r = loc_database_verify(self->db);
+static PyObject* Database_verify(DatabaseObject* self, PyObject* args) {
+       PyObject* public_key = NULL;
+       FILE* f = NULL;
+
+       // Parse arguments
+       if (!PyArg_ParseTuple(args, "O", &public_key))
+               return NULL;
+
+       // Convert into FILE*
+       int fd = PyObject_AsFileDescriptor(public_key);
+       if (fd < 0)
+               return NULL;
+
+       // Re-open file descriptor
+       f = fdopen(fd, "r");
+       if (!f) {
+               PyErr_SetFromErrno(PyExc_IOError);
+               return NULL;
+       }
+
+       int r = loc_database_verify(self->db, f);
 
        if (r == 0)
                Py_RETURN_TRUE;
@@ -297,7 +316,7 @@ static struct PyMethodDef Database_methods[] = {
        {
                "verify",
                (PyCFunction)Database_verify,
-               METH_NOARGS,
+               METH_VARARGS,
                NULL,
        },
        { NULL },
index ed35d29804bfcc90cbd3b80ad92b093092d4443b..c455d1e5c660c98a3de1d2e6caaab311991475d0 100644 (file)
@@ -138,6 +138,11 @@ class CLI(object):
                        default="@databasedir@/database.db", help=_("Path to database"),
                )
 
+               # public key
+               parser.add_argument("--public-key", "-k",
+                       default="@databasedir@/signing-key.pem", help=_("Public Signing Key"),
+               )
+
                # lookup an IP address
                lookup = subparsers.add_parser("lookup",
                        help=_("Lookup one or multiple IP addresses"),
@@ -216,8 +221,15 @@ class CLI(object):
                        sys.exit(1)
 
                # Verify the database
-               if not db.verify():
-                       sys.stderr.write("location-query: Could not verify the database\n")
+               try:
+                       with open(args.public_key, "r") as f:
+                               if not db.verify(f):
+                                       sys.stderr.write("location-query: Could not verify the database\n")
+                                       sys.exit(1)
+
+               # Catch any errors when loading the public key
+               except (FileNotFoundError, OSError) as e:
+                       sys.stderr.write("Could not read the public key: %s\n" % e)
                        sys.exit(1)
 
                # Call function
index ecb68826a3d0a869d3768ed3e4b7bc4f425cc03b..611b34fe7a18f55f47eb1794ff3f2a30f362f1da 100644 (file)
@@ -39,12 +39,31 @@ static void Writer_dealloc(WriterObject* self) {
 }
 
 static int Writer_init(WriterObject* self, PyObject* args, PyObject* kwargs) {
-       // Create the writer object
-       int r = loc_writer_new(loc_ctx, &self->writer);
-       if (r)
+       PyObject* private_key = NULL;
+       FILE* f = NULL;
+
+       // Parse arguments
+       if (!PyArg_ParseTuple(args, "|O", &private_key))
                return -1;
 
-       return 0;
+       // Convert into FILE*
+       if (private_key) {
+               int fd = PyObject_AsFileDescriptor(private_key);
+               if (fd < 0)
+                       return -1;
+
+               // Re-open file descriptor
+               f = fdopen(fd, "r");
+               if (!f) {
+                       PyErr_SetFromErrno(PyExc_IOError);
+                       return -1;
+               }
+       }
+
+       // Create the writer object
+       int r = loc_writer_new(loc_ctx, &self->writer, f);
+
+       return r;
 }
 
 static PyObject* Writer_get_vendor(WriterObject* self) {
@@ -180,7 +199,7 @@ static PyObject* Writer_write(WriterObject* self, PyObject* args) {
        if (!PyArg_ParseTuple(args, "s", &path))
                return NULL;
 
-       FILE* f = fopen(path, "w");
+       FILE* f = fopen(path, "w+");
        if (!f) {
                PyErr_Format(PyExc_IOError, strerror(errno));
                return NULL;
index df325ba6658354103f8654e068040edb887cce7e..b531173c38e3812d08f96be3887e712bc268c487 100644 (file)
@@ -34,7 +34,7 @@ int main(int argc, char** argv) {
 
        // Create a database
        struct loc_writer* writer;
-       err = loc_writer_new(ctx, &writer);
+       err = loc_writer_new(ctx, &writer, NULL);
        if (err < 0)
                exit(EXIT_FAILURE);
 
@@ -49,7 +49,7 @@ int main(int argc, char** argv) {
                loc_as_unref(as);
        }
 
-       FILE* f = fopen("test.db", "w");
+       FILE* f = fopen("test.db", "w+");
        if (!f) {
                fprintf(stderr, "Could not open file for writing: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
index 2f2bd986cc106b6b450ee279d964917c9a365b38..43836956d4d24677bb71f1a095aac0ed0423bb63 100644 (file)
@@ -49,7 +49,7 @@ int main(int argc, char** argv) {
 
        // Create a database
        struct loc_writer* writer;
-       err = loc_writer_new(ctx, &writer);
+       err = loc_writer_new(ctx, &writer, NULL);
        if (err < 0)
                exit(EXIT_FAILURE);
 
@@ -75,7 +75,7 @@ int main(int argc, char** argv) {
        }
        loc_country_unref(country);
 
-       FILE* f = fopen("test.db", "w");
+       FILE* f = fopen("test.db", "w+");
        if (!f) {
                fprintf(stderr, "Could not open file for writing: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
index 74e83ee08115976c262818e564419042e4b51406..9e16608c1297e7d3b2c7bad002b8b4ed8a718456 100644 (file)
@@ -40,6 +40,20 @@ const char* LICENSE = "CC";
 int main(int argc, char** argv) {
        int err;
 
+       // Open public key
+       FILE* public_key = fopen(ABS_SRCDIR "/examples/public-key.pem", "r");
+       if (!public_key) {
+               fprintf(stderr, "Could not open public key file: %s\n", strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
+       // Open private key
+       FILE* private_key = fopen(ABS_SRCDIR "/examples/private-key.pem", "r");
+       if (!private_key) {
+               fprintf(stderr, "Could not open private key file: %s\n", strerror(errno));
+               exit(EXIT_FAILURE);
+       }
+
        struct loc_ctx* ctx;
        err = loc_new(&ctx);
        if (err < 0)
@@ -47,7 +61,7 @@ int main(int argc, char** argv) {
 
        // Create a database
        struct loc_writer* writer;
-       err = loc_writer_new(ctx, &writer);
+       err = loc_writer_new(ctx, &writer, private_key);
        if (err < 0)
                exit(EXIT_FAILURE);
 
@@ -99,7 +113,7 @@ int main(int argc, char** argv) {
                exit(EXIT_FAILURE);
        }
 
-       FILE* f = fopen("test.db", "w");
+       FILE* f = fopen("test.db", "w+");
        if (!f) {
                fprintf(stderr, "Could not open file for writing: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
@@ -129,6 +143,14 @@ int main(int argc, char** argv) {
                exit(EXIT_FAILURE);
        }
 
+       // Verify the database signature
+       err = loc_database_verify(db, public_key);
+       if (err) {
+               fprintf(stderr, "Could not verify the database: %d\n", err);
+               exit(EXIT_FAILURE);
+       }
+
+       // Try reading something from the database
        vendor = loc_database_get_vendor(db);
        if (!vendor) {
                fprintf(stderr, "Could not retrieve vendor\n");
@@ -143,5 +165,8 @@ int main(int argc, char** argv) {
 
        loc_unref(ctx);
 
+       fclose(private_key);
+       fclose(public_key);
+
        return EXIT_SUCCESS;
 }
index a9f2f63bf1c9d37fa71032643a6ae5bc2b3673a5..9a745661d331c2b93509649200e774c16e2818fb 100644 (file)
@@ -93,7 +93,7 @@ int main(int argc, char** argv) {
 
        // Create a database
        struct loc_writer* writer;
-       err = loc_writer_new(ctx, &writer);
+       err = loc_writer_new(ctx, &writer, NULL);
        if (err < 0)
                exit(EXIT_FAILURE);
 
@@ -120,7 +120,7 @@ int main(int argc, char** argv) {
        // Set ASN
        loc_network_set_asn(network4, 1024);
 
-       FILE* f = fopen("test.db", "w");
+       FILE* f = fopen("test.db", "w+");
        if (!f) {
                fprintf(stderr, "Could not open file for writing: %s\n", strerror(errno));
                exit(EXIT_FAILURE);
index e303969f8d3cb44cbd61df78780e95feeba95809..0a4c4087e0d94c22c3ca466d85470da146de034b 100644 (file)
@@ -14,6 +14,7 @@
        Lesser General Public License for more details.
 */
 
+#include <assert.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.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>
@@ -43,6 +50,9 @@ struct loc_writer {
        off_t description;
        off_t license;
 
+       // Private key to sign any databases
+       EVP_PKEY* private_key;
+
        struct loc_as** as;
        size_t as_count;
 
@@ -52,7 +62,26 @@ struct loc_writer {
        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;
@@ -73,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;
 }
@@ -86,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]);
@@ -467,6 +509,84 @@ static int loc_database_write_countries(struct loc_writer* writer,
        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);
+
+       // 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;
+       }
+
+       // 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;
+               }
+
+               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\n");
+       r = 0;
+
+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);
@@ -481,6 +601,7 @@ LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* f) {
        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';
 
@@ -525,12 +646,19 @@ LOC_EXPORT int loc_writer_write(struct loc_writer* writer, FILE* 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;
 }