Craton HSM

OpenSSL Backend

OpenSSL Backend

The craton-hsm-openssl crate implements the CryptoBackend trait over the system's OpenSSL 3 library via the openssl Rust bindings. If the linked OpenSSL build has the FIPS provider installed and active, every operation dispatched through this backend runs inside that provider's validation boundary.

  • Crate: craton-hsm-openssl
  • Backend type: OpenSslBackend
  • License: BSL 1.1
  • MSRV: Rust 1.75
  • Status: production-ready

Choose this backend when you already have an OpenSSL 3 FIPS provider in production and need Craton HSM to reuse it, or when you want a non-FIPS OpenSSL build for development symmetry with an existing OpenSSL-based stack.

Build

cargo build -p craton-hsm-openssl

Requirements:

  • OpenSSL 3.x development headers and libraries at build time (libssl-dev / openssl-devel / vcpkg). OpenSSL 1.1.1 builds are best-effort only — the library is EOL upstream.
  • For FIPS operation, an OpenSSL 3 installation with the FIPS provider loaded and activated in openssl.cnf. This crate does not itself enable FIPS mode; that is a property of the linked OpenSSL build.

On macOS, point the build at Homebrew's OpenSSL:

export OPENSSL_DIR=$(brew --prefix openssl)

This crate exposes no Cargo features. Hardware acceleration (AES-NI, AVX) comes from whatever the linked OpenSSL build provides.

Algorithms

  • AES in GCM, CBC, CTR, and key-wrap modes (128 / 192 / 256-bit).
  • RSA PKCS#1 v1.5, RSA-PSS, and RSA-OAEP. Modulus size is clamped at a 2048-bit minimum and 8192-bit ceiling.
  • ECDSA on P-256 and P-384.
  • Ed25519 sign and verify.
  • ECDH on P-256 and P-384 with HKDF-SHA256 derivation (fixed salt matching the core crate's ECDH-HKDF contract).
  • SHA-2 (SHA-256, SHA-384, SHA-512) via the streaming DigestAccumulator.

Verification paths use subtle::ConstantTimeEq for constant-time comparisons, and private-key material is wrapped in Zeroizing buffers.

Usage

use craton_hsm_openssl::OpenSslBackend;
use craton_hsm::crypto::backend::CryptoBackend;

let backend = OpenSslBackend;

let key = backend.generate_aes_key(32, true)?;
let ct  = backend.aes_256_gcm_encrypt(key.as_ref(), b"plaintext")?;
# Ok::<(), craton_hsm::error::HsmError>(())

FIPS Provider Notes

FIPS compliance is a property of the underlying OpenSSL build, not of this crate. The crate does not call OSSL_PROVIDER_load on your behalf and does not force the FIPS property query string. Deployments targeting FIPS must:

  1. Install an OpenSSL 3 distribution with the FIPS provider present (fips.so / fips.dll).
  2. Enable FIPS in openssl.cnf so the default library context uses the FIPS provider.
  3. Ensure the process that loads craton-hsm-openssl inherits that configuration.

If those preconditions are not met, operations still succeed but the deployment is not FIPS-compliant regardless of what this crate does. For a self-contained FIPS boundary, use craton-hsm-awslc instead.

AES-GCM Nonce Safety

The backend generates random 96-bit nonces and enforces the NIST SP 800-38D birthday bound of 2^32 encryptions per key:

  • A per-process counter map keyed by SHA-256(key) tracks the encryption count for each key.
  • On exhaustion, the key is moved into a sticky poison set that cannot be cleared by reset_gcm_counter or evict_gcm_counters. Re-keying is the only way to continue.
  • The counter map has a soft cap of 1,000,000 entries. Poisoned entries are never evicted; if the map cannot be shrunk below the cap, encryption fails fast rather than silently re-arming a retired key.

For keys that outlive the process, use the file-backed journal:

use craton_hsm_openssl::OpenSslBackend;

let backend = OpenSslBackend::new_with_persistent_gcm_counter(
    "/var/lib/craton-hsm/gcm-counter.journal",
)?;
# Ok::<(), craton_hsm::error::HsmError>(())

Behaviour mirrors the craton-hsm-awslc journal: HMAC-SHA256 footer, fsync-batched 1024-count flushes, fail- closed on integrity mismatch, warn-and-accept on a torn write with no footer.

Limitations

  • No AAD for AES-GCM. The core CryptoBackend trait does not thread additional authenticated data through its API, so AAD cannot be supplied via this backend today.
  • RSA-OAEP uses MGF1 with the OAEP hash; label and MGF1 hash are not independently configurable.
  • RSA-PSS salt length is fixed at digest length (the RFC 8017 SHOULD value); configurable salt length is not exposed.
  • Not implemented: ChaCha20-Poly1305, AES-GCM-SIV, AES-CCM, AES-CMAC, HMAC, X25519, X448. These gaps live in the core trait and affect every backend.
  • The nonce counter defaults to per-process memory. Long-lived keys used across restarts need either rotation, a higher-level counter, or the persistent journal above.