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.
Unless you need HSM-level security, Nutrient offers a powerful signing API.
Prefer to jump straight into code? View the example repo on GitHub.
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:
- Document Engine — Prepares the document and requests a signature
- Signing service — Communicates with CloudHSM using PKCS#11 to perform cryptographic operations
- 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:
- A Document Engine instance with digital signature support
- An AWS CloudHSM cluster configured and initialized
- A crypto user (CU) account created in the HSM
- The CloudHSM PKCS#11 client library installed on your signing service host
- 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:
# 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=6000Document Engine configuration
Point Document Engine to your signing service by setting the SIGNING_SERVICE_URL environment variable:
export SIGNING_SERVICE_URL=http://signing-service:6000/signFor 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:5000Signing methods
Document Engine supports two signature container formats when using AWS CloudHSM.
RAW signing (recommended for CAdES)
Use RAW signing when your HSM returns a raw PKCS#1 signature. This is the recommended approach for CAdES signatures:
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:
- Receives the
data_to_be_signedfrom Document Engine - Calculates the digest using the specified hash algorithm
- Encodes it in PKCS#1 v1.5 format
- Signs it using the HSM’s private key using PKCS#11
- Returns the raw signature to Document Engine
PKCS#7 container signing
Use PKCS#7 signing when you need a complete signature container:
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:
- Receives the document digest from Document Engine
- Creates a PKCS#7 container
- Uses a custom signer function that delegates signing to the HSM
- 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
- 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.
- Credential management
- Store
HSM_PINsecurely using AWS Secrets Manager. - Rotate crypto user passwords regularly.
- Use least-privilege principles for HSM user permissions.
- Store
- 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.
- Signing token validation
- The signing service validates requests using the
signingTokenparameter. - Implement additional authentication as needed for your use case.
- The signing service validates requests using the
Testing the integration
To verify your setup:
- Start the signing service with CloudHSM configuration
- Make a test signing request to Document Engine:
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\"}" }'- Check the signing service logs for HSM connection messages
- 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.