Architecture¶
Engineering architecture for OpenSignature. Authoritative product rules live in the product specification and docs/TASKS.md.
Goals¶
- Formats: PAdES, XAdES, CAdES, ASiC-S, ASiC-E (profiles B → T → LT → LTA).
- API never signs synchronously.
- Cryptography behind replaceable
ISigningProviderimplementations. - Binaries in file/object storage; PostgreSQL holds metadata, jobs, audit, outbox.
- RabbitMQ transports job references only.
- Tenant isolation, idempotency, OpenTelemetry end-to-end.
Component map¶
| Project | Role |
|---|---|
OpenSignature.Web |
React admin / demo UI |
OpenSignature.Api |
HTTP boundary; validate; 202 Accepted |
OpenSignature.Application |
Use cases, DTOs, ports |
OpenSignature.Domain |
Entities, state machine — no infrastructure |
OpenSignature.Infrastructure |
EF Core, PostgreSQL, storage, RabbitMQ, outbox |
OpenSignature.Signing.Contracts |
ISigningProvider and shared contracts |
OpenSignature.Signing |
Providers and format engines |
OpenSignature.Validation |
Certificate + signature validation reports |
OpenSignature.Worker |
Consume jobs; sign; update state |
flowchart TB
subgraph clients [Clients]
WEB[OpenSignature.Web]
EXT[External REST clients]
end
API[OpenSignature.Api]
APP[Application]
DOM[Domain]
INF[Infrastructure]
PG[(PostgreSQL)]
FS[(IFileStorage)]
MQ[[RabbitMQ]]
WRK[OpenSignature.Worker]
SIG[OpenSignature.Signing]
VAL[OpenSignature.Validation]
WEB --> API
EXT --> API
API --> APP
APP --> DOM
APP --> INF
INF --> PG
INF --> FS
INF --> MQ
MQ --> WRK
WRK --> APP
WRK --> INF
WRK --> SIG
SIG --> VAL
Dependency rules¶
flowchart LR
Web --> Api --> Application --> Domain
Application --> Infrastructure
Infrastructure --> Domain
Worker --> Application
Worker --> Infrastructure
Worker --> Signing
Signing --> Contracts[Signing.Contracts]
- Domain depends on nothing outside itself.
- Signing must not depend on RabbitMQ.
- Api must not perform cryptographic signing.
- Prefer ports:
IFileStorage,ISigningProvider, messaging abstractions.
Signing providers¶
flowchart TB
I[ISigningProvider]
I --> PFX[PfxSigningProvider]
I --> P11[Pkcs11SigningProvider]
I --> SC[SmartCardSigningProvider]
I --> HSM[HsmSigningProvider]
Hardware rule:
Private keys never leave the token/HSM. PFX is development/demo only.
Storage keys¶
Server-generated (never client filenames):
tenants/{tenantId}/signatures/{yyyy}/{MM}/{dd}/{signatureId}/input.bin
tenants/{tenantId}/signatures/{yyyy}/{MM}/{dd}/{signatureId}/signed.bin
RabbitMQ topology¶
flowchart LR
OB[Outbox publisher] -->|routing key signature.created| EX((esign.signature))
EX --> Q[esign.signature.worker]
Q -.->|DLX| DLX((esign.signature.dlx))
DLX --> DLQ[esign.signature.dlq]
Messages carry jobId, tenantId, signatureId, storage paths, format/profile, attempt — not file bytes.
Outbox pattern¶
- Same PostgreSQL transaction: signature metadata + job +
OutboxMessage. - Publisher drains unpublished rows to RabbitMQ.
- Mark published only after broker handoff.
- Worker consumption is idempotent (job lock + terminal-state checks).
Multi-tenancy¶
- Entities and messages include
TenantId. - Idempotency key =
TenantId + Idempotency-Key. - Auth roadmap: MVP API keys; production OAuth2/OIDC + JWT + RBAC.
Observability¶
Every hop should carry correlationId, traceId, signatureId, jobId.
Key metrics: request/completion/failure totals, duration, queue wait, RabbitMQ retries, provider failures, storage operations.
Never log private keys, PINs, passwords, secrets, or document contents.