Enhanced PDF signing with UUID and metadata handling: - Added `UUID` and `OriginalDocumentSha256` to `PdfSignOptionsDto` and `PdfSignatureCheckItemDto`. - Introduced `BusinessReason` to separate metadata from signature reasons. - Updated `PdfDigitalSignatureService` to embed and parse metadata in signature reasons. - Improved error handling to include metadata in validation results. - Updated `PdfSignatureController` to handle `UUID` in signing requests. Refactored PKCS#11 configuration: - Replaced `LibraryPath` with `AssetsRootPath` for asset resolution. - Added `ProxySocket` and `DefaultApplicationName` options. - Clarified runtime behavior for asset resolution in `README.md`. Updated documentation and examples to reflect these changes.
392 lines
12 KiB
Markdown
392 lines
12 KiB
Markdown
# 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:AssetsRootPath` must resolve to a directory that contains the expected native assets for the host OS.
|
|
- If `AssetsRootPath` is empty, runtime resolves assets from `AppContext.BaseDirectory`.
|
|
|
|
---
|
|
|
|
## 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`
|
|
|
|
```json
|
|
{
|
|
"Crypt": {
|
|
"Pkcs11": {
|
|
"AssetsRootPath": "E:\\DataFiles\\Digital Data\\EnvelopeGenerator",
|
|
"SlotId": 1720207650,
|
|
"TokenLabel": "DevToken",
|
|
"ProxySocket": "tcp://172.24.12.64:5657",
|
|
"UserPin": "***",
|
|
"PrivateKeyLabel": "sign-key",
|
|
"CertificateLabel": "sign-cert",
|
|
"DefaultApplicationName": "signFLOW",
|
|
"UseUtcSigningTime": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
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:AssetsRootPath` (optional; if missing, app base directory is used)
|
|
- `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
|
|
|
|
```bash
|
|
ldconfig -p | grep softhsm
|
|
find /usr -name "libsofthsm2.so" 2>/dev/null
|
|
```
|
|
|
|
### 7.2 List slots/tokens
|
|
|
|
```bash
|
|
softhsm2-util --show-slots
|
|
```
|
|
|
|
### 7.3 Create new token (recommended for new company/test tenant)
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
pkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so --slot <slot-id> --login --pin <user-pin> --list-objects
|
|
```
|
|
|
|
### 7.5 Generate company signing key pair
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
softhsm2-util --init-pin --slot <slot-id> --so-pin <so-pin> --pin <new-user-pin>
|
|
```
|
|
|
|
### 7.9 Delete operations
|
|
|
|
Delete by label/id/type:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:AssetsRootPath` = base directory that contains `pkcs11\windows\proxy\libpkcs11-proxy.dll`
|
|
- If null/empty, runtime resolves assets from application output (`AppContext.BaseDirectory`)
|
|
- `Crypt:Pkcs11:SlotId` = `2021549410` (or current company slot)
|
|
- `Crypt:Pkcs11:TokenLabel` = `CompanyProdToken`
|
|
- `Crypt:Pkcs11:ProxySocket` = `tcp://172.24.12.64:5657`
|
|
- `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:
|
|
|
|
```bash
|
|
dotnet test "EnvelopeGenerator.Tests/EnvelopeGenerator.Tests.csproj" -f net8.0 --filter "FullyQualifiedName~EnvelopeGenerator.Tests.Application.Crypt"
|
|
```
|