Skip to content

How-To: Use OpenSSL 3.x with PKCS#11 for Signing

This guide explains how to use the Cryptera Keyserver PKCS#11 library with OpenSSL 3.x

This setup allows you to perform signing using keys stored inside the Keyserver’s HSM without exposing private key material.


Production Authentication Required

This workflow describes production-grade signing using OpenSSL 3.x and PKCS#11.
All production deployments must authenticate using an external OAuth2/OIDC identity provider such as
Azure Entra ID, GitLab OIDC, GitHub Actions OIDC, or Keycloak.
The internal Authserver from the demo environment must not be used for production signing.

Internal Authserver for Testing Only

You may use the Keyserver’s internal Authserver while testing this guide on a demo deployment.
In production, authentication must always be handled by your organization’s identity provider
to ensure MFA enforcement, account lifecycle management, and compliance logging.

Prerequisites

While most Linux distributions should work, this tutorial is based on Ubuntu 24.04 with the following prerequisites:

For building Keyserver PKCS#11 Library, libpkcs11ks

For operation

  • cURL, for HTTP requests
  • openssl, for command line cryptographic operations
  • pkcs11-tool (opensc), for interfacing with Hardware Security Module.
  • jq
  • libengine-pkcs11-openssl

Building libpkcs11ks.so

Build the library from source and place it somewhere readable by your tooling.:

Fetch the latest version of the source code for the library:

git clone https://github.com/cryptera-device-security/libpkcs11ks.git

From the libpkcs11ks folder:

docker build -t 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

docker run -v "$(pwd)/output:/output" libpkcs11ks

Save the libpkcs11ks.so artifact from the output folder for later use.

Configuration of PKCS11 Library (libpkcs11ks.so)

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-security.io"
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, GitLab JWT, etc.

5. Create an OpenSSL 3.x Provider Configuration

To get OpenSSL to work with PKCS11,create a 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/libpkcs11ks.so

init = 0

The following line should be altered to match the path for the newly built PKCS11 binary:

MODULE_PATH = /opt/app/scripts/libpkcs11ks.so

Now to set this as the active configuration file set the following environment variable:

export OPENSSL_CONF=<PATH_TO_ABOVE_FILE>

6. Find the Key via PKCS#11 URI

(Requires valid KSC_ID_TOKEN, see below) List keys:

pkcs11-tool --module /path/to/libpkcs11ks.so --list-objects

You will see:

Private Key Object; label: key1; id: <...>

Build the key URI:

pkcs11:token=Keyserver;object=key1;type=private

Signing using OpenSSL

1. Authenticate and Obtain an OAuth2 Token

Note: This stage is for trial setup only. For production deployments, refer to the Authentication Providers section.

Encode credentials:

export AUTH_CREDS=$(echo -n "<CLIENT>:<PASSWORD>" | base64)

Request token:

export KSC_ID_TOKEN=$(
  curl -sS --tlsv1.3 \
    -X POST \
    -H "Authorization: Basic $AUTH_CREDS" \
    -d "grant_type=client_credentials" \
    https://authserver.<UNIQUE-ID>.cryptera-security.io/oauth2/token \
    | jq -r .access_token
)

Verify:

echo $KSC_ID_TOKEN

2. Create an Operation

OpenSSL operations require the Keyserver to know:

  • Which key is allowed to be used
  • How many times
  • For how long

Create a short-lived operation:

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 "Content-Type: application/json" \
    -d "{
          \"validms\": 60000,
          \"description\": \"OpenSSL provider signing\",
          \"new-keyusagerestrictions\": [
            { \"keyid\": \"key1\", \"maxusagecount\": 1 }
          ]
        }" | jq -r .id
)

Display it:

echo $KSC_OPERATION_ID

3. Approve the Operation (If Required)

If the key requires approvals:

  1. Go to
    https://keyclient.<UNIQUE-ID>.cryptera-security.io/approvaladmin
    
  2. Log in as an approver
  3. Approve the operation

Otherwise skip this step.


4. Update per operation description

export KSC_OPERATION_DESCRIPTION="OpenSSL signing test 1"

5. Sign Data

Create data to sign:

echo "hello world" > data.bin

At this point a signing operation can be carried out using OpenSSL or any application that can rely on PKCS11. can be determined either in the web-ui or from the output from the pkcs11-tool list-objects command.

openssl dgst -engine pkcs11 -keyform engine  -sign "pkcs11:object=<KEYLABEL>" -sha256 -out <signature_file> <data_file>
OpenSSL will call PKCS11, which in turn asks KeyServer to perform a signing operation and return the signature.

openssl dgst \
  -engine pkcs11 \
  -keyform engine \
  -sign "pkcs11:object=key1" \
  -sha256 \
  -out signature.bin \
  data.bin

OpenSSL will:

  1. Load the pkcs11 engine
  2. Use the Keyserver PKCS#11 module
  3. Send a request to the Keyserver
  4. The Keyserver performs signing inside the HSM
  5. The signature is returned

8. Extract the Certificate for Verification

Export Certificate via PKCS#11:

pkcs11-tool \
  --module /path/to/libpkcs11ks.so \
  --read-object --type cert --label <KEYLABEL> \
  --output-file <KEYLABEL>.der

Convert to PEM:

openssl x509 -inform DER -in <KEYLABEL>.der -out <KEYLABEL>.pem -outform PEM

Extract the public key from the certificate

openssl x509 -in <KEYLABEL>.pem -pubkey -noout > <KEYLABEL>_pubkey.pem

9. Verify the Signature

openssl dgst -sha256 -verify <KEYLABEL>.pem -signature signature.bin data.bin

If successful:

Signature Verified Successfully

Summary

Summary

This guide covered how to use OpenSSL 3.x with the Cryptera Keyserver PKCS#11 library for secure signing operations. You learned how to:

  • Build and configure the libpkcs11ks PKCS#11 library
  • List and identify HSM-protected keys using pkcs11-tool
  • Authenticate to the Keyserver and obtain an OAuth2 token
  • Create and approve signing operations with usage restrictions
  • Perform signing using OpenSSL with PKCS#11 engine and provider
  • Export certificates and public keys for verification
  • Verify signatures using OpenSSL

By following these steps, you can securely sign data using keys stored in the Keyserver HSM, ensuring private key material remains protected and all operations are authenticated and auditable.