Skip to content

Latest commit

 

History

History
270 lines (200 loc) · 11.7 KB

File metadata and controls

270 lines (200 loc) · 11.7 KB

Besu HSM Plugin

License Besu Discord

A Hardware Security Module (HSM) plugin for Besu. This plugin abstracts Besu's node key operations by offloading the node private key into a physical HSM such as Thales Luna, AWS CloudHSM, or any PKCS#11-compliant HSM. The node key is used for devp2p peer handshakes (ECDH), peer discovery (DiscV4/DiscV5), and BFT consensus signing (QBFT/IBFT2). It is not used to sign transactions — transaction signing is handled separately by account keys.

Three provider modes are supported. The default is native-pkcs11, which works with any PKCS#11 HSM and is recommended for production. cloudhsm-jce is the production path for AWS CloudHSM, and sunpkcs11-jce is a dev/test option targeting SoftHSM2.

Providers

Pick the provider that matches your HSM. The CLI flag values listed below are what you pass to --plugin-hsm-provider-type.

  • native-pkcs11 (default, recommended for production) — Works with any PKCS#11 v2.40 HSM, including strict-spec HSMs such as Thales Luna and Utimaco GP HSM. No certificate is required alongside the private key on the token. Not currently compatible with AWS CloudHSM — see Known Limitations.
  • cloudhsm-jce (production, AWS CloudHSM only) — Uses the AWS CloudHSM JCE provider directly, with no PKCS#11 configuration file. Authenticates via the HSM_USER and HSM_PASSWORD environment variables.
  • sunpkcs11-jce (dev/test only) — Targets SoftHSM2 for local development and integration tests. ECDH compatibility with production HSMs is not guaranteed; most strict v2.40 HSMs reject the required peer-point format. Requires CKA_SENSITIVE=false on derived secrets and a self-signed certificate associated with the private key on the token. Production users should choose native-pkcs11 or cloudhsm-jce.

Architecture

Besu HSM Plugin Architecture

The plugin sits between Besu's validator process and the HSM, registering with Besu's SecurityModuleService. Provider selection, key aliases, and authentication are configured via plugin CLI options.

Installation

1. Get the plugin distribution

Download the pre-built zip from the GitHub Releases page.

Or build from source:

./gradlew distZip

The distribution zip is generated at build/distributions/besu-hsm-plugin-<version>.zip.

2. Install into Besu

Extract the zip and copy the plugin JAR into Besu's plugins/ directory:

unzip besu-hsm-plugin-<version>.zip
cp besu-hsm-plugin-<version>/besu-hsm-plugin-<version>.jar /path/to/besu/plugins/

Besu auto-discovers JARs placed in that directory at startup — no classpath flag is needed.

3. Enable the plugin

Start Besu with --security-module=hsm plus the provider-specific options described in the Plugin CLI Options section and the deployment guides below.

Deployment Guides

Full walkthroughs for setting up a QBFT validator network on each supported HSM, including key generation steps, are in:

Plugin CLI Options

The plugin registers the following CLI options with Besu:

Option Description Required
--plugin-hsm-provider-type Provider type: native-pkcs11 (default), cloudhsm-jce, or sunpkcs11-jce No
--plugin-hsm-config-path Path to the PKCS#11 configuration file native-pkcs11 and sunpkcs11-jce
--plugin-hsm-password-path Path to the file containing the token PIN/password native-pkcs11 and sunpkcs11-jce
--plugin-hsm-key-alias Alias/label of the private key on the HSM Yes
--plugin-hsm-public-key-alias Alias/label of the public key on the HSM cloudhsm-jce only
--plugin-hsm-cloudhsm-jar-path Path to CloudHSM JCE jar file or directory (default: /opt/cloudhsm/java) No
--plugin-hsm-ec-curve EC curve: secp256k1 (default) or secp256r1 No

native-pkcs11 example

besu --security-module=hsm \
  --plugin-hsm-config-path=/etc/besu/pkcs11.cfg \
  --plugin-hsm-password-path=/etc/besu/hsm-pin.txt \
  --plugin-hsm-key-alias=mykey

The PKCS#11 config file points the plugin at the vendor library and slot, e.g.:

library = /usr/safenet/lunaclient/lib/libCryptoki2_64.so
slot = 0

See the Thales Luna deployment guide for the full config-file format and a complete validator-setup walkthrough.

cloudhsm-jce example

The CloudHSM JCE jar is auto-discovered from /opt/cloudhsm/java/ by default; override with --plugin-hsm-cloudhsm-jar-path if needed.

export HSM_USER=besu_crypto_user
export HSM_PASSWORD=<password>

besu --security-module=hsm \
  --plugin-hsm-provider-type=cloudhsm-jce \
  --plugin-hsm-key-alias=my-private-key \
  --plugin-hsm-public-key-alias=my-public-key

