recover-keys.c

/* Written by positron in 2022.  GPLv3, I guess.

   This crude quickly-written program extract each *private* key found in a p≡p
   key database and copies it into a file named after the key FPR, suitable to
   be imported into GPG's key ring.

     gcc --static -Wall -ggdb -Og recover-keys.c -lsqlite3 -lm -ldl -o recover-keys

   Of course the program can work on copies or symlinks of the keys.db files --
   The -shm and -wal files should be made available to this as well.

   The program expects keys.db to be in the current working directory when it
   is run.

   One can do, for example,
     cp ~luca/.pEp/keys.db* .
*/

#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "sqlite3.h"

#define KEYS_DB_PATH  "keys.db"

int main (void)
{
    sqlite3 *db = NULL;
    sqlite3_stmt *stmt = NULL;
    int sql_status;

#define FATAL(...)              \
    do {                        \
        printf("" __VA_ARGS__); \
        goto error;             \
    } while (false);

#define CHECK_SQL_STATUS                                 \
    do {                                                 \
        if (sql_status != SQLITE_OK                      \
            && sql_status != SQLITE_ROW                  \
            && sql_status != SQLITE_DONE) {              \
            FATAL("SQL error: %s", sqlite3_errmsg(db));  \
        }                                                \
    } while (false)

    // Open DB.
    sql_status = sqlite3_open_v2(KEYS_DB_PATH,
                                 &db,
                                 SQLITE_OPEN_READONLY, NULL);
    CHECK_SQL_STATUS;

    // Prepare statement.
    sql_status
        = sqlite3_prepare_v2(db,
                             " SELECT K.primary_key, K.tpk, U.userid"
                             " FROM Keys K NATURAL JOIN Userids U"
                             " WHERE K.secret;",
                             -1,
                             & stmt,
                             NULL);
    CHECK_SQL_STATUS;

    // Execute the query..
    while (sql_status != SQLITE_DONE) {
        sql_status = sqlite3_step(stmt);
        CHECK_SQL_STATUS;
        if (sql_status == SQLITE_DONE)
            break;

        // We found a row.  Let us get key material from the database.
        const unsigned char *primary_key;
        const void *blob;
        size_t blob_length;
        const unsigned char *user_id;
        primary_key = sqlite3_column_text(stmt, 0);
        blob = sqlite3_column_blob(stmt, 1);
        blob_length = sqlite3_column_bytes(stmt, 1);
        user_id = sqlite3_column_text(stmt, 2);
        printf("Read primary_key %s with user-id %s, %lu-B blob\n", primary_key,
               (user_id != NULL ? (const char *) user_id : "(none)"),
               (unsigned long) blob_length);
        char *file_name = malloc(strlen((char *) primary_key) + 100);
        if (file_name == NULL)
            FATAL("malloc failed");
        sprintf(file_name, "KEY-%s", primary_key);

        // Write the key to a file named after its FPR.  This key is easy to
        // import into GPG's key ring, and I guess something similar in the
        // case of Sequoia.
        FILE *f = fopen(file_name, "w");
        if (f == NULL)
            FATAL("could not open %s", file_name);
        if (fwrite(blob, blob_length, 1, f) != 1)
            FATAL("fwrite failed");
        fclose(f);
        printf ("* Written file %s\n", file_name);
        free(file_name);
    }

    // Finalise.
    sqlite3_finalize(stmt);

    printf("Success.\n");
    return EXIT_SUCCESS;

 error:
    printf("\nFailed.\n");
    return EXIT_FAILURE;
}