← Week 1: HSM fundamentals + PKCS#11

Day 3: SoftHSM2 + cryptoki — First Steps

Phase 5 · August 28, 2026

← Week 1: HSM fundamentals + PKCS#11

Agenda (2–3 hours)

  • Setup (30 min): Install SoftHSM2; initialize a token; configure cryptoki
  • Build (90 min): hsm-demo list-slots and hsm-demo generate-key subcommands
  • Explore (30 min): Inspect key attributes; verify token persistence
← Week 1: HSM fundamentals + PKCS#11

SoftHSM2: A Software HSM for Development

SoftHSM2 is an open-source software-only PKCS#11 implementation.

It is NOT a real HSM (keys are stored on disk, not in hardware), but it:

  • Implements the full PKCS#11 v2.40 API
  • Behaves identically to a real HSM from your code's perspective
  • Lets you develop and test HSM workflows without hardware
  • Ships with most Linux distributions
# Install
sudo apt install softhsm2   # Debian/Ubuntu
brew install softhsm        # macOS

# Initialize a new token
softhsm2-util --init-token --slot 0 --label "dev-hsm" \
  --so-pin 1234567890 --pin 123456

# Verify
softhsm2-util --show-slots
← Week 1: HSM fundamentals + PKCS#11

Project Setup: hsm-demo

cargo new hsm-demo
cd hsm-demo
# Cargo.toml
[package]
name = "hsm-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
cryptoki  = "0.7"
clap      = { version = "4", features = ["derive"] }
hex       = "0.4"
sha2      = "0.10"
// src/main.rs
#[derive(Subcommand)]
enum Commands {
    ListSlots,
    GenerateKey { #[arg(long)] label: String },
    Sign { #[arg(long)] label: String, #[arg(long)] data: String },
    Verify { #[arg(long)] label: String, #[arg(long)] data: String,
              #[arg(long)] sig: String },
    KeyCeremony,
}
← Week 1: HSM fundamentals + PKCS#11

list-slots Subcommand

// src/slots.rs
use cryptoki::context::{CInitializeArgs, Pkcs11};

pub fn list_slots(lib_path: &str) -> anyhow::Result<()> {
    let pkcs11 = Pkcs11::new(lib_path)?;
    pkcs11.initialize(CInitializeArgs::OsThreads)?;

    let slots = pkcs11.get_slots_with_token()?;

    if slots.is_empty() {
        println!("No slots with tokens found.");
        return Ok(());
    }

    for slot in &slots {
        let info = pkcs11.get_slot_info(*slot)?;
        let token = pkcs11.get_token_info(*slot)?;

        println!("Slot {:?}", slot);
        println!("  Slot description: {}", info.slot_description().trim());
        println!("  Token label:      {}", token.label().trim());
        println!("  Token model:      {}", token.model().trim());
        println!("  Token serial:     {}", token.serial_number().trim());
        println!("  Flags: initialized={} login_required={}",
            token.token_initialized(), token.login_required());
        println!();
    }

    Ok(())
}
← Week 1: HSM fundamentals + PKCS#11

generate-key Subcommand

// src/keygen.rs
use cryptoki::context::Pkcs11;
use cryptoki::mechanism::Mechanism;
use cryptoki::object::{Attribute, KeyType, ObjectClass};
use cryptoki::session::UserType;
use cryptoki::types::AuthPin;

pub fn generate_ecdsa_key(
    pkcs11: &Pkcs11,
    slot: cryptoki::slot::Slot,
    pin: &str,
    label: &str,
) -> anyhow::Result<()> {
    let session = pkcs11.open_rw_session(slot)?;
    session.login(UserType::User, Some(&AuthPin::new(pin.into())))?;

    // P-256 OID: 1.2.840.10045.3.1.7
    let ec_params = hex::decode("06082a8648ce3d030107")?;

    let pub_template = vec![
        Attribute::Token(true),
        Attribute::Private(false),
        Attribute::Verify(true),
        Attribute::EcParams(ec_params.clone()),
        Attribute::Label(label.into()),
    ];
    let priv_template = vec![
        Attribute::Token(true),
        Attribute::Private(true),
        Attribute::Sensitive(true),
        Attribute::Extractable(false),
        Attribute::Sign(true),
        Attribute::Label(label.into()),
    ];

    let (pub_handle, _priv_handle) = session.generate_key_pair(
        &Mechanism::EccKeyPairGen,
        &pub_template,
        &priv_template,
    )?;

    println!("Generated ECDSA P-256 key pair, label: {label}");
    println!("Public key handle: {:?}", pub_handle);
    Ok(())
}
← Week 1: HSM fundamentals + PKCS#11

Finding Existing Keys

// Find a key by label
use cryptoki::object::{Attribute, ObjectClass};

fn find_private_key(
    session: &cryptoki::session::Session,
    label: &str,
) -> anyhow::Result<cryptoki::object::ObjectHandle> {
    let template = vec![
        Attribute::Class(ObjectClass::PRIVATE_KEY),
        Attribute::Label(label.into()),
        Attribute::Token(true),
    ];

    let handles = session.find_objects(&template)?;
    handles.into_iter().next()
        .ok_or_else(|| anyhow::anyhow!("key not found: {label}"))
}

This pattern — find by label + class — is how all HSM-backed signing tools
locate keys. The label is a string you set at generate_key_pair time.

← Week 1: HSM fundamentals + PKCS#11

Verifying Persistence

After generate-key, verify the key persists across processes:

# Generate key
cargo run -- generate-key --label ca-key-01

# Start a new process — key should still be there
cargo run -- list-slots

# Expected: label "ca-key-01" visible in the token
# Keys with CKA_TOKEN = true survive session/process termination

SoftHSM2 stores token objects in ~/.local/share/softhsm/tokens/ (Linux)
or /var/lib/softhsm/tokens/ (system-wide install).

Real CloudHSM: objects persist in the HSM cluster across sessions,
process restarts, and even EC2 reboots — this is the same behavior.

← Week 1: HSM fundamentals + PKCS#11

Challenge Assignment

Complete and test both subcommands:

  1. hsm-demo list-slots — prints all slots with token info
    Expected output: slot 0, label "dev-hsm", initialized, login_required

  2. hsm-demo generate-key --label my-ca-key — generates ECDSA P-256 key pair
    Expected output: "Generated ECDSA P-256 key pair, label: my-ca-key"

  3. Run list-slots again after generating — does the key count change?
    (Slot token info doesn't show key count, but softhsm2-util --show-slots
    should show the token is populated)

  4. Run softhsm2-util --show-objects --pin 123456 — does your key appear?

Document any unexpected behavior. If cryptoki gives unhelpful error codes,
look up the CKR_ constant in the PKCS#11 spec — error codes are informative.

← Week 1: HSM fundamentals + PKCS#11

Resources

  • SoftHSM2 config: /etc/softhsm/softhsm2.conf or ~/.config/softhsm2/softhsm2.conf
  • cryptoki examples: github.com/parallaxsecond/rust-cryptoki/tree/main/cryptoki/tests
  • P-256 OID in DER: 06 08 2a 86 48 ce 3d 03 01 07 (you'll need this for EcParams)
  • pkcs11-tool (OpenSC): pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --list-objects