Note: The CloudHSM JCE provider requires separate aliases for the private and public keys because CloudHSM does not associate certificates with key entries the way Java's SunPKCS11 KeyStore does.

See the AWS CloudHSM guides for cluster setup and a full QBFT walkthrough.

sunpkcs11-jce example (dev/test)

besu --security-module=hsm \
  --plugin-hsm-provider-type=sunpkcs11-jce \
  --plugin-hsm-config-path=/etc/besu/pkcs11.cfg \
  --plugin-hsm-password-path=/etc/besu/hsm-pin.txt \
  --plugin-hsm-key-alias=mykey

Certificate requirement: Java's SunPKCS11 KeyStore retrieves the public key via the certificate associated with the key alias (KeyStore.getCertificate()). The HSM must have a certificate stored alongside the private key — a self-signed certificate is sufficient.

ECDH key agreement: Besu uses ECDH for devp2p handshakes. For this to work through SunPKCS11, the PKCS#11 configuration file must allow Java to extract the derived shared secret. Add the following to your configuration file:

attributes(generate,CKO_SECRET_KEY,CKK_GENERIC_SECRET) = {
  CKA_SENSITIVE = false
  CKA_EXTRACTABLE = true
}

See docker/softhsm2/config/pkcs11-softhsm.cfg for a complete example. Most production HSMs reject these attributes — use native-pkcs11 or cloudhsm-jce instead.

Experimental: secp256r1 Curve Support

The plugin supports the secp256r1 (NIST P-256) elliptic curve as an alternative to the default secp256k1, controlled by the --plugin-hsm-ec-curve CLI option:

--plugin-hsm-ec-curve=secp256r1

Besu itself must also be configured to use secp256r1 via the ecCurve field in the genesis file. See the Besu documentation on alternative elliptic curves for details.

Note: Alternative elliptic curve support in Besu is experimental. The secp256r1 curve has been tested with SoftHSM2 in a 4-node QBFT network.

Known Limitations

DiscV5 (Discovery v5) requires secp256k1

DiscV5 is supported with HSM-backed keys, but only on the secp256k1 curve. Besu's DiscV5 implementation (and the underlying ENR v4 identity scheme) is fixed to secp256k1, so HSM keys on the secp256r1 curve cannot participate in DiscV5 peer discovery. Use DiscV4 (--bootnodes) or static peering (--static-nodes-file) for secp256r1 deployments.

PKCS#11's CKM_ECDH1_DERIVE returns only the x-coordinate of the ECDH shared point, so the plugin recovers the y-parity needed for SEC1-compressed encoding via a second ECDH against a probe point (Q + G). This costs one extra HSM round-trip per DiscV5 handshake.

native-pkcs11 is not compatible with AWS CloudHSM

native-pkcs11's ECDH recipe relies on extracting the derived shared secret out of the HSM through standard PKCS#11 — either by CKA_SENSITIVE=false + C_GetAttributeValue(CKA_VALUE), or by the wrap-then-decrypt-with-AES-KEK encrypt-decrypt-oracle pattern. AWS CloudHSM rejects all of these paths: it doesn't permit non-sensitive ECDH-derived secrets, doesn't permit C_GetAttributeValue(CKA_VALUE) on a sensitive secret, doesn't accept CKM_AES_CBC for wrap, and rejects the CKM_AES_GCM wrap params struct in any layout we tried.

cloudhsm-jce works on CloudHSM because it uses CloudHSM's proprietary CKA-extension protocol to retrieve the derived value — that's a CloudHSM-specific path that doesn't exist in standard PKCS#11. Reproducing it from a vendor-neutral FFM binding would mean depending on CloudHSM's private SDK, which defeats the purpose of native-pkcs11.

Use cloudhsm-jce for AWS CloudHSM. native-pkcs11 is intended for Thales Luna and any other HSM whose PKCS#11 implementation supports the standard wrap-decrypt-oracle pattern (sign will work on CloudHSM via native-pkcs11, but ECDH — and therefore peer handshake — will fail, so a validator can't actually run).

Useful Links

If you have any questions, queries or comments, Besu channel on Discord is the place to find us.

Development

Prerequisites

  • Java 25+ — the plugin uses the Java 25 Foreign Function & Memory API for the native-pkcs11 provider.

Building

./gradlew build

Running Tests

# Unit tests
./gradlew test

# Integration tests (requires Docker)
./gradlew integrationTest

Container logs: Integration tests are quiet by default. To dump full container logs to stderr — useful when an integration test fails locally, or when re-running a failed GitHub Actions job with Enable debug logging — set RUNNER_DEBUG=1:

RUNNER_DEBUG=1 ./gradlew integrationTest

Note: Integration tests build the SoftHSM2 image from docker/softhsm2/Dockerfile, pinning hyperledger/besu to the version declared in gradle/libs.versions.toml (besu = "..."). Bumping the catalog automatically updates the integration-test image.