From d94a7680397494a2c3b09533b873861f2a59c25d Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 9 Jul 2024 16:41:13 +1000 Subject: [PATCH 1/3] feat: Dockerfile to test the plugin --- .dockerignore | 10 +++ .gitignore | 4 + Dockerfile | 44 ++++++++++ README.md | 53 +++++++++-- docker/clean_volumes.sh | 3 + docker/scripts/entrypoint.sh | 88 +++++++++++++++++++ docker/volumes/config/besu-dev.toml | 19 ++++ docker/volumes/config/pkcs11-hsm-password.txt | 1 + docker/volumes/config/pkcs11-softhsm.cfg | 11 +++ .../besu/plugin/softhsm/Pkcs11HsmPlugin.java | 17 ++-- .../softhsm/Pkcs11PluginCliOptions.java | 2 +- .../softhsm/Pkcs11SecurityModuleService.java | 16 +++- 12 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 docker/clean_volumes.sh create mode 100644 docker/scripts/entrypoint.sh create mode 100644 docker/volumes/config/besu-dev.toml create mode 100644 docker/volumes/config/pkcs11-hsm-password.txt create mode 100644 docker/volumes/config/pkcs11-softhsm.cfg diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e783d81 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +# Copyright 2024, Usman Saleem. +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +# Exclude everything +* + +# Include specific files and directories needed for the build +!docker/scripts/entrypoint.sh +!Dockerfile +!build/libs/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index ab5e240..82718d3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,7 @@ build .idea + +# Ignore data and tokens in volume directory +docker/volumes/data +docker/volumes/tokens diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6c93d13 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,44 @@ +# syntax=docker/dockerfile:1 +# Copyright 2024, Usman Saleem. +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +# Start from the latest Hyperledger Besu image +FROM hyperledger/besu:latest + +# Switch to root to install packages +USER 0 + +# Install additional packages for SoftHSM2 and OpenSC +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + openssl \ + libssl3 \ + softhsm2 \ + opensc \ + gnutls-bin && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +# Create a directory for SoftHSM2 tokens. This can be overridden using a volume mount to persist. +RUN mkdir -p /var/lib/tokens && chmod 755 /var/lib/tokens && chown besu:besu /var/lib/tokens + +# Switch back to the besu user +USER besu + +# Update workdir to Besu home directory +WORKDIR /opt/besu + +# Set environment variables for SoftHSM2 configuration +ENV SOFTHSM2_CONF=/opt/besu/softhsm2.conf + +# Copy the PKCS11 plugin JAR to the plugins directory +COPY --chown=besu:besu ./build/libs/besu-pkcs11-plugin-*.jar ./plugins/ + +# Copy the initialization script +COPY --chown=besu:besu --chmod=755 ./docker/scripts/entrypoint.sh ./entrypoint.sh + +# Create a custom SoftHSM2 configuration file in besu home directory +RUN echo "directories.tokendir = /var/lib/tokens" > ./softhsm2.conf + +# Set the entrypoint to our new script +ENTRYPOINT ["/opt/besu/entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md index 5846c0e..79d2d4f 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,57 @@ The plugin jar will be available at `build/libs/besu-pkcs11-plugin-.jar Drop the `besu-pkcs11-plugin-.jar` in the `/plugins` folder under Besu installation. This plugin will expose following additional cli options: -`TBA` +```shell +--plugin-pkcs11-softhsm-config-path= + Path to the PKCS11 configuration file +--plugin-pkcs11-softhsm-key-alias= + Alias or label of the private key that is stored in the HSM +--plugin-pkcs11-softhsm-password-path= + Path to the file that contains password or PIN to access PKCS11 token +``` -## Linux SoftHSM Setup -Following steps are tested on Ubuntu 24.04 LTS. Install following packages. -`TBA` ## Docker setup -See Dockerfile for details. +- The plugin can be tested as a docker image. The provided `Dockerfile` is based on Besu's official docker image. +It installs following additional package to manage SECP256K1 private keys and SoftHSM: + +``` +apt-get install -y --no-install-recommends \ + openssl \ + libssl3 \ + softhsm2 \ + opensc \ + gnutls-bin +``` +- The Dockerfile uses `scripts/entrypoint.sh` as entrypoint. This script initializes SoftHSM and generates a private key +if required. +- The Dockerfile copies the plugin jar to `/plugins` folder. +- To persist SoftHSM data, a volume should be mounted to `/softhsm2`. The host directory should have ownership of userid 1000:1000. +- Decide the token/pin to use for SoftHSM. +- See [Besu documentation](https://besu.hyperledger.org/public-networks/get-started/install/run-docker-image) for further details about other docker options. +- Following is an example to build the docker image: +```shell +docker build --no-cache -t besu-pkcs11:latest . +``` +- To run Besu node for testing with SoftHSM, Following directories be mounted as volumes. +Change the path according to your requirements: + - `./docker/volumes/data` for Besu data. Will be mounted to `/var/lib/besu` + - `./docker/volumes/tokens` for SoftHSM data. Will be mounted to `/var/lib/tokens` + - `./docker/volumes/config` for Besu and PKCS11 config files. Will be mounted to `/etc/besu/config`. This directory already contains sample configurations. + +> [!NOTE] +> To initialize the SoftHSM tokens, the entrypoint script will attempt to generate a SECP256K1 private key and +> initialize SoftHSM on the first run. The SoftHSM `PIN` is defined in `./docker/volumes/config/pkcs11-hsm-password.txt`. +> The `SO_PIN` can be overridden via environment variable, however, it is not required once initialization is done. + +- To run the Besu node: +```shell +docker run --rm -it \ + -v ./docker/volumes/data:/var/lib/besu \ + -v ./docker/volumes/tokens:/var/lib/tokens \ + -v ./docker/volumes/config:/etc/besu/config \ + besu-pkcs11:latest --config-file=/etc/besu/config/besu-dev.toml +``` ## License diff --git a/docker/clean_volumes.sh b/docker/clean_volumes.sh new file mode 100755 index 0000000..c26e96a --- /dev/null +++ b/docker/clean_volumes.sh @@ -0,0 +1,3 @@ +#! /bin/sh +rm -rf ./volumes/data +rm -rf ./volumes/tokens \ No newline at end of file diff --git a/docker/scripts/entrypoint.sh b/docker/scripts/entrypoint.sh new file mode 100644 index 0000000..7fa6f53 --- /dev/null +++ b/docker/scripts/entrypoint.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# Copyright 2024, Usman Saleem. +# SPDX-License-Identifier: (Apache-2.0 OR MIT) + +# Set default values for PIN and SO_PIN +DEFAULT_PIN="test123" +DEFAULT_SO_PIN="sotest123" + +# Path to the PIN file +PIN_FILE="/etc/besu/config/pkcs11-hsm-password.txt" + +# Read PIN from file if it exists, otherwise use environment variable or default value +if [ -f "$PIN_FILE" ]; then + PIN=$(cat "$PIN_FILE") +else + PIN="${PIN:-$DEFAULT_PIN}" +fi + +# Use environment variables if set, otherwise use default values +SO_PIN="${SO_PIN:-$DEFAULT_SO_PIN}" + +# Set up cleanup trap +trap 'rm -f /tmp/ec-secp256k1-*.pem' EXIT + +# Check if SoftHSM module exists +SOFTHSM_MODULE="/usr/lib/softhsm/libsofthsm2.so" +if [ ! -f "$SOFTHSM_MODULE" ]; then + echo "SoftHSM module not found: $SOFTHSM_MODULE" + exit 1 +fi + +# Check if token already exists +if ! softhsm2-util --show-slots | grep -q "testtoken"; then + echo "Initializing SoftHSM token ..." + if ! softhsm2-util --init-token --slot 0 --label "testtoken" --pin "$PIN" --so-pin "$SO_PIN"; then + echo "Failed to initialize token" + exit 1 + fi + + echo "Generating SECP256K1 private key using openssl ..." + # Generating temporary SECP256K1 private key (-noout=not encoded) + if ! openssl ecparam -name secp256k1 -genkey -noout -out /tmp/ec-secp256k1-priv-key.pem; then + echo "Failed to generate private key" + exit 1 + fi + + # Generate public key from private key + if ! openssl ec -in /tmp/ec-secp256k1-priv-key.pem -pubout -out /tmp/ec-secp256k1-pub-key.pem; then + echo "Failed to generate public key" + exit 1 + fi + + # Generate a self-signed certificate + if ! openssl req -new -x509 -key /tmp/ec-secp256k1-priv-key.pem -out /tmp/ec-secp256k1-cert.pem -days 365 -subj '/CN=example.com'; then + echo "Failed to generate self-signed certificate" + exit 1 + fi + + echo "Importing openssl secp256k1 key into softhsm id: 1, label: testkey ..." + # Importing private key and cert in softhsm. Note we have to specify --usage-derive for ECDH key agreement to work + if ! pkcs11-tool --module "$SOFTHSM_MODULE" --login --pin "$PIN" \ + --write-object /tmp/ec-secp256k1-priv-key.pem --type privkey --usage-derive --id 1 --label "testkey" \ + --token-label "testtoken"; then + echo "Failed to import private key" + exit 1 + fi + + if ! pkcs11-tool --module "$SOFTHSM_MODULE" --login --pin "$PIN" \ + --write-object /tmp/ec-secp256k1-pub-key.pem --type pubkey --usage-derive --id 1 --label "testkey" \ + --token-label "testtoken"; then + echo "Failed to import public key" + exit 1 + fi + + if ! pkcs11-tool --module "$SOFTHSM_MODULE" --login --pin "$PIN" \ + --write-object /tmp/ec-secp256k1-cert.pem --type cert --id 1 --label "testkey" \ + --token-label "testtoken"; then + echo "Failed to import certificate" + exit 1 + fi + + echo "Token and keys initialized successfully." +else + echo "Token already exists. Skipping initialization." +fi + +# Launch Besu with the provided arguments +exec besu "$@" \ No newline at end of file diff --git a/docker/volumes/config/besu-dev.toml b/docker/volumes/config/besu-dev.toml new file mode 100644 index 0000000..7e87d87 --- /dev/null +++ b/docker/volumes/config/besu-dev.toml @@ -0,0 +1,19 @@ +network="dev" +miner-enabled=true +miner-coinbase="0xfe3b557e8fb62b89f4916b721be55ceb828dbd73" +rpc-http-cors-origins=["all"] +host-allowlist=["*"] +rpc-ws-enabled=true +rpc-http-enabled=true +data-path="/var/lib/besu" + +# plugins options +plugin-pkcs11-hsm-config-path="/etc/besu/config/pkcs11-softhsm.cfg" +plugin-pkcs11-hsm-key-alias="testkey" +plugin-pkcs11-hsm-password-path="/etc/besu/config/pkcs11-hsm-password.txt" + +# security module +security-module="pkcs11-hsm" + +# Logging +logging="DEBUG" \ No newline at end of file diff --git a/docker/volumes/config/pkcs11-hsm-password.txt b/docker/volumes/config/pkcs11-hsm-password.txt new file mode 100644 index 0000000..274c005 --- /dev/null +++ b/docker/volumes/config/pkcs11-hsm-password.txt @@ -0,0 +1 @@ +1234 \ No newline at end of file diff --git a/docker/volumes/config/pkcs11-softhsm.cfg b/docker/volumes/config/pkcs11-softhsm.cfg new file mode 100644 index 0000000..a4e620f --- /dev/null +++ b/docker/volumes/config/pkcs11-softhsm.cfg @@ -0,0 +1,11 @@ +name = Softhsm-Besu-SM +library = /usr/lib/softhsm/libsofthsm2.so +# Instead of slot = xxx, use slotListIndex +slotListIndex = 0 +showInfo = false + +# In order for ECDHA Key Agreement to work, we need following for derived secrets +attributes(generate,CKO_SECRET_KEY,CKK_GENERIC_SECRET) = { + CKA_SENSITIVE = false + CKA_EXTRACTABLE = true +} \ No newline at end of file diff --git a/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11HsmPlugin.java b/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11HsmPlugin.java index 18f095f..599c5d8 100644 --- a/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11HsmPlugin.java +++ b/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11HsmPlugin.java @@ -7,6 +7,7 @@ import org.hyperledger.besu.plugin.BesuPlugin; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.SecurityModuleService; +import org.hyperledger.besu.plugin.services.securitymodule.SecurityModule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,11 +49,17 @@ private void registerCliOptions(final BesuContext besuContext) { */ private void registerSecurityModule(final BesuContext besuContext) { // lazy-init our security module implementation during register phase - besuContext - .getService(SecurityModuleService.class) - .orElseThrow( - () -> new IllegalStateException("Expecting SecurityModuleService to be present")) - .register(SECURITY_MODULE_NAME, () -> new Pkcs11SecurityModuleService(cliParams)); + final SecurityModuleService securityModuleService = + besuContext + .getService(SecurityModuleService.class) + .orElseThrow( + () -> new IllegalStateException("Expecting SecurityModuleService to be present")); + + securityModuleService.register(SECURITY_MODULE_NAME, this::getSecurityModuleSupplier); + } + + private SecurityModule getSecurityModuleSupplier() { + return new Pkcs11SecurityModuleService(cliParams); } @Override diff --git a/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11PluginCliOptions.java b/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11PluginCliOptions.java index d616716..6b05464 100644 --- a/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11PluginCliOptions.java +++ b/src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11PluginCliOptions.java @@ -27,7 +27,7 @@ public class Pkcs11PluginCliOptions { names = "--plugin-" + SECURITY_MODULE_NAME + "-key-alias", description = "Alias or label of the private key that is stored in the HSM", required = true, - paramLabel = "") + paramLabel = "