This repo lays the groundwork for setting up and interacting with the YubiHSM2 FIPS using Golang. It also documents architectural trade-offs and technical challenges encountered during the process.
The YubiHSM2 FIPS, recognized for its cost-effectiveness, small form-factor, and extensive developer & community features, is a certified device adhering to the stringent Federal Information Processing Standards (FIPS). This certification underscores its reliability and security, making it a good choice for many applications (e.g., blockchain validators, filesystem encryption, etc).
A Hardware Security Module (HSM) is a physical computing device that safeguards and manages digital keys for strong authentication and provides crypto-processing. HSM traditionally comes in the form of a plug-in cards or an external device that attaches directly to a computer or network server.
The main purpose of an HSM is to secure cryptographic keys and operations within the device, offering a higher level of security than software-based management because the keys are less susceptible to theft or unauthorized access.
👉 HSMs are specifically designed to protect the lifecycle of cryptographic keys. Their tamper-resistant physical design ensures that sensitive keys are never exposed outside the module.
👉 FIPS (Federal Information Processing Standards) is developed by NIST (National Institute of Standards and Technology), part of the U.S. Department of Commerce. FIPS standards are issued to establish requirements for various purposes, such as ensuring computer security and interoperability.
👉 YubiHSM2 FIPS product is certified with FIPS 140-2, Level 3.
First, install the yubihsm2-sdk
.
You can fetch the SDK package, including all tools, from here: https://developers.yubico.com/YubiHSM2/Releases/
To install (brew
):
brew install yubihsm2-sdk
If you don't know where the yubihsm_pkcs11.dylib
is located, you can use:
sudo find /usr -name "yubihsm_*.dylib" -print
Visit the SDK release website and select the package that is aligned to your Linux distro and version. For example, for Ubuntu 22.04, choose the file yubihsm2-sdk-2023-11-ubuntu2204-amd64.tar.gz
.
After downloading, extract it:
tar -xvzf yubihsm2-sdk-2023-11-ubuntu2204-amd64.tar.gz
cd yubihsm2-sdk
To install:
apt --fix-broken -y install $(ls ./*.deb | grep -v './libyubihsm-dev')
This command installs all *.deb
files except the libyubihsm-dev
one, which is not strictly necessary.
You might need to add a udev
rule. Create the file /etc/udev/rules.d/
and add the following content:
# This udev file should be used with udev 188 and newer
ACTION!="add|change", GOTO="yubihsm2_connector_end"
# Yubico YubiHSM 2
# The OWNER attribute here has to match the uid of the process running the Connector
SUBSYSTEM=="usb", ATTRS{idVendor}=="1050", ATTRS{idProduct}=="0030", OWNER="yubihsm-connector"
LABEL="yubihsm2_connector_end"
To better understand the role of all components and how they communicate, the following picture depicts a high-level architecture.
Important points:
- The logical representation of nodes (i.e., machines) is just an example. All components placed within
Node N
,Node C1
, andNode C2
, can be on a single node/machine. The separation between 3 different nodes is just for clarity -- highlighting thatClient 2
doesn't require, e.g.,yubihsm-shell
, and thatClient 1
doesn't need, e.g.,yubihsm_pkcs11.so
, and so on. - The native libraries (green), the connector (purple), and
Client 1
(blue) are included in the YubiHSM2 SDK. - An advantage of using the PKCS#11 standard to communicate with an HSM is interoperability. Therefore, it doesn't matter what exactly is behind the PKCS#11 interface since it can be easily replaced without affecting the application implementation.
From the developer portal at Yubico website:
The Connector is not a trusted component. Sessions are established cryptographically between the application and the YubiHSM 2 using a symmetric mutual authentication scheme that is both encrypted and authenticated.
An important aspect about connectivity, also highlighted at the Yubico developer's website:
The Connector is not required to run on the same host as the applications which access it. In that case the Connector should be configured to be listening on a different address and port rather than the default localhost:12345, making sure that the client has access.
To install:
yubihsm-connector install
Then, start it:
sudo yubihsm-connector --config yubihsm-connector-config.yaml start
Check if the yubihsm-connector
is running successfully using curl
:
curl -v http://localhost:12345/connector/status
You should see something like this, with a HTTP 200
response:
status=OK
serial=*
version=3.0.4
pid=10276
address=localhost
port=12345
-
Start the
yubihsm-shell
(interactive mode):yubihsm-shell
Connect and create a session:
yubihsm> connect Session keepalive set up to run every 15 seconds yubihsm> session open 1 password Created session 0
Then, for example, list all objects:
yubihsm> list objects 0 Found 1 object(s) id: 0x0001, type: authentication-key, algo: aes128-yubico-authentication, sequence: 0, label: DEFAULT AUTHKEY CHANGE THIS ASAP
-
You might want to run the
yubihsm-shell
non-interactively, specifying actions directly in the command line:yubihsm-shell --authkey=1 --password=password --outformat=hex --action=list-objects
Imagine that you just bought the YubiHSM2 FIPS and want to set-up the following:
- Generate an asymmetric key (secp256r1) that will never leave the HSM
- Enable this key to sign data
🚨IMPORTANT🚨: it'd save lots of time if you read Core Concepts before starting. Make sure you understand what an Object, Capability (including "Delegated Capabilities"), and Domain are.
Set up a new auth key:
yubihsm-shell --authkey 1 --password password --outformat base64 -a put-authentication-key -i 0x0002 -l hsm-go-test -d 2 -c generate-asymmetric-key,export-wrapped,get-pseudo-random,put-wrap-key,import-wrapped,delete-asymmetric-key,sign-ecdsa --delegated sign-ecdsa,exportable-under-wrap,export-wrapped,import-wrapped --new-password newpassword123
Details:
-i 0x0002
: we're setting the identifier of the object to0x0002
. If you leave the-i
param as0
, a new one will be automatically assigned.-d 2
: indicates this authentication key is only valid in domain2
.
🚨IMPORTANT🚨: delete the default authentication key (id: 0x0001) when configuring productive environments.
Generate the key (ecdsa
, secp256r1
):
yubihsm-shell --authkey 2 --password newpassword123 --outformat base64 -a generate-asymmetric-key -i 0 -l hsm-go-test-key1 -d 2 -c sign-ecdsa -A ecp256
Returns:
Generated Asymmetric key 0x13db
List all the objects (to check if the key was created):
yubihsm-shell --authkey 2 --password newpassword123 --outformat base64 -a list-objects
Sign the data from the data.txt
file (and output the signature to signature.b64
file):
cat data.txt | yubihsm-shell --authkey 2 --password newpassword123 --outformat base64 -a sign-ecdsa -i 0x13db -A ecdsa-sha256
Get the public key (and output result to the asymmetric_key.pub
file):
yubihsm-shell --authkey 2 --password newpassword123 --outformat base64 -a get-public-key -i 0x13db > asymmetric_key.pub
Convert the signature.b64
file to a binary format using base64
command-line tool, and output the result to the signature.bin
file:
-
If you're on MacOS:
base64 -d -i signature.b64 > signature.bin
-
If you're on Linux:
base64 -d signature.b64 > signature.bin
Verify the signature (from signature.bin
file) using openssl
:
openssl dgst -sha256 -signature signature.bin -verify asymmetric_key.pub data.txt
Ensure you review the .env.template
and the const
section of the main.go
file. To change any default values, the easiest approach is to create a .env
file and override the variables you need. All other values will use the defaults.
The Golang example code uses the PKCS#11 standard.
Run:
go run main.go
In summary, here's what main.go
does:
- Lists all objects (keys)
- Fetches the private key object (i.e., object identifier)
- Tests whether the private key value can be fetched (nooooo! 😅)
- Fetches the public key object (i.e., object identifier)
- Prints the public key (in hex and base64)
- Requests a signature from the HSM (using
data.txt
file) - Prints the signature (in hex and base64)
- Prints the curve based on the public key
- Verifies the signature based on the public key ✅ 🥳
🚨IMPORTANT🚨: DO NOT USE THIS CODE IN PRODUCTION. PLEASE. This is just an example and was created in the "quick and dirty" mode. 😅
- Clarify the optimal setup in terms of auth key, audit key, and wrap key. A superior setup might involve having 3 keys with distinct roles.
- Determine the best approach for setting up the wrap key and managing backups.
- Create a Docker image containing all the tools from the YubiHSM SDK.
Parts of this README were heavily inspired and based on the user guide provided by Yubico. Their documentation is exceptionally well-written. Rewriting some of the steps, which are scattered throughout their documentation, helped me better understand and clarify the details. I hope it does the same for you. 😉
While this project was pursued during my free time, I'd like to extend special thanks to AxLabs and NGD for providing me with the opportunity to experiment with the YubiHSM2 FIPS hardware. 🙏🥳