ADORSYS-GIS / ADORSYS-GIS/cloud-identity-wallet

Consolidate crypto signer, X.509 validation, and trust anchor loading

Abierto
#378 0 comentarios 0 reacciones 1 asignado Reclamado por @martcpp Ver en GitHub
enhancement
Lenguaje dominante
Rust
Estrellas
4
Forks
0
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

**Priority:** P0 — Critical

### Description

Consolidate duplicated and scattered building blocks across the codebase so that crypto signing, X.509 chain validation, and trust anchor loading are each implemented once and cheaply shared across all consumers (issuance engine, presentation engine, and future modules).

### Background

Currently the codebase has several instances of duplicated or ad-hoc construction:

1. **Crypto signer** (`src/domain/models/keys.rs:tenant_crypto_signer()`):
- Loads a tenant key from `TenantRepo` and builds a `CryptoSigner` on every call
- Each invocation blocks on `spawn_blocking` for DER parsing + key construction
- `IssuanceEngine` and `PresentationEngine` both need this same flow but call it independently
- No caching — signing the same credential twice parses the key twice

2. **X.509 chain validation**:
- SD-JWT VC x5c verification lives in `cloud-wallet-openid4vc::formats::sd_jwt`
- mdoc IACA chain verification lives in `cloud-wallet-openid4vc::formats::mdoc`
- Both do similar things (parse DER → build chain → validate → extract public key) but with different trust anchor inputs and error types
- The verifier key resolution in `PresentationEngine::CompositeKeyResolver` does its own X.509 parsing

3. **X5cTrustAnchors / IACA trust store loading** (`src/utils.rs:load_iaca_roots()`):
- Called once for issuance, once for presentation (duplicate I/O)
- Always returns raw DER bytes, even though both consumers parse them into structured trust stores
- Result is not cached — if the function were called again, it would re-read and re-parse
- `x5c_trust_anchors` for SD-JWT verification has no production loader (see #365)
- `iaca_trust_store` for mdoc verification has no production loader path either (only test fixtures)

4. **OidClient construction** (`src/setup.rs`):
- Built separately in `build_issuance_engine()` and `build_presentation_engine()` with identical config
- Could be shared as a single `Arc`

### Scope

1. **`CryptoSignerPool`** — a cheaply-clonable, lazily-initialized signer cache:
```rust
pub struct CryptoSignerPool {
inner: Arc>>>,
tenant_repo: Arc,
}
```
- `get_signer(tenant_id)` checks the cache first; on miss, loads key from `TenantRepo`, builds a `CryptoSigner`, caches it
- Both `IssuanceEngine` and `PresentationEngine` share a single `Arc`
- Key eviction: when a tenant key is rotated, the old entry must be invalidated
- Thread-safe via `Arc>` or `DashMap`

2. **Unified X.509 chain validation**:
- Extract common chain-building and validation logic into `cloud-wallet-crypto::x509` (new module)
- `X509ChainVerifier` struct with:
- `trust_anchors: Arc` — loaded once at startup
- `fn verify_chain(&self, chain: &[DerCertificate]) -> Result`
- Both SD-JWT x5c and mdoc IACA verification should delegate to this
- Error types remain format-specific (wrap the common error), but validation logic is shared

3. **`TrustStoreLoader`** — a single startup-time loader that:
- Reads `root_truststore_dir` (or `iaca_root_paths` / `x5c_root_paths` per #SUB-ISSUE-5)
- Validates each certificate (well-formed X.509, basic-constraints CA:true)
- Returns `TrustStores { iaca: Arc, x5c: Arc }`
- Loaded once in `build_service()`, passed as `Arc` to both engines
- Addresses #365 (production loader for x5c trust anchors)

4. **Shared `OidClient`**:
- Build `OidClient` once in `build_service()`
- Pass `Arc` to both `build_issuance_engine()` and `build_presentation_engine()`
- Reduces duplicate HTTP client construction

5. **Move `load_iaca_roots()` from `utils.rs`** to the `TrustStoreLoader` in `cloud-wallet-crypto` or a new `trust` module in the server crate. `src/utils.rs` should no longer contain trust-anchor loading code.

6. **Test impact**: All existing tests should pass unchanged. New unit tests for:
- `CryptoSignerPool` — cache hit, cache miss, key eviction
- `X509ChainVerifier` — valid chain, expired, self-signed leaf, untrusted root
- `TrustStoreLoader` — PEM+DER, mixed PEM bundles, invalid cert → hard error, empty dir → warn

### Out of Scope

- This issue does not change the X.509 verification algorithm or error handling semantics — only consolidates the duplicated code into one place
- This issue does not add new trust anchor sources (CRL/OCSP is separate)

### Acceptance Criteria

- [ ] `CryptoSignerPool` exists and is shared between `IssuanceEngine` and `PresentationEngine`
- [ ] Signer cache eliminates redundant key parsing on repeated calls
- [ ] X.509 chain validation logic is unified in `cloud-wallet-crypto::x509`
- [ ] SD-JWT and mdoc verification delegate to the unified verifier
- [ ] `TrustStoreLoader` loads and validates roots once at startup
- [ ] IACA and X5C trust anchors share `Arc` references across engines
- [ ] `OidClient` is constructed once and shared
- [ ] `load_iaca_roots()` removed from `src/utils.rs`
- [ ] All existing tests pass unchanged
- [ ] New unit tests for cache, chain validation, and trust store loading cover the scenarios listed above

### Dependencies

- #SUB-ISSUE-5 (consolidated OID4VC config) — the `TrustStoreLoader` should consume config from `Oid4vcConfig`
- #365 (x5c trust anchors production loader) — this issue supersedes and subsumes that work

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.