Craton HSM

Examples

Examples

Four end-to-end recipes against a Craton HSM token. Each one starts from an initialised token with a user PIN already set (see the Quickstart if you do not have one yet) and shows the exact commands, a short code snippet, the expected output shape, and where to look when something goes wrong.

All recipes use the same two environment variables:

export MODULE=/usr/local/lib/libcraton_hsm.so
export USER_PIN=87654321

RSA-2048 with OpenSSL

What it shows. Classical PKCS#1 v1.5 signing through OpenSSL 3's pkcs11-provider. This is the most common integration in CAs, code-signing pipelines, and TLS servers.

Prerequisites

  • OpenSSL 3.x.
  • pkcs11-provider installed (apt install openssl-pkcs11-provider on Debian / Ubuntu).
  • An RSA-2048 key on the token with label rsa-signer. Create one first:
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --keypairgen --key-type rsa:2048 --label rsa-signer --id 10

Minimal provider config

Save this as openssl-pkcs11.cnf:

openssl_conf = openssl_init

[openssl_init]
providers = provider_sect

[provider_sect]
default = default_sect
pkcs11  = pkcs11_sect

[default_sect]
activate = 1

[pkcs11_sect]
module                   = /usr/lib/x86_64-linux-gnu/ossl-modules/pkcs11.so
pkcs11-module-path       = /usr/local/lib/libcraton_hsm.so
pkcs11-module-token-pin  = 87654321
activate                 = 1

Commands

export OPENSSL_CONF=$PWD/openssl-pkcs11.cnf

# Extract the public key from the token.
openssl pkey \
    -in "pkcs11:object=rsa-signer;type=public" \
    -pubout -out rsa-signer.pub.pem

# Sign a file. The private key never leaves the token.
openssl dgst -sha256 -sign "pkcs11:object=rsa-signer;type=private" \
    -out message.sig message.txt

# Verify using the extracted public key (no HSM needed).
openssl dgst -sha256 -verify rsa-signer.pub.pem \
    -signature message.sig message.txt

Expected output

Verified OK

Troubleshooting pointer

If openssl prints could not load the shared library or lists no providers, the module path in the config is wrong or the token PIN is stale. See PKCS#11 integrations for the OpenSSL 1.1.x engine fallback and for common pkcs11: URI errors.

ECDSA P-256 with pkcs11-tool

What it shows. Raw ECDSA over a SHA-256 digest, driven entirely from the OpenSC pkcs11-tool CLI. Useful for scripts, smoke tests, and for integrators building on top of the C ABI.

Prerequisites

  • pkcs11-tool from OpenSC.
  • A user session against the token.

Commands

# Generate an EC P-256 key pair on the token.
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --keypairgen --key-type EC:secp256r1 \
    --label ec-signer --id 20

# Produce the digest off-box (raw ECDSA takes the hash, not the message).
printf 'hello, hsm' | openssl dgst -sha256 -binary > digest.bin

# Sign the digest with the token key.
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --sign --mechanism ECDSA \
    --id 20 \
    --input-file digest.bin --output-file digest.sig

# Verify through the same module (sanity check).
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --verify --mechanism ECDSA \
    --id 20 \
    --input-file digest.bin --signature-file digest.sig

Snippet — equivalent via the Rust cryptoki crate

use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::mechanism::Mechanism;
use cryptoki::session::UserType;
use cryptoki::types::AuthPin;

let pkcs11 = Pkcs11::new("/usr/local/lib/libcraton_hsm.so")?;
pkcs11.initialize(CInitializeArgs::OsThreads)?;

let slot = pkcs11.get_slots_with_token()?[0];
let session = pkcs11.open_rw_session(slot)?;
session.login(UserType::User, Some(&AuthPin::new("87654321".into())))?;

let sig = session.sign(&Mechanism::Ecdsa, ec_priv_handle, &digest)?;

Expected output

Using signature algorithm ECDSA
Signature is valid.

Troubleshooting pointer

A CKR_MECHANISM_INVALID here usually means FIPS approved mode is enabled and the caller asked for a mechanism outside the approved list. Check --list-mechanisms and the [algorithms] section of craton_hsm.toml.

AES-256-GCM with Java SunPKCS11

What it shows. Symmetric authenticated encryption from a JVM application using Java's built-in SunPKCS11 provider. The key is generated on the token and is never exported.

Prerequisites

  • JDK 11+ with SunPKCS11 on the classpath (it ships with OpenJDK).
  • The Craton HSM library at a known path.
  • A user PIN set on the token.

