Integrate digital signatures with AWS CloudHSM

This guide explains how to configure Nutrient Document Engine’s signing service to use AWS CloudHSM(opens in a new tab) for hardware-backed digital signatures.

Overview

AWS CloudHSM(opens in a new tab) is a cloud-based hardware security module (HSM) that provides cryptographic key storage and operations. HSMs are specialized devices designed to safeguard sensitive data and cryptographic keys. By integrating Document Engine’s signing service with AWS CloudHSM, private keys never leave the secure hardware, significantly enhancing the security of your digital signature operations.

Architecture

Document Engine’s signing service acts as an intermediary between Document Engine and AWS CloudHSM:

  1. Document Engine — Prepares the document and requests a signature
  2. Signing service — Communicates with CloudHSM using PKCS#11 to perform cryptographic operations
  3. AWS CloudHSM — Stores private keys and performs signing operations in hardware

The signing service uses the PKCS#11(opens in a new tab) standard interface to communicate with CloudHSM, ensuring private keys remain secure within the HSM at all times.

Prerequisites

Before starting the integration, you’ll need:

  1. A Document Engine instance with digital signature support
  2. An AWS CloudHSM cluster configured and initialized
  3. A crypto user (CU) account created in the HSM
  4. The CloudHSM PKCS#11 client library installed on your signing service host
  5. Your signing certificate’s private key imported or generated in the HSM

Follow the official CloudHSM getting started(opens in a new tab) guide to set up your HSM cluster and install the client library.

Configuration

Follow the steps below.

Environment variables

Configure your signing service with the following environment variables:

Terminal window
# Path to the CloudHSM PKCS#11 library.
export HSM_MODULE=/opt/cloudhsm/lib/libcloudhsm_pkcs11.so
# HSM Crypto User credentials (format: "username:password")
export HSM_PIN=crypto_user:your_password
# Signing service port.
export SIGNING_SERVICE_PORT=6000

Document Engine configuration

Point Document Engine to your signing service by setting the SIGNING_SERVICE_URL environment variable:

Terminal window
export SIGNING_SERVICE_URL=http://signing-service:6000/sign

For more information on Document Engine configuration, refer to the configuration guide.

Docker deployment

When deploying with Docker, use docker-compose to set up both services:

services:
signing_service:
build: .
environment:
SIGNING_SERVICE_PORT: 6000
HSM_MODULE: /opt/cloudhsm/lib/libcloudhsm_pkcs11.so
HSM_PIN: ${HSM_PIN}
volumes:
- /opt/cloudhsm:/opt/cloudhsm # Mount CloudHSM client libraries
ports:
- 6000:6000
document_engine:
image: nutrient/document-engine:latest
environment:
SIGNING_SERVICE_URL: http://signing_service:6000/sign
ACTIVATION_KEY: ${ACTIVATION_KEY}
ports:
- 5000:5000

Signing methods

Document Engine supports two signature container formats when using AWS CloudHSM.

Use RAW signing when your HSM returns a raw PKCS#1 signature. This is the recommended approach for CAdES signatures:

Terminal window
curl -X 'POST' 'http://localhost:5000/api/documents/my-document-id/sign' \
-H 'Authorization: Token token="secret"' \
-H 'Content-Type: application/json' \
-d '{
"signatureContainer": "raw",
"signatureType": "cades",
"cadesLevel": "b-lt",
"signingToken": "{\"signMethod\": \"hsm\"}"
}'

The signing service:

  1. Receives the data_to_be_signed from Document Engine
  2. Calculates the digest using the specified hash algorithm
  3. Encodes it in PKCS#1 v1.5 format
  4. Signs it using the HSM’s private key using PKCS#11
  5. Returns the raw signature to Document Engine

PKCS#7 container signing

Use PKCS#7 signing when you need a complete signature container:

Terminal window
curl -X 'POST' 'http://localhost:5000/api/documents/my-document-id/sign' \
-H 'Authorization: Token token="secret"' \
-H 'Content-Type: application/json' \
-d '{
"signatureContainer": "pkcs7",
"signatureType": "cms",
"signingToken": "{\"signMethod\": \"hsm\"}"
}'

The signing service:

  1. Receives the document digest from Document Engine
  2. Creates a PKCS#7 container
  3. Uses a custom signer function that delegates signing to the HSM
  4. Returns the complete DER-encoded PKCS#7 container

Implementation details

HSM initialization

The signing service initializes the CloudHSM connection using the graphene-pk11 library:

import * as graphene from "graphene-pk11";
const mod = graphene.Module.load(process.env.HSM_MODULE, "CloudHSM");
mod.initialize();

Authentication

The service authenticates with the HSM using your crypto user credentials:

const session = slot.open(
graphene.SessionFlag.SERIAL_SESSION | graphene.SessionFlag.RW_SESSION,
);
session.login(process.env.HSM_PIN, graphene.UserType.User);

Key management

The signing service retrieves the private key from the HSM for signing operations. Keys are stored securely within the HSM and never leave the hardware:

const privateKeys = session.find({
class: graphene.ObjectClass.PRIVATE_KEY,
});
const privateKey = privateKeys.items(0);

Certificate management

For RAW signing, your signing service must provide the certificate chain to Document Engine. The signing service responds to the get_certificates callback:

{
"certificates": ["<base64-encoded-signer-certificate>"],
"ca_certificates": ["<base64-encoded-ca-certificate>"]
}

Security considerations

  1. Private key security
    • Private keys never leave the HSM.
    • The signing service only needs the PKCS#11 library and PIN.
    • Use AWS IAM roles and security groups to control access.
  2. Credential management
    • Store HSM_PIN securely using AWS Secrets Manager.
    • Rotate crypto user passwords regularly.
    • Use least-privilege principles for HSM user permissions.
  3. Network security
    • Deploy the signing service on the same private network as Document Engine.
    • Restrict external network access to the signing service.
    • Use AWS security groups to control HSM cluster access.
  4. Signing token validation
    • The signing service validates requests using the signingToken parameter.
    • Implement additional authentication as needed for your use case.

Testing the integration

To verify your setup:

  1. Start the signing service with CloudHSM configuration
  2. Make a test signing request to Document Engine:
Terminal window
curl -X 'POST' 'http://localhost:5000/api/documents/test-doc/sign' \
-H 'Authorization: Token token="secret"' \
-H 'Content-Type: application/json' \
-d '{
"signatureContainer": "raw",
"signatureType": "cades",
"signingToken": "{\"signMethod\": \"hsm\"}"
}'
  1. Check the signing service logs for HSM connection messages
  2. Verify the signed document validates correctly

Additional resources

For a complete reference implementation of the signing service with HSM support (using SoftHSM, but adaptable to AWS CloudHSM), see the signing service example on GitHub(opens in a new tab).

For more information on the signing service protocol, refer to the callbacks section in the API reference for the digital signatures endpoint.