Skip to main content
The pkcs11-daemon is a persistent background service that bootstraps cryptographic key material at startup and exposes it for use by other system components. It coordinates three sub-components: a SQLite-backed Vault for key persistence, a Crypto engine for key generation, and an SDImporter for importing keys from removable storage.

Architecture overview

Vault

SQLite-backed key store. Persists key label, type, and PEM data to vault.db.

Crypto engine

Generates RSA 2048-bit keys via OpenSSL EVP. Post-quantum generation is stubbed.

SDImporter

Background thread that polls /media/sdcard02 for SD cards and imports their keys into the Vault.

Startup sequence

The daemon entry point in daemon.cpp runs the following sequence:
1

Open the Vault

daemon.cpp
Vault vault("vault.db");
vault.init();
Opens (or creates) vault.db and runs CREATE TABLE IF NOT EXISTS keys to ensure the schema exists.
2

Start the SD card watcher

daemon.cpp
SDImporter importer(vault);
std::thread sd_thread(&SDImporter::watch_ports, &importer);
Launches SDImporter::watch_ports on a detached background thread.
3

Generate default RSA key

daemon.cpp
std::string rsa = Crypto::generate_rsa();
vault.store_key("default-rsa", "rsa", rsa);
Generates a 2048-bit RSA key pair using OpenSSL and stores the PEM-encoded private key under the label "default-rsa".
4

Generate default PQC key

daemon.cpp
std::string pq = Crypto::pqc_generate();
vault.store_key("default-pq", "dilithium", pq);
Calls the post-quantum stub. Currently stores an empty string under the label "default-pq" with type "dilithium".
5

Enter the keep-alive loop

daemon.cpp
while (true) {
    std::cout << "daemon alive\n";
    std::this_thread::sleep_for(std::chrono::seconds(10));
}
The daemon runs indefinitely, printing a heartbeat every 10 seconds.

Vault

The Vault class (vault.h / vault.cpp) wraps a SQLite3 database.

Schema

CREATE TABLE IF NOT EXISTS keys (
    label TEXT PRIMARY KEY,
    type  TEXT,
    data  TEXT
);
ColumnTypeDescription
labelTEXT PRIMARY KEYHuman-readable key identifier (e.g., "default-rsa").
typeTEXTAlgorithm family string (e.g., "rsa", "dilithium").
dataTEXTPEM-encoded key material or empty string for stubs.

API

Vault::Vault
constructor
Opens the SQLite database at path. Uses sqlite3_open internally.Parameter: path (const std::string&) — filesystem path to the database file (e.g., "vault.db").
Vault::init
void
Runs the CREATE TABLE IF NOT EXISTS keys DDL statement. Safe to call on an existing database.
Vault::store_key
void
Inserts or replaces a key row using INSERT OR REPLACE INTO keys.Parameters: label — key identifier; type — algorithm string; data — key material.
Vault::get_key
std::string
Returns the data column for the given label, or an empty string if no matching row exists.Parameter: label (const std::string&) — key identifier to look up.
The current implementation builds SQL queries via string concatenation, which is vulnerable to SQL injection. Do not expose store_key or get_key to untrusted input without parameterised queries.

Crypto engine

The Crypto class (crypto.h / crypto.cpp) provides static factory methods for key generation.

generate_rsa()

Generates a 2048-bit RSA key pair using the OpenSSL EVP API and returns the PEM-encoded private key as a std::string.
crypto.cpp
std::string Crypto::generate_rsa() {
    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nullptr);
    // ... keygen_init, set_rsa_keygen_bits(2048), keygen ...
    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr);
    char* data;
    long len = BIO_get_mem_data(bio, &data);
    std::string pem(data, len);
    EVP_PKEY_free(pkey);
    BIO_free(bio);
    return pem;
}
Returns an empty string and logs to stderr on any OpenSSL failure.

pqc_generate()

pqc_generate() is not yet implemented. It logs "pqc_generate() not implemented yet" to stderr and returns an empty string. Integration with liboqs is planned.
crypto.cpp
std::string Crypto::pqc_generate() {
    std::cerr << "pqc_generate() not implemented yet\n";
    return "";
}

SDImporter

The SDImporter class (sd_import.h / sd_import.cpp) watches three SD card mount points in a background thread.

watch_ports()

Runs an infinite polling loop with a 10-second sleep between iterations. On each pass it checks whether any of the following paths exist using std::filesystem::exists:
  • /media/sdcard0
  • /media/sdcard1
  • /media/sdcard2
sd_import.cpp
void SDImporter::watch_ports() {
    while (true) {
        for (int port = 0; port < 3; port++) {
            std::string path = "/media/sdcard" + std::to_string(port);
            if (std::filesystem::exists(path)) {
                std::cout << "SD card detected at " << path << "\n";
                // TODO: safely import keys from SD card to vault
            }
        }
        std::this_thread::sleep_for(std::chrono::seconds(10));
    }
}
The actual key import logic is not yet implemented (marked TODO). Detection is working, but keys are not yet read from or written to the Vault from this path.

Build requirements

DependencyRequiredNotes
C++17 compilerYesstd::filesystem support required.
CMake 3.20+Yes
OpenSSL (libssl, libcrypto)YesUsed by Crypto::generate_rsa() via the EVP API.
SQLite3YesUsed by Vault for key persistence.
liboqsOptionalRequired for Crypto::pqc_generate() when implemented.
# Ubuntu/Debian
apt-get install libssl-dev libsqlite3-dev

# Configure and build
cmake -S pkcs11-daemon -B pkcs11-daemon/build
cmake --build pkcs11-daemon/build

Build docs developers (and LLMs) love