+LOC_EXPORT int loc_database_verify(struct loc_database* db, FILE* f) {
+ // Cannot do this when no signature is available
+ if (!db->signature1 && !db->signature2) {
+ DEBUG(db->ctx, "No signature available to verify\n");
+ return 1;
+ }
+
+ // Start the stopwatch
+ clock_t start = clock();
+
+ // 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();
+
+ // Initialise hash function
+ r = EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey);
+ if (r != 1) {
+ ERROR(db->ctx, "Error initializing signature validation: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ r = 1;
+
+ goto CLEANUP;
+ }
+
+ // Reset file to start
+ rewind(db->f);
+
+ // Read magic
+ struct loc_database_magic magic;
+ fread(&magic, 1, sizeof(magic), db->f);
+
+ hexdump(db->ctx, &magic, sizeof(magic));
+
+ // Feed magic into the hash
+ r = EVP_DigestVerifyUpdate(mdctx, &magic, sizeof(magic));
+ if (r != 1) {
+ ERROR(db->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+ r = 1;
+
+ goto CLEANUP;
+ }
+
+ // Read the header
+ struct loc_database_header_v1 header_v1;
+ size_t bytes_read;
+
+ switch (db->version) {
+ case LOC_DATABASE_VERSION_1:
+ bytes_read = fread(&header_v1, 1, sizeof(header_v1), db->f);
+ if (bytes_read < sizeof(header_v1)) {
+ ERROR(db->ctx, "Could not read header\n");
+ r = 1;
+
+ goto CLEANUP;
+ }
+
+ // Clear signatures
+ memset(header_v1.signature1, '\0', sizeof(header_v1.signature1));
+ header_v1.signature1_length = 0;
+ memset(header_v1.signature2, '\0', sizeof(header_v1.signature2));
+ header_v1.signature2_length = 0;
+
+ hexdump(db->ctx, &header_v1, sizeof(header_v1));
+
+ // Feed header into the hash
+ r = EVP_DigestVerifyUpdate(mdctx, &header_v1, sizeof(header_v1));
+ if (r != 1) {
+ ERROR(db->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+ r = 1;
+
+ goto CLEANUP;
+ }
+ 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 64kB
+ char buffer[64 * 1024];
+
+ while (!feof(db->f)) {
+ bytes_read = fread(buffer, 1, sizeof(buffer), db->f);
+
+ hexdump(db->ctx, buffer, bytes_read);
+
+ r = EVP_DigestVerifyUpdate(mdctx, buffer, bytes_read);
+ if (r != 1) {
+ ERROR(db->ctx, "%s\n", ERR_error_string(ERR_get_error(), NULL));
+ r = 1;
+
+ goto CLEANUP;
+ }
+ }
+
+ // Check first signature
+ if (db->signature1) {
+ hexdump(db->ctx, db->signature1, db->signature1_length);
+
+ r = EVP_DigestVerifyFinal(mdctx,
+ (unsigned char*)db->signature1, db->signature1_length);
+
+ if (r == 0) {
+ DEBUG(db->ctx, "The first signature is invalid\n");
+ r = 1;
+ } else if (r == 1) {
+ DEBUG(db->ctx, "The first signature is valid\n");
+ r = 0;
+ } else {
+ ERROR(db->ctx, "Error verifying the first signature: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ r = -1;
+ }
+ }
+
+ // Check second signature only when the first one was invalid
+ if (r && db->signature2) {
+ hexdump(db->ctx, db->signature2, db->signature2_length);
+
+ r = EVP_DigestVerifyFinal(mdctx,
+ (unsigned char*)db->signature2, db->signature2_length);
+
+ if (r == 0) {
+ DEBUG(db->ctx, "The second signature is invalid\n");
+ r = 1;
+ } else if (r == 1) {
+ DEBUG(db->ctx, "The second signature is valid\n");
+ r = 0;
+ } else {
+ ERROR(db->ctx, "Error verifying the second signature: %s\n",
+ ERR_error_string(ERR_get_error(), NULL));
+ r = -1;
+ }
+ }
+
+ clock_t end = clock();
+ INFO(db->ctx, "Signature checked in %.4fms\n",
+ (double)(end - start) / CLOCKS_PER_SEC * 1000);
+
+CLEANUP:
+ // Cleanup
+ EVP_MD_CTX_free(mdctx);
+ EVP_PKEY_free(pkey);
+
+ return r;
+}
+