$(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
TESTS_CFLAGS = \
$(AM_CFLAGS) \
- -DLIBLOC_PRIVATE
+ -DLIBLOC_PRIVATE \
+ -DABS_SRCDIR=\"$(abs_srcdir)\"
TESTS = \
src/test-libloc \
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 \
--- /dev/null
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEILuXwOyh5xQM3KnGb50wk00Lc4I7Hl0wnTCLAbFKpFJQoAoGCCqGSM49
+AwEHoUQDQgAE9RG8+mTIvluN0b/gEyVtcffqHxaOlYDyxzPKaWZPS+3UcNN4lwm2
+F4Tt2oNCqcuGl5hsZFNV7GJbf17h3F8/vA==
+-----END EC PRIVATE KEY-----
--- /dev/null
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9RG8+mTIvluN0b/gEyVtcffqHxaO
+lYDyxzPKaWZPS+3UcNN4lwm2F4Tt2oNCqcuGl5hsZFNV7GJbf17h3F8/vA==
+-----END PUBLIC KEY-----
#!/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)
# include <endian.h>
#endif
+#include <openssl/err.h>
#include <openssl/evp.h>
+#include <openssl/pem.h>
#include <loc/libloc.h>
#include <loc/as.h>
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;
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);
loc_stringpool_unref(db->pool);
+ // Free signature
+ if (db->signature)
+ free(db->signature);
+
// Close database file
if (db->f)
fclose(db->f);
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);
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;
}
// Feed header into the hash
- EVP_DigestUpdate(mdctx, &header_v0, sizeof(header_v0));
+ EVP_DigestVerifyUpdate(mdctx, &header_v0, sizeof(header_v0));
break;
default:
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;
}
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);
#define LOC_DATABASE_PAGE_SIZE 4096
+#define LOC_SIGNATURE_MAX_LENGTH 4096
+
struct loc_database_magic {
char magic[7];
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 {
#include <netinet/in.h>
#include <stdbool.h>
+#include <stdio.h>
#include <syslog.h>
#include <loc/libloc.h>
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);
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;
{
"verify",
(PyCFunction)Database_verify,
- METH_NOARGS,
+ METH_VARARGS,
NULL,
},
{ NULL },
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"),
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
}
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) {
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;
// 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);
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);
// 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);
}
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);
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)
// 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);
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);
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");
loc_unref(ctx);
+ fclose(private_key);
+ fclose(public_key);
+
return EXIT_SUCCESS;
}
// 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);
// 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);
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>
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_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;
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;
}
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]);
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);
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';
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;
}