Files
EnvelopeGenerator/EnvelopeGenerator.Infrastructure.Crypt/README.md
TekH 3a71d57619 Enhance FES docs, plans, and SoftHSM integration
Updated README.md for clarity and added official references. Introduced detailed infrastructure and integration plans for FES, including phased implementation, deployment options, and risk mitigations. Added FES workflow diagrams in draw.io and PDF formats. Documented SoftHSM testing process and integration details in ticket.md. Improved alignment with clean architecture principles and production readiness.
2026-10-08 10:31:00 +02:00

12 KiB

EnvelopeGenerator.Infrastructure.Crypt

PKCS#11-based cryptographic infrastructure for FES (Advanced Electronic Signature) workflows.

Official references:

  • SoftHSMv2 (official): https://github.com/softhsm/SoftHSMv2
  • OpenSC pkcs11-tool docs: https://github.com/OpenSC/OpenSC/wiki/Using-pkcs11-tool-and-OpenSSL
  • PKCS#11 standard overview (OASIS): https://www.oasis-open.org/committees/pkcs11/

1) Scope

This project is the crypto layer only.

  • Signs hashes via keys in HSM/SoftHSM.
  • Reads certificates from HSM/SoftHSM.
  • Verifies signatures.
  • Exposes abstractions for Application/Server integration.

This project is not:

  • a REST API,
  • a UI module,
  • a full document orchestration workflow.

2) SoftHSM and PKCS#11 basics

  • SoftHSM is a software implementation of an HSM.
  • It does not expose REST endpoints.
  • Access is via PKCS#11 shared library (.so/.dll).
  • App loads module path and performs PKCS#11 operations.

Supported deployment patterns:

  1. Direct module (libsofthsm2.so, softhsm2-x64.dll, etc.)
  2. PKCS#11 proxy module (remote HSM/SoftHSM backend)

Critical platform rule:

  • A Windows process cannot load a Linux .so directly.
  • Crypt:Pkcs11:LibraryPath must always point to a native library compatible with the host OS of EnvelopeGenerator.Server.

2.1) Windows Server + Linux SoftHSM architecture (decision TODO)

Current business requirement: EnvelopeGenerator.Server runs on Windows, SoftHSM runs on Linux.

This repository must use one of the following three paths:

  1. Linux signing service (recommended)
    • Windows EnvelopeGenerator.Server calls HTTPS signing API.
    • Linux service performs PKCS#11 operations against SoftHSM.
    • Private keys stay on Linux.
  2. PKCS#11 proxy bridge
    • Windows app loads a Windows PKCS#11 proxy client DLL.
    • Proxy forwards operations to Linux-side PKCS#11 endpoint connected to SoftHSM.
  3. Local Windows SoftHSM (dev/test only, optional)
    • SoftHSM is also installed on Windows.
    • App talks to local Windows PKCS#11 module.

TODO - architecture lock-in:

  • Select one path as production standard (1 is recommended).
  • Document final runtime topology (hosts, ports, TLS, auth).
  • Define where audit metadata (IP/email/phone/device) is stored and signed/linked.
  • Update SignatureController.Submit integration contract for FES branch.
  • Add environment-specific runbook for Windows host + Linux HSM operations.

3) FES flow mapping

  • Step 6: hash is sent to HSM signing operation -> ISignatureProvider
  • Step 7: signature and certificate context are resolved -> Pkcs11SignatureProvider, ICertificateProvider
  • Step 8: PDF embedding + audit trail are completed by upper layers; this project provides crypto primitives

4) Service surface

Main interfaces:

  • ISignatureProvider
  • ICertificateProvider
  • ISignatureVerifier
  • ICryptographicHealthCheck

DI entry point:

  • CryptDependencyInjection.AddCryptInfrastructure(...)

5) Runtime configuration

Configuration section: Crypt:Pkcs11

