Architecture note · ETSI TS 119 432 conformance mapping

Signature Creation Application (SCA).

The CodeB Remote Signature Service — the browser signer at sign.html together with the CSC v2 API server — plays the Signature Creation Application (SCA) role defined by ETSI TS 119 432. Its job is to compose the Data-To-Be-Signed (DTBS), run the Signature Activation Protocol (SAP) so the user authorises signature creation, hand the SAD off to the Remote Wallet Secure Cryptographic Device (RWSCD, currently our software-backed ICryptoModule), and finalise the signature into a PAdES envelope. This page maps each CodeB endpoint and browser step onto the ETSI vocabulary so an auditor can trace conformance end-to-end.

Scope reminder. The RWSCD is currently software-backed (per-tenant PFX; per-user credential in Phase 1b), so the composite system produces AdES per Regulation (EU) 910/2014 Art. 3(11). Not QES. Not qualified. The ICryptoModule boundary is HSM-ready — when wired to a certified QSCD (Azure Key Vault Managed HSM at FIPS 140-3 Level 3, or PKCS#11 against Utimaco / Thales), the same SCA continues to work unmodified against a qualified RWSCD.

The four ETSI actors

ETSI TS 119 432 identifies four cooperating actors in a cloud signature. Every remote signature flow in CodeB is a specific realization of these:

  • Signer — the natural person whose intent binds the signature. Signs in as themselves via OIDC.
  • SCA (Signature Creation Application) — composes the DTBS, gathers signer intent, orchestrates SAP, drives the RWSCD, finalises the envelope. = sign.html + csc.ashx.
  • SIC (Signature Initiator Client) — where the signer starts (a document viewer, a PMS, a legal-review app). In the reference flow the SIC and SCA are collocated inside sign.html; in the API flow the SIC is your business app calling the CSC v2 API.
  • RWSCD (Remote Wallet Secure Cryptographic Device) — holds the signing key, releases operations only on presentation of a valid SAD. = ICryptoModule boundary. Today: SoftwareCryptoModule.cs. Roadmap: HsmAzureKeyVaultCryptoModule.cs, HsmPkcs11CryptoModule.cs.

ETSI TS 119 432 verb → CodeB endpoint mapping

Every ceremony verb in the ETSI spec has a concrete corresponding call in the CodeB stack:

ETSI TS 119 432 verbCodeB endpoint / stepWhat happens
identify-signer OIDC authorization code + PKCE → access token Standard OIDC round-trip. See the OIDC sign-in cookbook.
discover-credentials POST /csc/v2/credentials/list Returns credential IDs owned by the identified signer.
get-credential-info POST /csc/v2/credentials/info Returns subject DN, issuer DN, serial, validity window, algorithm.
prepare-signature SCA composes the CMS signedAttributes DER, hashes with SHA-256 Client-side inside sign.html (browser flow) or server-side inside signDoc (server flow). Produces the SAD-bound hash.
authorize-signature (SAP init) POST /csc/v2/credentials/authorize SCAL1 → returns SAD immediately. SCAL2 → returns pending=true + WebAuthn challenge.
authorize-signature-confirm (SAP finalize) POST /csc/v2/credentials/authorize/confirm SCAL2 only. Confirms user intent via WebAuthn assertion + PIN. Releases the SAD JWT.
authorize-signature (SAP via wallet) OpenID4VP vp-start with transaction_data ARF §5.7.5 alternative: the EU Wallet becomes the SAP authoriser. See ARF §5.7.5 binding below.
sign-hash POST /csc/v2/signatures/signHash RWSCD signs the SAD-bound hash. Returns raw ECDSA r||s.
sign-doc POST /csc/v2/signatures/signDoc Server-side one-shot: SCA composes + RWSCD signs + PAdES assembly. Returns signed PDF.
finalize-signature SCA assembles the PAdES envelope (client-side sign.html or server-side signDoc) Levels B-B / B-T / B-LT / B-LTA per digital signatures cookbook.
timestamp-signature POST /csc/v2/signatures/timestamp  or  POST /csc/v2/signatures/tsa RFC 3161 timestamp. Proxy to external TSA (Sectigo default) OR in-house issuer (tenant-generated TSA cert).
revocation-info POST /csc/v2/ocsp RFC 6960 OCSP for the signer + TSA certs. Used to embed id-aa-ets-revocationValues for PAdES-B-LT.

Signature Activation Level (SCAL) trust anchors

Per ETSI EN 419 241-1:

SCAL1

Sole control level 1 — the signer authorises through user identification only (session-bound). Suitable for low-risk, one-off signatures.

  • Trigger: ?scal=1 or tenant default Csc:DefaultScal=1
  • Flow: credentials/authorize returns the SAD immediately
  • SAD lifetime: 300 s

SCAL2

Sole control level 2 — the signer authorises through user identification plus a per-signature explicit consent (PIN, WebAuthn, or wallet VP). Suitable for QES trajectories and CT2274 SCA/RWSCD boundaries.

  • Trigger: ?scal=2 or tenant default Csc:DefaultScal=2
  • Flow: authorize returns pending + challenge → authorize/confirm after user gesture
  • Second factor options: PIN, WebAuthn (FIDO2 assertion), or wallet VP with matching transaction_data_hashes (see below)

ARF §5.7.5 — wallet as SAP authoriser

The July 2026 EU Digital Identity Wallet Architecture Reference Framework 3.0 defines a transaction_data parameter on OpenID4VP requests (Sec. 5.7.5, referencing OpenID4VP 1.0 Final §5.7.5). This is the mechanism that lets the EU Wallet substitute for the WebAuthn / PIN second factor in SCAL2 — making the wallet the authorised source of signer intent.

Wire format

The SCA appends ?transaction_data=<b64u> to /oidc.ashx?action=vp-start. The b64u value decodes to a JSON array of base64url-encoded transaction_data entries. Each entry decodes to a JSON object with:

{
  "type":           "qes_authorization",              // ETSI TS 119 432 SAP marker
  "credential_ids": [ "aloaha-eudi-pid" ],           // DCQL credential id(s) the entry binds to
  "documentDigests": [
    { "hash": "<b64 SHA-256 of DTBS>",
      "hashAlgorithmOID": "2.16.840.1.101.3.4.2.1",
      "label": "Contract v3.pdf" }
  ],
  "processID": "sca-txn-a1b2c3d4"
}

The server (see oidc.ashx vp-start) validates that:

  • the outer array has 1–16 entries;
  • each entry is a base64url string that decodes to a JSON object;
  • each entry has type and a non-empty credential_ids array;
  • the total value fits inside the 32 KiB body cap.

Any failure returns HTTP 400 invalid_request with an error_description that names the failing check (fail-closed per RASP policy).

What the wallet does

Per OpenID4VP 1.0 Final §5.7.5, when the wallet processes each entry it MUST:

  • display the transaction context to the holder for explicit consent;
  • compute hash(transaction_data_entry_string) using an agreed hash algorithm (SHA-256 baseline);
  • include transaction_data_hashes and transaction_data_hashes_alg inside the KB-JWT of every SD-JWT VC in the returned VP token.

What the SCA verifies

The SCA re-computes the same hash locally, compares byte-for-byte with the wallet's echoed hash, and only then mints the SAD JWT with authorization_source=wallet. The SAD payload includes transaction_data_hashes as a signed claim so downstream RWSCD calls can verify the same binding.

What ships today (2026-07-27)

  • Shipped: vp-start accepts transaction_data, validates strictly, stores on VpSession, and emits the array inside the signed JAR request object so wallets that implement §5.7.5 hash + echo correctly.
  • Shipped: LOUD diagnostics ([ARF-TXDATA-DIAG]) + [METRIC] arf.txdata.accepted|rejected.
  • Shipped: per-tenant persistence through SaveVpSessionToDisk so the binding survives app-pool recycles for the full 300 s TTL.
  • Next iteration: KB-JWT transaction_data_hashes extraction inside vp-response + surface into the GET-poll body + SAD-issuance hook in credentials/authorize.

DTBS / DTBSR construction

Per ETSI TS 119 432 §5.3, the SCA constructs the Data To Be Signed (DTBS) and its representation (DTBSR = SHA-256 of the CMS signedAttributes). CodeB layout:

  • DTBS = the bytes of the PDF covered by the ByteRange array (the whole file except the /Contents<...> hex window).
  • signedAttributes = DER-sorted SET of five mandatory CMS attributes: contentType, messageDigest (SHA-256 of DTBS), signingTime, signingCertificateV2 (RFC 5035 ESSCertIDv2 with issuerSerial), id-aa-CMSAlgorithmProtection (RFC 6211).
  • DTBSR = SHA-256 of the DER-encoded signedAttributes SET.
  • SAD binding = the DTBSR base64 is the hash[0] value in credentials/authorize. The SAD JWT payload includes this same hash, and signHash refuses to sign any value that does not match.

This layout produces envelopes that Adobe Reader (built-in Adobe.PPKLite validator) verifies as PAdES basic profile, and that pyHanko / iText validate as PAdES-B-B / B-T / B-LT / B-LTA depending on the added unsigned attributes.

Observability + ceremony logging

Every ceremony verb emits a diagnostic line and a metric so an auditor can reconstruct the full SCA flow from logs alone:

[CSC-SCAL2-DIAG] authorize enter tenant=<t> sub=<sub> scal=2 hashes=1
[CSC-SCAL2-DIAG] webauthn-options-minted tenant=<t> authId=<16hex> ...
[CSC-SCAL2-DIAG] confirm-ok tenant=<t> authId=<16hex> factor=webauthn+pin
[METRIC] csc.sad.issued tenant=<t> scal=2 factor=webauthn
[ARF-TXDATA-DIAG] vp-start tenant=<t> sid=<sid> tx_entries=1 raw_bytes=488
[METRIC] arf.txdata.accepted tenant=<t> entries=1
[ARF-TXDATA-DIAG] JAR-embed sid=<sid> entries=1

The metric tags are stable across releases so downstream SIEM pipelines can build dashboards.

Standards + citations

  • ETSI TS 119 432 — Protocols for Remote Digital Signature Creation. SCA / SIC / RWSCD / Signer roles + ceremony verb catalogue.
  • ETSI EN 419 241-1 — Trustworthy Systems Supporting Server Signing. SCAL1 vs SCAL2 definitions.
  • Cloud Signature Consortium API v2 — concrete wire realization for most SCA verbs.
  • OpenID4VP 1.0 Final §5.7.5 — transaction_data parameter + transaction_data_hashes return.
  • EU Digital Identity Wallet Architecture Reference Framework 3.0 (21 July 2026) §5.7.5 — wallet-as-SAP profile.
  • Regulation (EU) 910/2014 Articles 3(11), 26, 32 — AdES definition, validation requirements.