From 726f9984f4287abb003d2d8c8905330828ae3a7a Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Thu, 28 Nov 2019 15:03:16 +0000 Subject: [PATCH] Implement signing/verifying databases Databases can now carry a builtin signature which can be verified when the database is being loaded. Signed-off-by: Michael Tremer --- Makefile.am | 9 +- examples/private-key.pem | 5 ++ examples/public-key.pem | 4 + examples/python/create-database.py | 44 ++++++---- src/database.c | 97 +++++++++++++-------- src/loc/database.h | 2 +- src/loc/format.h | 9 +- src/loc/private.h | 1 + src/loc/writer.h | 2 +- src/python/database.c | 25 +++++- src/python/location-query.in | 16 +++- src/python/writer.c | 29 +++++-- src/test-as.c | 4 +- src/test-country.c | 4 +- src/test-database.c | 29 ++++++- src/test-network.c | 4 +- src/writer.c | 134 ++++++++++++++++++++++++++++- 17 files changed, 333 insertions(+), 85 deletions(-) create mode 100644 examples/private-key.pem create mode 100644 examples/public-key.pem diff --git a/Makefile.am b/Makefile.am index 2e6e0cb..b181e4c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 0000000..febe142 --- /dev/null +++ b/examples/private-key.pem @@ -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 index 0000000..709563b --- /dev/null +++ b/examples/public-key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9RG8+mTIvluN0b/gEyVtcffqHxaO +lYDyxzPKaWZPS+3UcNN4lwm2F4Tt2oNCqcuGl5hsZFNV7GJbf17h3F8/vA== +-----END PUBLIC KEY----- diff --git a/examples/python/create-database.py b/examples/python/create-database.py index d2c3b61..86205e0 100644 --- a/examples/python/create-database.py +++ b/examples/python/create-database.py @@ -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) diff --git a/src/database.c b/src/database.c index 3c1c34a..9fd7c70 100644 --- a/src/database.c +++ b/src/database.c @@ -32,7 +32,9 @@ # include #endif +#include #include +#include #include #include @@ -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; } diff --git a/src/loc/database.h b/src/loc/database.h index 47a516a..f42b432 100644 --- a/src/loc/database.h +++ b/src/loc/database.h @@ -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); diff --git a/src/loc/format.h b/src/loc/format.h index 3330301..3762c5e 100644 --- a/src/loc/format.h +++ b/src/loc/format.h @@ -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 { diff --git a/src/loc/private.h b/src/loc/private.h index 728ced2..70cdbb9 100644 --- a/src/loc/private.h +++ b/src/loc/private.h @@ -21,6 +21,7 @@ #include #include +#include #include #include diff --git a/src/loc/writer.h b/src/loc/writer.h index d99969c..96d14ac 100644 --- a/src/loc/writer.h +++ b/src/loc/writer.h @@ -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); diff --git a/src/python/database.c b/src/python/database.c index f6247cf..9bd938c 100644 --- a/src/python/database.c +++ b/src/python/database.c @@ -71,8 +71,27 @@ static PyObject* Database_repr(DatabaseObject* self) { return PyUnicode_FromFormat("", 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 }, diff --git a/src/python/location-query.in b/src/python/location-query.in index ed35d29..c455d1e 100644 --- a/src/python/location-query.in +++ b/src/python/location-query.in @@ -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 diff --git a/src/python/writer.c b/src/python/writer.c index ecb6882..611b34f 100644 --- a/src/python/writer.c +++ b/src/python/writer.c @@ -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; diff --git a/src/test-as.c b/src/test-as.c index df325ba..b531173 100644 --- a/src/test-as.c +++ b/src/test-as.c @@ -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); diff --git a/src/test-country.c b/src/test-country.c index 2f2bd98..4383695 100644 --- a/src/test-country.c +++ b/src/test-country.c @@ -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); diff --git a/src/test-database.c b/src/test-database.c index 74e83ee..9e16608 100644 --- a/src/test-database.c +++ b/src/test-database.c @@ -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; } diff --git a/src/test-network.c b/src/test-network.c index a9f2f63..9a74566 100644 --- a/src/test-network.c +++ b/src/test-network.c @@ -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); diff --git a/src/writer.c b/src/writer.c index e303969..0a4c408 100644 --- a/src/writer.c +++ b/src/writer.c @@ -14,6 +14,7 @@ Lesser General Public License for more details. */ +#include #include #include #include @@ -25,10 +26,16 @@ # include #endif +#include +#include +#include +#include + #include #include #include #include +#include #include #include #include @@ -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; } -- 2.39.2