{
  "Crypt": {
    "Pkcs11": {
      "LibraryPath": "/usr/lib/softhsm/libsofthsm2.so",
      "SlotId": 1720207650,
      "TokenLabel": "DevToken",
      "UserPin": "***",
      "PrivateKeyLabel": "sign-key",
      "PrivateKeyIdHex": null,
      "CertificateLabel": "sign-cert",
      "CertificateIdHex": null
    }
  }
}

Security rules:

  • Never commit real PINs.
  • Use environment variables or secret vault.
  • Use test-only tokens for development.

Production naming convention used in this repository:

  • Token label: CompanyProdToken
  • Private key label: company-sign-key-v1
  • Certificate label: company-sign-cert-v1

These names are also exposed as constants in EnvelopeGenerator.Infrastructure.Crypt/Options/Pkcs11ProviderOptions.cs.


6) Test configuration source

Crypt tests read PKCS#11 values from EnvelopeGenerator.Tests/appsettings.json under Crypt:Pkcs11.

Required keys:

  • Crypt:Pkcs11:LibraryPath
  • Crypt:Pkcs11:SlotId (or Crypt:Pkcs11:TokenLabel)
  • Crypt:Pkcs11:UserPin
  • Crypt:Pkcs11:PrivateKeyLabel
  • Crypt:Pkcs11:CertificateLabel

No environment variable is required for crypt tests in this repository anymore.


7) Full SoftHSM lifecycle runbook (project scope)

7.1 Discover module path

ldconfig -p | grep softhsm
find /usr -name "libsofthsm2.so" 2>/dev/null

7.2 List slots/tokens

softhsm2-util --show-slots
softhsm2-util --init-token --slot <uninitialized-slot> --label <token-label> --so-pin <so-pin> --pin <user-pin>

Important:

  • After init, token is reassigned to a new slot id.
  • Re-run --show-slots and use the new slot id.

7.4 Login and inspect objects

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> --list-objects

7.5 Generate company signing key pair

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> \
  --keypairgen --key-type rsa:3072 --label sign-key --id 01

7.6 Import company certificate

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> \
  --write-object company-sign-cert.der --type cert --label sign-cert --id 01

7.7 Read object metadata (for diagnostics)

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> --list-objects
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> --list-mechanisms

7.8 Update operations

PKCS#11 objects are usually immutable for key/cert payload; updates are done by rotation:

  1. Create/import new key/cert with new label or id.
  2. Update app config/env labels.
  3. Validate with integration tests.
  4. Delete old objects when safe.

PIN operations:

  • User PIN change (while logged in):
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <old-user-pin> --change-pin --new-pin <new-user-pin>
  • User PIN reset via SO credentials:
softhsm2-util --init-pin --slot <slot-id> --so-pin <so-pin> --pin <new-user-pin>

7.9 Delete operations

Delete by label/id/type:

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> \
  --delete-object --type cert --label sign-cert

Token-level destructive reset (do not use unless approved):

softhsm2-util --delete-token --token <token-label>

8) New company onboarding checklist

For company-level signing model (one company key/cert pair):

  1. Create token with company label (or assign dedicated token in remote HSM).
  2. Generate key pair in token.
  3. Obtain CA certificate and import into token.
  4. Record secure runtime values:
    • module path
    • slot id or token label
    • user pin
    • private key label
    • certificate label
  5. Run crypt integration tests.
  6. Store values in vault and wire app environment.

9) Rotation/update checklist

  1. Create new key/cert (sign-key-v2, sign-cert-v2).
  2. Configure app env to new labels.
  3. Run integration tests.
  4. Deploy.
  5. Keep old key/cert during grace period.
  6. Remove old key/cert after legal/operational confirmation.

10) Running tests

Before running, configure EnvelopeGenerator.Tests/appsettings.json -> Crypt:Pkcs11 with real Linux SoftHSM values.

Run crypt tests:

dotnet test "EnvelopeGenerator.Tests/EnvelopeGenerator.Tests.csproj" -f net8.0 --filter "FullyQualifiedName~EnvelopeGenerator.Tests.Application.Crypt"