SunPKCS11 config (pkcs11.cfg)

name    = Craton HSM
library = /usr/local/lib/libcraton_hsm.so
slot    = 0

Snippet

import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;

Provider provider = Security.getProvider("SunPKCS11").configure("pkcs11.cfg");
Security.addProvider(provider);

KeyStore ks = KeyStore.getInstance("PKCS11", provider);
ks.load(null, "87654321".toCharArray());

// Generate the key inside the token. Extractable=false keeps it there.
KeyGenerator kg = KeyGenerator.getInstance("AES", provider);
kg.init(256);
SecretKey key = kg.generateKey();

byte[] nonce = new byte[12];
SecureRandom.getInstanceStrong().nextBytes(nonce);

Cipher c = Cipher.getInstance("AES/GCM/NoPadding", provider);
c.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(128, nonce));
byte[] ciphertext = c.doFinal("payload".getBytes());

System.out.printf("ct=%d bytes, tag included%n", ciphertext.length);

Expected output shape

ct=23 bytes, tag included

(7 bytes of plaintext plus the 16-byte GCM tag.)

Notes

  • SunPKCS11 does not surface post-quantum mechanisms. For ML-DSA / ML-KEM / SLH-DSA use pkcs11-tool, the native C ABI, or a PKCS#11 v3.0-aware wrapper.
  • Craton HSM enforces the SP 800-38D birthday bound per key: each AES-GCM key has its own nonce counter and refuses to encrypt after 2^31 operations on that key.
  • The token must be initialised and have a user PIN before the JVM loads the provider; otherwise KeyStore.load throws CKR_TOKEN_NOT_PRESENT.

Troubleshooting pointer

javax.crypto.ProviderException with CKR_CRYPTOKI_NOT_INITIALIZED inside a servlet container is almost always a fork-safety issue — see the fork-safety notes in First token. Broader integration guidance lives in PKCS#11 integrations.

Post-quantum ML-DSA-65

What it shows. A FIPS 204 module-lattice digital signature issued and verified through the PKCS#11 ABI. This mechanism has no analogue in SoftHSMv2.

Prerequisites

  • enable_pqc = true in [algorithms] (the default).
  • fips_approved_only left at false (ML-DSA is FIPS 204 but CMVP approval of the wrapped module is out of scope in Core; see the Post-quantum cryptography page).
  • A user session on the token.

Commands

# Generate an ML-DSA-65 key pair.
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --keypairgen --mechanism CKM_ML_DSA_KEY_PAIR_GEN \
    --key-type ML-DSA:65 \
    --label pqc-signer --id 30

# Sign an arbitrary-length message (ML-DSA consumes the message, not a digest).
printf 'post-quantum hello' > message.bin

pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --sign --mechanism CKM_ML_DSA \
    --id 30 \
    --input-file message.bin --output-file message.pqsig

# Verify.
pkcs11-tool --module $MODULE --login --pin $USER_PIN \
    --verify --mechanism CKM_ML_DSA \
    --id 30 \
    --input-file message.bin --signature-file message.pqsig

Expected output

Using signature algorithm CKM_ML_DSA
Signature is valid.

Snippet — the same flow inside a Rust application

use cryptoki::mechanism::Mechanism;

let mech = Mechanism::MlDsa;                          // CKM_ML_DSA
let (pub_h, priv_h) = session.generate_key_pair(
    &Mechanism::MlDsaKeyPairGen,
    &pub_template,
    &priv_template,
)?;
let sig = session.sign(&mech, priv_h, b"post-quantum hello")?;

Notes

  • ML-DSA signature sizes are substantial (ML-DSA-65 produces roughly 3309 bytes). Make sure any transport format you choose can carry them.
  • The PQC crates (ml-dsa, ml-kem, slh-dsa) are currently at release-candidate versions; the on-the-wire signature format is standardised (FIPS 204) but the Rust API may evolve before 1.0. See Post-quantum cryptography for the current support matrix and migration notes.
  • Pairwise consistency — a sign/verify roundtrip on the freshly generated key — is enforced inside C_GenerateKeyPair for all PQC mechanisms.

Troubleshooting pointer

If CKM_ML_DSA_KEY_PAIR_GEN is missing from --list-mechanisms, either enable_pqc = false or fips_approved_only = true in craton_hsm.toml. Toggle the flag and restart the host process so C_Initialize re-reads the config.

Where to go next