Skip to content

Commit

Permalink
feat: Besu plugin that provides a PKCS11 based security module (#9)
Browse files Browse the repository at this point in the history
* build: Add hyperledger jfrog maven repository for Besu
* doc: Update README with build instructions
* build: Add besu plugin dependency
* feat: Implement BesuPlugin interface
* feat: Add cli option and security module
* feat: Pkcs11 Security Module Service implementation
* chore: Throw SecurityModuleException
  • Loading branch information
usmansaleem authored Jul 8, 2024
1 parent 4184b33 commit 9f8b4d5
Show file tree
Hide file tree
Showing 11 changed files with 472 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## Copyright 2024, Usman Saleem.
## SPDX-License-Identifier: (Apache-2.0 OR MIT)
#
# https://help.github.com/articles/dealing-with-line-endings/
#
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## Copyright 2024, Usman Saleem.
## SPDX-License-Identifier: (Apache-2.0 OR MIT)

# Ignore Gradle project-specific cache directory
.gradle

Expand Down
30 changes: 26 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
# Besu Plugin - PKCS11 SoftHSM

A [Besu plugin](https://besu.hyperledger.org/private-networks/reference/plugin-api-interfaces) that shows how to
integrate with HSM with PKCS11 interface. SoftHSM is used as a test HSM.
A [Besu plugin][1] that provides a custom security module to load the [node key][2] from an HSM, such as [SoftHSM][3],
using PKCS11 libraries.

[1]: <https://besu.hyperledger.org/private-networks/reference/plugin-api-interfaces>
[2]: <https://besu.hyperledger.org/public-networks/concepts/node-keys>
[3]: <https://www.opendnssec.org/softhsm/>

![GitHub Actions Workflow Status](https://github.com/usmansaleem/besu-pkcs11-plugin/actions/workflows/ci.yml/badge.svg?branch=main)
![GitHub Release](https://img.shields.io/github/v/release/usmansaleem/besu-pkcs11-plugin?include_prereleases)

## Build Instructions
You can either use pre-built jar from Assets section in [releases](https://github.com/usmansaleem/besu-pkcs11-plugin/releases)
or build it yourself.

> [!NOTE]
> This project requires Java 21 or later. If it is not available, the gradle build will attempt to download one and use it.
- Check [Besu releases](https://github.com/hyperledger/besu/releases) for latest stable version and update it in
[`gradle/libs.versions.toml`](gradle/libs.versions.toml). For example:

```toml
[versions]
besu = "24.6.0"
```

- Build the plugin:

```shell
./gradlew clean build
```

The plugin jar will be available at `build/libs/besu-pkcs11-plugin-<version>.jar`.

## Usage

Drop the `jar` in the `/plugins` folder under Besu installation. This plugin will expose following additional cli
options:
Drop the `besu-pkcs11-plugin-<version>.jar` in the `/plugins` folder under Besu installation. This plugin will expose
following additional cli options:
`TBA`

## Linux SoftHSM Setup
Expand Down
26 changes: 21 additions & 5 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright 2024, Usman Saleem.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
import org.jreleaser.model.Active
import org.jreleaser.model.Distribution
import org.jreleaser.model.UpdateSection
Expand All @@ -14,16 +16,30 @@ project.group = "info.usmans.tools"
repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()

// For Besu plugin dependencies
maven {
url = uri("https://hyperledger.jfrog.io/artifactory/besu-maven/")
content { includeGroupByRegex("org\\.hyperledger\\.besu($|\\..*)") }
}
}

dependencies {
// Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter)
// This project jar is not supposed to be used as compilation dependency.
// `api` is used here to distinguish between dependencies which should be used IF it is to be used
// as a dependency during compiling some other library that depends on this project.
api(libs.besu.plugin.api)
api(libs.bcprov)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// https://github.com/google/auto/tree/main/service
annotationProcessor(libs.google.auto.service)
implementation(libs.google.auto.service.annotations)
implementation(libs.slf4j.api)
implementation(libs.picocli)

// This dependency is exported to consumers, that is to say found on their compile classpath.
api(libs.bcprov)
// testing dependencies
testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

// Apply a specific Java toolchain to ease working on different environments.
Expand Down
9 changes: 8 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

[versions]
besu = "24.6.0"
picocli = "4.7.5" # same version as in Besu
google-auto-service = "1.1.1"
junit-jupiter = "5.10.2"
spotless = "6.25.0"
slf4j = "2.0.13"
Expand All @@ -12,10 +15,14 @@ jgitver = "0.10.0-rc03"
jreleaser = "1.13.1"

[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
besu-plugin-api = {module = "org.hyperledger.besu:plugin-api", version.ref = "besu"}
google-auto-service = { module = "com.google.auto.service:auto-service", version.ref = "google-auto-service" }
google-auto-service-annotations = { module = "com.google.auto.service:auto-service-annotations", version.ref = "google-auto-service" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
bcprov = { module = "org.bouncycastle:bcprov-jdk18on", version.ref = "bouncy-castle" }
picocli = {module = "info.picocli:picocli", version.ref = "picocli"}
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

[plugins]
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright 2024, Usman Saleem.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
plugins {
// Apply the foojay-resolver plugin to allow automatic download of JDKs
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
Expand Down

This file was deleted.

67 changes: 67 additions & 0 deletions src/main/java/info/usmans/besu/plugin/softhsm/Pkcs11HsmPlugin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright 2024, Usman Saleem.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
package info.usmans.besu.plugin.softhsm;

import com.google.auto.service.AutoService;
import org.hyperledger.besu.plugin.BesuContext;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.plugin.services.PicoCLIOptions;
import org.hyperledger.besu.plugin.services.SecurityModuleService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Besu plugin that provides a custom security module to load the node key from an HSM using
* PKCS11 libraries.
*/
@AutoService(BesuPlugin.class)
public class Pkcs11HsmPlugin implements BesuPlugin {
static final String SECURITY_MODULE_NAME = "pkcs11-hsm";
private static final Logger LOG = LoggerFactory.getLogger(Pkcs11HsmPlugin.class);
private final Pkcs11PluginCliOptions cliParams = new Pkcs11PluginCliOptions();

@Override
public void register(final BesuContext besuContext) {
LOG.info("Registering plugin ...");
registerCliOptions(besuContext);
registerSecurityModule(besuContext);
}

/**
* Registers {@code Pkcs11PluginCliOptions} with {@code PicoCLIOptions} service provided by {@code
* BesuContext}.
*
* @param besuContext An instance of {@code BesuContext}
*/
private void registerCliOptions(final BesuContext besuContext) {
besuContext
.getService(PicoCLIOptions.class)
.orElseThrow(() -> new IllegalStateException("Expecting PicoCLIOptions to be present"))
.addPicoCLIOptions(SECURITY_MODULE_NAME, cliParams);
}

/**
* Registers {@code Pkcs11SecurityModule} with the {@code SecurityModuleService} service provided
* by {@code BesuContext}.
*
* @param besuContext An instance of {@code 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));
}

@Override
public void start() {
LOG.debug("Starting plugin ...");
}

@Override
public void stop() {
LOG.debug("Stopping plugin ...");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2024, Usman Saleem.
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
package info.usmans.besu.plugin.softhsm;

import static info.usmans.besu.plugin.softhsm.Pkcs11HsmPlugin.SECURITY_MODULE_NAME;

import java.nio.file.Path;
import picocli.CommandLine.Option;

/** Represents cli options that are required by the Besu PKCS11-SoftHSM plugin. */
public class Pkcs11PluginCliOptions {
@Option(
names = "--plugin-" + SECURITY_MODULE_NAME + "-config-path",
description = "Path to the PKCS11 configuration file",
required = true,
paramLabel = "<path>")
private Path pkcs11ConfigPath;

@Option(
names = "--plugin-" + SECURITY_MODULE_NAME + "-password-path",
description = "Path to the file that contains password or PIN to access PKCS11 token",
required = true,
paramLabel = "<path>")
private Path pkcs11PasswordPath;

@Option(
names = "--plugin-" + SECURITY_MODULE_NAME + "-key-alias",
description = "Alias or label of the private key that is stored in the HSM",
required = true,
paramLabel = "<path>")
private String privateKeyAlias;

/** Default constructor. Performs no initialization. */
public Pkcs11PluginCliOptions() {}

/**
* Constructor that initializes the PKCS11 configuration file path.
*
* @param pkcs11ConfigPath the path to the PKCS11 configuration file
*/
public Pkcs11PluginCliOptions(final Path pkcs11ConfigPath) {
this.pkcs11ConfigPath = pkcs11ConfigPath;
}

/**
* Returns the path to the PKCS11 configuration file.
*
* @return the path to the PKCS11 configuration file
*/
public Path getPkcs11ConfigPath() {
return pkcs11ConfigPath;
}

/**
* Returns the path to the file that contains the password or PIN to access the PKCS11 token.
*
* @return the path to the file that contains the password or PIN to access the PKCS11 token
*/
public Path getPkcs11PasswordPath() {
return pkcs11PasswordPath;
}

/**
* Returns the alias or label of the private key that is stored in the HSM.
*
* @return the alias or label of the private key that is stored in the HSM
*/
public String getPrivateKeyAlias() {
return privateKeyAlias;
}
}
Loading

0 comments on commit 9f8b4d5

Please sign in to comment.