This article shows in detail how to use OpenSSL to sign critical data with an network HSM. The data can be sensitive log data, license data, or any other data where integrity on transfer must be ensured.
Recently I was challenged in a project to sign some data using OpenSSL on an HSM. In my case it is license data. The data resides on a Linux host, that does not have a hardware security module available.
After short research I found that the Sematicon AG se.SAM N200 Crypto Appliance is available for remote evaluation. Business users can request an evaluation from the Sematicon website. The se.SAM N200 Crypto Appliance is an easy-to-use network HSM offering a PKCS11 interface for OpenSSL 1.1.1 and OpenSSL 3.0 and can sign any data using PKCS11 or REST-API.
Create a key to use for signing
First, I need an HSM secured key that can be used for OpenSSL signing. Therefore, login to the HSM admin GUI and create a key.
The N200 HSM offers various cryptographic signing keys:
- RSA keys e.g., a RSA 4096 bit key
- ECC keys like NIST, SECP, Brainpool up to 521 bits
I choose a very common NIST 256 key also known as secp256r1 or prime256v1 curve. The HSM offers you a convenient web GUI where you just press the ADD KEY button and let you inspire from the well documented options.
After generating the ECC NIST key (please keep the key PIN in mind!) I need to setup the PKCS11 interface on the Linux host to remotely connect to the HSM key.
Setting up the remote PKCS11 interface
My Linux host where I want to sign data is connected to the HSM (in my case over the Internet), so I need to setup the PKCS11 interface on my Linux host. These are the steps required:
- Install a few required packages
- Download the pkcs11 library on the Linux host
- Setup the N200.conf configuration file
- Adopt the openssl.cnf configuration file
- Query the correct PKCS11 uri
- Finally, sign data with OpenSSL
- Complexity considerations
1. Install a few required packages
Install the following packages
- openssl 1.1.1 or openssl 3.0 -> typically, installed already, required to perform the data signing
- libengine-pkcs11 (or sometime libengine-pkcs11-openssl) -> the PKCS11 engine for OpenSSL, a library that allows smart card or HSM keys to be used with OpenSSL
- gnutls-bin -> required for the command line tool p11tool to query the PKCS11 uris of the HSM connection
- optionally: opensc -> provides the tool pkcs11-tool to perform various checks with the PKCS11 library e.g., read slots, token and key details
2. Download the pkcs11 library on the Linux host
Ask your sematicon contact for the latest PKCS11 driver for your Linux system, they offer the following PKCS11 options:
For Intel/AMD x64 systems:
- pkcs11-sesam-x64-3.so -> the se.SAM PKCS11 library for OpenSSL 3.0
- pkcs11-sesam-x64-so -> the se.SAM PKCS11 library for OpenSSL 1.1.x
For ARM x64 systems like Raspberry PI 3 or 4:
- pkcs11-sesam-arm-3.so -> the se.SAM PKCS11 library for OpenSSL 3.0
- pkcs11-sesam-arm-so -> the se.SAM PKCS11 library for OpenSSL 1.1.x
I installed the pkcs11-sesam-x64-3.so library in /usr/lib/x86_64-linux-gnu/pkcs11/
3. Setup the N200.conf configuration file
The PKCS11 library requires a configuration file referring to the REST-API of the network HSM, therefore create the following file and replace the variables accordingly:
# vi /etc/n200.conf
{
„url“: „https://**hostname_or_ip_address**/n200/web/postv1“,
„user“: „**user-name**“,
„pass“: „**password-for-admin**“,
„core“: 1,
„keygen_pin“: „12345678“,
„log_level“: 10,
„log_output“: „/tmp/seSAM_p11.log“
}
You can verify if the se.SAM N200 pkcs11 library is working by installing the tool ‚pkcs11-tool‘ from the Linux package ‚opensc‘ (sometimes installable as ‚opensc-pkcs11‘)
$ pkcs11-tool –module /usr/lib/x86_64-linux-gnu/pkcs11/pkcs11-sesam-x64-3.so -T
Available slots:
Slot 0 (0xbf5a6b2): hsm0101231A9FC2EA1393EE0000178A
token label : 828191063
token manufacturer : sematiconAG
token model : N200 core
token flags : login required, rng, token initialized, PIN initialized, other flags=0x20
hardware version : 4.11
firmware version : 4.10
serial num : 200648370
pin min/max : 8/128
4. Adopt the openssl.cnf configuration file
The default OpenSSL configuration file resides in /etc/ssl/openssl.cnf, you can verify the location with the command „openssl version -a“, see line OPENSSLDIR.
Edit the file /etc/ssl/openssl.cnf and alter accordingly:
Add the following sections before the section „[new_oids]“:
[engine_section]
pkcs11 = pkcs11_section
# apt install libengine-pkcs11-openssl
[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-3/pkcs11.so
MODULE_PATH = /usr/lib/x86_64-linux-gnu/pkcs11/pkcs11-sesam-x64-so
init = 0
In section „[default_conf]“ add the line „engines = engine_section“
[default_conf]
ssl_conf = ssl_sect
engines = engine_section
You don’t need to reboot, the next time you start openssl the updated configuration will be loaded.
Your OpenSSL config now can use the PKCS11 engine, to verify use the following command:
$ openssl engine pkcs11 -t
(pkcs11) pkcs11 engine
[ available ]
5. Query the correct PKCS11 uri
In order to use the key with OpenSSL you need to refer the key using the pkcs11 notation. I used the p11-tool that is included in Linux package „gnutls-bin“.
First, I need to include the se.SAM N200 pkcs11 library to the modules of the p11tool. Therefore, create the file /usr/share/p11-kit/modules/sesam.module with the content below.
# vi /usr/share/p11-kit/modules/sesam.module
# This file describes how to load the pk11 module
# See: http://p11-glue.freedesktop.org/doc/p11-kit/config.html
module: /usr/lib/x86_64-linux-gnu/pkcs11/pkcs11-sesam-x64-so
Now I am able to query the PKCS11 uri (full reference see RFC7512) using the p11tool:
$ p11tool –list-token-urls
pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004
$ p11tool –list-all „pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004“
Object 0:
URL: pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004;id=%F9%0F%B8%E7%9E%60;object=735952479;type=public
Type: Public key (EC/ECDSA-SECP256R1)
Label: 735952479
Flags: CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
ID: f9:0f:b8:e7:9e:60
Object 1:
URL: pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004;id=%F9%0F%B8%E7%9E%60;object=735952479;type=private
Token ‚hsm0101231A9FC2EA1393EE00000004‘ with URL ‚pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004‘ requires user PIN
Enter PIN:
Type: Private key (EC/ECDSA-SECP256R1)
Label: 735952479
Flags: CKA_PRIVATE; CKA_NEVER_EXTRACTABLE; CKA_SENSITIVE;
ID: f9:0f:b8:e7:9e:60
Note the URI of the private key „pkcs11:model=N200%20core;manufacturer=sematiconAG;serial=201044558;token=hsm0101231A9FC2EA1393EE00000004;id=%F9%0F%B8%E7%9E%60;object=735952479;type=private“ as that URI string will be required for OpenSSL signing.
You can shorten the URI by removing the following references:
- model=…
- manufacturer=…
- id=…
- type=private
6. Finally, sign data with OpenSSL
Now I can sign the file ‚mydata‘ using the options „-engine pkcs11“ and „-keyform engine“:
$ openssl dgst -sha256 -engine pkcs11 -keyform engine -sign „pkcs11:serial=201044558;token=hsm0101231A9FC2EA1393EE00000004;object=735952479“ -out mydata.sig mydata
Engine „pkcs11“ set.
Enter PKCS#11 token PIN for hsm0101231A9FC2EA1393EE00000004:
If you see a binary signature with less than 1 KB the signature is successfully created!
$ hd mydata.sig
00000000 30 46 02 21 00 b7 7f b7 39 a4 13 ee 42 63 d3 6d |0F.!….9…Bc.m|
00000010 58 1e f9 4e 8b 85 23 5c 58 8d 31 d0 71 83 b9 e9 |X..N..#\X.1.q…|
00000020 be 79 69 c3 ed 02 21 00 81 0a 5d 33 88 eb 9f e2 |.yi…!…]3….|
00000030 a5 c1 83 4a 6f 76 32 34 3a 26 9d 03 fe 9c b2 7f |…Jov24:&……|
00000040 b2 f4 d5 06 d3 e0 e3 c9 |……..|
00000048
The signature is now stored in „mydata.sig“. You can use any tool using the public key of the HSM key to verify the signature.
Complexity considerations
To sign data with OpenSSL you need a lot of dependencies on your Linux host to allow all security related libraries working perfectly together. A full distribution upgrade to the next Linux kernel most likely will break these dependencies.
I checked the N200 HSM manual and found that they offer a RESTful API with JSON returns to do signature and verification as well. In my next project I definitely will use the REST-API instead of dealing with PKCS URIs and debugging with pkcs11 command line tools.
Hello, thank you for post!
I have a similar problem on my project,
We use openssl to sign a x509 certificate with the function X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md).
-> This function hashs the certificate, computes the signature and fill the output certificate with the coputed signature.
For our test, everything works fine but now for our production release instead of setting the private key as parameter of X509_sign() we would like to sign with a Private Key which is stored inside an HSM on the cloud.
To generate signature of raw data we use a dedicated API.
So we would like to send the certificate digest to our signature platform using the API, get the signature as feedback and set the signature inside the certificate..
Do you have an idea how we should proceed ?
Thank you
Hi Chris,
You need to have an OpenSSL Engine that calls te cloud HSM API and embed the signature to the X.509 certificate.
Most likely you will not be able to embed the API signature manually as certificate signing is highly complex. (I already developed an OpenSSL engine in the past, so I know about the dependencies)
I use the sematicon N200 crypto appliance HSM that has an integrated PKI solution and uses an internal engine to sign X.509 certificates.
The one-time costs is just about 5000-6000K for the complete HSM solution including PKI software and a developer friendly Rest-API. Most likely this is much cheaper than the annual fee for your cloud HSM.
Secure regards, Andreas