To interface to cryptographic keys stored on a YubiHSM 2 from Java code, we can use the SunPKCS11 provider.
This has the added benefit that we can write code that is independent of the specific HSM used, as long as the HSM has a PKCS#11 module available.
Apart from writing code, we need to configure all components correctly in order for the code to work correctly.
This includes the configuration of the YubiHSM 2 connector, Java keytool
, and the SunPKCS11 provider.
To illustrate, we will code a simple RSA signing example below.
Let’s assume we have a single YubiHSM 2 connected locally via USB.
Store the connector configuration in a file named yubihsm.conf
and point to it via the YUBIHSM_PKCS11_CONF
environment variable
so that the YubiHSM 2 PKCS#11 module will be able to find it:
echo "connector=yhusb://" > yubihsm.conf export YUBIHSM_PKCS11_CONF=yubihsm.conf
We will be using Java’s keytool
to manage keys and certificates on the YubiHSM 2.
For convenience, store the PKCS#11 configuration options in a file named keytool.config
:
cat keytool.config keytool.all = -keystore NONE -storetype PKCS11 -storepass 0001password -addProvider SunPKCS11 -providerArg ./sunpkcs11.conf
The file sunpkcs11.conf
is used to configure the PKCS#11 module we want to use,
and the PKCS#11 attributes we want to define for objects created or imported via the SunPKCS11 provider:
cat sunpkcs11.conf name = YubiHSM2 library = /usr/local/lib/pkcs11/yubihsm_pkcs11.dylib attributes(*, CKO_PRIVATE_KEY, CKK_RSA) = { CKA_SIGN=true }
Finally, we can create an RSA key pair and a self-signed certificate using Java’s keytool
.
keytool -conf keytool.config -genkey -alias rsaSign -keyalg RSA -dname CN=rsaSign Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 90 days for: CN=rsaSign
Note that when using keytool
, the keys are generated in software and subsequently imported into the YubiHSM 2.
To generate keys on the YubiHSM 2 itself, use the yubihsm-shell
tool.
The generated certificate should now be visible from keytool
.
For example:
keytool -conf keytool.config -list Keystore type: PKCS11 Keystore provider: SunPKCS11-YubiHSM2 Your keystore contains 1 entry rsaSign, PrivateKeyEntry, Certificate fingerprint (SHA-256): 02:50:E4:1B:D8:FB:1B:07:AB:8C:05:85:37:BD:FB:89:6F:57:1F:CC:86:EC:E5:F2:BE:61:76:68:38:58:F0:39
Now, let’s turn to coding. In this example, we will be signing files using the RSA private key stored on our YubiHSM 2.
The data to be signed is a simple text file, for instance:
date > datatosign
The code will need to do some file I/O, and use the JCA and JCE standards to generate and verify signatures. Let’s start with the necessary imports:
import java.nio.file.Path; import java.nio.file.Files; import java.io.FileOutputStream; import java.security.cert.X509Certificate; import java.security.*;
To keep things simple, we will not handle Exceptions here and define some hardcoded parameters:
public class RsaSignP11 { public static void main(String... argv) throws Exception { String pkcs11Conf = "sunpkcs11.conf"; String userPin = "0001password"; String keyAlias = "rsaSign"; String infile = "datatosign"; String outfile = "signature.bin"; // see below ... } }
Using hard-coded parameters is only to keep the example concise. Normally these would be command-line parameters or read from a configuration file. Passwords should never be hard-coded and are typically read from a terminal on demand.
To continue with our example code, first load and configure the SunPKCS11 provider:
Provider provider = Security.getProvider("SunPKCS11"); provider = provider.configure(pkcs11Conf); Security.addProvider(provider);
Load the PKCS11 KeyStore, authenticating with the User PIN:
KeyStore ks = KeyStore.getInstance("PKCS11", provider); ks.load(null, userPin.toCharArray());
Retrieve the private key, and sign the data read from the datatosign
file using the SHA256withRSA
algorithm:
PrivateKey privateKey = (PrivateKey) (ks.getKey(keyAlias, null)); Signature rsaSig = Signature.getInstance("SHA256withRSA"); rsaSig.initSign(privateKey); byte[] datatosign = Files.readAllBytes(Path.of(infile)); rsaSig.update(datatosign); byte[] sigBytes = rsaSig.sign();
Optionally, the signature can be stored in a signature file for others to verify:
new FileOutputStream(outfile).write(sigBytes);
While we are at it, let’s also verify if the signature generated in sigBytes
can be verified using the corresponding public Key.
First we need to extract the public key from the certificate pointed to by our rsaSign
alias:
X509Certificate cert = (X509Certificate) ks.getCertificate(keyAlias); PublicKey publicKey = cert.getPublicKey();
Again using the SHA256withRSA
algorithm, verify that the signature in sigBytes
matches
the data in datatosign
using our publicKey
:
rsaSig = Signature.getInstance("SHA256withRSA"); rsaSig.initVerify(publicKey); rsaSig.update(datatosign); assert rsaSig.verify(sigBytes) == true : "verify failed";
To test, compile the source:
javac RsaSignP11.java
Run the program:
java RsaSignP11 $
There is no output, meaning the assert
was passed without issues and the signatures verifies.
Debugging issues with HSM’s can be difficult. It may help to enable logging using the following JVM system properties:
For PKCS#11 keystore specific debugging info:
-Djava.security.debug=pkcs11keystore
For general SunPKCS11 provider debugging info:
-Djava.security.debug=sunpkcs11
Also, refer to the documentation on PKCS#11 with YubiHSM 2 for generating debug logs from the PKCS#11 module itself.