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-provideron 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-toolfrom 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
SunPKCS11on 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
SunPKCS11does not surface post-quantum mechanisms. For ML-DSA / ML-KEM / SLH-DSA usepkcs11-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.loadthrowsCKR_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 = truein[algorithms](the default).fips_approved_onlyleft atfalse(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_GenerateKeyPairfor 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
- PKCS#11 integrations — deeper coverage of OpenSSL engines and providers, Java
SunPKCS11, NSS, SSH agents, and third-party tooling. - Post-quantum cryptography — supported mechanisms, parameter sizes, and migration strategy.
- Operator runbook — backup, restore, PIN rotation, audit export.
- Troubleshooting — mapping PKCS#11 return codes to the likely root cause.