11) Common failures

  • CKR_PIN_INCORRECT: wrong user pin for that token.
  • CRYPT_PROVIDER_UNAVAILABLE: bad module path or native load issue.
  • CRYPT_SLOT_NOT_FOUND: wrong slot id or token label.
  • CRYPT_LOGIN_FAILED: login failed (pin/token mismatch).
  • CRYPT_KEY_NOT_FOUND: key label/id mismatch.
  • CRYPT_CERT_NOT_FOUND: certificate label/id mismatch.

12) Integration note

Current implementation uses CKM_SHA256_RSA_PKCS while caller provides hash bytes. Validate mechanism semantics in your provider/HSM policy. If provider expects raw payload instead of digest for selected mechanism, signing flow must be adjusted.


13) Production-first onboarding (single-line checkpoints)

This section is the authoritative path for production-style setup (critical documents, FES).

13.1 Step 1 - Initialize dedicated company token

One-line command:

mkdir -p ./fes-prod && cd ./fes-prod && export SO_PIN="$(openssl rand -hex 16)" USER_PIN="$(openssl rand -hex 12)" && softhsm2-util --init-token --slot <uninitialized-slot> --label CompanyProdToken --so-pin "$SO_PIN" --pin "$USER_PIN" && softhsm2-util --show-slots

Checkpoint:

  • Token appears as initialized with label CompanyProdToken.
  • SoftHSM reassigns token to a new slot ID; use that slot ID in all next commands.

Security note:

  • Do not print/store PINs in shell history in real production operations.
  • Persist secrets in a vault or protected secret store.

13.2 Step 2 - Generate company signing key pair inside HSM

One-line command:

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <company-slot-id> --login --pin "$USER_PIN" --keypairgen --key-type rsa:3072 --label company-sign-key-v1 --id 01 && pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <company-slot-id> --login --pin "$USER_PIN" --list-objects

Checkpoint:

  • Private Key Object and Public Key Object exist for label company-sign-key-v1, id 01.
  • Private key remains non-extractable in token.

13.3 Step 3 - Obtain CA-issued certificate for HSM key

Required policy:

  • Certificate must be issued by enterprise CA for the key generated in token.
  • Do not use self-signed cert for production signing identity.

Operational note:

  • CSR generation method depends on installed PKCS#11 OpenSSL integration (engine_pkcs11 / provider).
  • After CA issues certificate, import certificate DER into token with matching id/label family.

OpenSSL 3 prerequisite check:

openssl version && openssl engine -t -c 2>/dev/null | grep -i pkcs11 || true

If no PKCS#11 engine/provider is listed, install integration packages first (distribution specific), then continue.

13.4 Step 4 - Import CA certificate into token

One-line command:

pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <company-slot-id> --login --pin "$USER_PIN" --write-object ./company-sign-cert-v1.der --type cert --id 01 --label company-sign-cert-v1 && pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <company-slot-id> --login --pin "$USER_PIN" --list-objects

Checkpoint:

  • Certificate Object exists with id 01 and expected label.

13.5 Step 5 - Wire runtime/test configuration

Server runtime (EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json) must include:

  • Crypt:Pkcs11:LibraryPath = native PKCS#11 library path for the server host OS
    • Windows host: Windows DLL path (vendor DLL or proxy client DLL)
    • Linux host: Linux .so path
  • Crypt:Pkcs11:SlotId = 2021549410 (or current company slot)
  • Crypt:Pkcs11:TokenLabel = CompanyProdToken
  • Crypt:Pkcs11:UserPin = real user pin
  • Crypt:Pkcs11:PrivateKeyLabel = company-sign-key-v1
  • Crypt:Pkcs11:CertificateLabel = company-sign-cert-v1

Test runtime (EnvelopeGenerator.Tests/appsettings.json) must mirror the same Crypt:Pkcs11 values.

Then run tests:

dotnet test "EnvelopeGenerator.Tests/EnvelopeGenerator.Tests.csproj" -f net8.0 --filter "FullyQualifiedName~EnvelopeGenerator.Tests.Application.Crypt"