Firmware signing using PKCS11, with Approval stage
This tutorial will guide the reader through performing an OpenSSL Signing operation based on PKCS11, using the Cryptera KeyServer API.
Web UI
The approval UI is available at https://keyclient.
Prerequisites
While most Linux distributions should work, this tutorial is based on Ubuntu 24.04 with the following prerequisites:
For Building - Docker Engine
For Operation - cURL, for HTTP requests - openssl, for command line cryptographic operations - pkcs11-tool, for interfacing with Hardware Security Module. - jq - libengine-pkcs11-openssl - opensc
Building PKCS11 library from Source (Optional)
From the main folder, build and tag the image "libpkcs11ks"
The container can then be used to build the library in a consistent environment. Running the container, will copy the Binary library to ./output directory - in addition a default test is triggered
Authentication
For development an authentication server is provided with a default configuration. When integrating into gitlab or similar, this server is not used, but instead KeyServer is configured to accept the GitLab JSON Web Token (JWT) or similar. For the sake of the tutorial, the default configuration is used.
The demo system comes pre-installed with a number of users configured:
- adminuser - This user has access to all operations, including Admin operations through the UI.
- adminclient - This user has access to all operations through the API
- orderer-client - Able to create pipeline requests and signing for all keys
- orderer-clientkey# - Able to create pipeline requests and signing for the specified key #, eg. orderer-clientkey1
- approver-key# - Able to approve signing requests for the specified key #, eg. approver-key2
To get an authentication token from the demo server:
export KSC_ID_TOKEN=$(curl -sS --tlsv1.3 -X POST -H "Authorization: Basic <BASE64-CREDENTIALS>" -d "grant_type=client_credentials" https://authserver.<UNIQUE-ID>.cryptera.com:8300/oauth2/token | jq -r .access_token)
Available Keys
The list of available keys is available here: https://keyclient.
- key1 - 0 approvers
- key2 - 1 approver
- key3 - 2 approvers
- key4 - 1 approver
Initiate Pipeline
Before requesting a signing operation, a pipeline must be initiated and approved if required.
Please refer to: https://keyserver.
In particular, validms defines the maximum amount of time the operation will be allowed to last. Followed by an array of which keys should be used and the amount if times each key is permitted. Below is an example for requesting 60 seconds approval to use key1 and key4 1 time each: please note that the whole sequence must be completed before approval expires
export KSC_OPERATION_ID=$(curl -sS --tlsv1.3 -X 'POST' \
'https://keyserver.<UNIQUE-ID>.cryptera.com:8200/api/operations' \
-H "Authorization: Bearer $KSC_ID_TOKEN" \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"validms": 60000,
"description": "Building of firmware for XYZ development board",
"new-keyusagerestrictions": [
{
"keyid": "key1",
"maxusagecount": 1
},
{
"keyid": "key4",
"maxusagecount": 1
}
]
}' | jq -r .id)
Approval of pipeline operation
Login with a user that has the appropriate approval rights to: https://keyclient.
The script / pipeline should poll the operation status API waiting for the operation to be approved ("approved":"true"), before continuing to performing signing operation (which from this point is executed synchrously)
Refer to: https://keyserver.
Signing
With the pipeline operation being approved the actual signing operation is synchronous. From this point PKCS11 is used as entry point for the KeyServer operations.
Configuring OpenSSL config to work with PKCS11
To get OpenSSL to work with PKCS11 we need to create an customized OpenSSL configuration file (openssl-ks.cnf):
openssl_conf = openssl_init
[openssl_init]
engines = engine_section
providers = provider_sect
[provider_sect]
default = default_sect
base = base_sect
[default_sect]
activate = 1
[base_sect]
activate = 1
[engine_section]
pkcs11 = pkcs11_section
[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-3/libpkcs11.so
MODULE_PATH = /opt/app/scripts/libpkcs11keyserver.so
init = 0
The following line should be altered to match the path for our newly built PKCS11 binary:
Now to set this as the active configuration file set the following environment variable:
Specifying variables
The PKCS11 library is inspecting the following variables, and require them to be updated for the specific operation being carried out:
export KSC_API_SERVER="https://keyserver.<UNIQUE-ID>.cryptera.com:8200"
export KSC_OPERATION_ID=$operation_id
export KSC_OPERATION_DESCRIPTION="Building of firmware for XYZ development board"
- KSC_API_SERVER - Reference to the specific customer KeyServer
- KSC_OPERATION_ID - The resulting Operation ID given as response to pipeline initiation
- KSC_OPERATION_DESCRIPTION - Description of changes, presented to the Approver for context
- KSC_ID_TOKEN - For example the token manually fetched above, or a GitLab JWT
List available keys
It is possible to list all available keys on the HSM using pkcs11-tool (make sure the module path is changed to match):
Signing with OpenSSL via PKCS11
At this point a signing operation can be carried out using eg. OpenSSL or any application that can rely on PKCS11.
openssl dgst -engine pkcs11 -keyform engine -sign "pkcs11:object=<KEYLABEL>" -sha256 -out <signature_file> <data_file>
Verification
It is possible to validate the signing operation by extracting the matching public key and validate the signature against it.
Retrieve the certificate for key1 and convert to PEM:
pkcs11-tool --module $SCRIPT_PATH/build/libkscpkcs11.so --read-object --type cert --label <KEYLABEL> --output-file <certkey.der>
openssl x509 -pubkey -noout -inform DER -in <certkey.der> -out <pubkey.pem> -outform PEM
Verification of signature for data and public key:
Tutorial script
A script implementing the process described in this tutorial is provided as an example scripts/signandvalidate.sh