Skip to content

JWS Serialization & JAdES Compliance Tiers

This page explains the two standards-honest representations an ANKASecure signature can take on the attached sign endpoints, how a client chooses between them with the optional serialization parameter, and how the choice interacts with the backend-authoritative RFC 3161 timestamping policy. It is written for direct REST API clients; SDK and CLI users get the same behavior through ergonomic overloads/flags (see SDK & CLI Overview*).


1. Two Equally-Standard Forms

RFC 7515 (JSON Web Signature) defines two first-class, equally-standard serializations of the same signature:

  • JWS Compact Serialization — the familiar three dot-separated segments base64url(protected).base64url(payload).base64url(signature). Compact, URL-safe, and the default when no timestamp is mandated. It has no unprotected header, so it cannot carry any post-signing metadata.
  • JWS JSON Serialization — a JSON object that can carry an unprotected header (header) alongside the protected header and signature, and can hold multiple signatures (used by composite / hybrid keys). Because it has an unprotected header, it can carry post-signing metadata such as a signature timestamp.

Neither form is "more correct" than the other — the choice is a representation decision, not a security one.


2. Why a Timestamp Forces JWS JSON

ETSI TS 119 182-1 (JAdES) places the signature-timestamp sigTst inside the JWS Unprotected Header, at header.etsiU[].sigTst. Only JWS JSON Serialization has an unprotected header. Therefore:

A timestamped (qualified) signature can only be JWS JSON Serialization — never Compact.

This is a standards requirement, not a product opinion. It yields two honest compliance tiers:

Compliance tier Serialization Timestamp Standard
RFC 7515 JWS Compact or JSON none RFC 7515
JAdES B-T JSON only sigTst embedded ETSI TS 119 182-1

3. Two Orthogonal Axes

Output is resolved from two independent inputs:

  • serialization — the client's representation choice, an OPTIONAL request field serialization ∈ {COMPACT, JSON} on the attached sign endpoints. Omit it to let the server pick the standards-correct form.
  • stamping — the backend-authoritative RFC 3161 TSA usage policy (deployment ceiling/floor + optional per-tenant override), described in TSA RFC 3161 Time-Stamping Architecture*. A client cannot turn stamping on or off; it is governed per tenant/deployment.

The hard rule at the seam: stamping ⇒ output MUST be JWS JSON.


4. Behavior Truth Table (attached sign)

Applies to POST /api/v3/crypto/sign, /api/v3/crypto/resign, and /api/v3/crypto/sign-encrypt (for sign-encrypt the table governs the inner JWS that is then encrypted):

serialization effective stamping server action output 400?
omitted OFF server chooses compact JWS no
omitted REQUIRED server chooses (auto-upgrade) JWS-JSON + sigTst no
COMPACT OFF honor compact JWS no
COMPACT REQUIRED impossible — 400 problem+json
JSON OFF honor JWS-JSON, no sigTst no
JSON REQUIRED honor JWS-JSON + sigTst no

Two invariants hold on every row:

  • An explicit COMPACT is never silently converted to JSON.
  • A mandated stamp is never silently dropped.

The COMPACT + REQUIRED row is the only combination the platform rejects.


5. The Single Rejection (COMPACT + REQUIRED)

When a client explicitly asks for COMPACT but the effective stamping policy is REQUIRED, the two cannot be reconciled (a compact JWS cannot carry sigTst), so the request is rejected — loudly, before anything is signed:

{
  "type": "https://docs.ankatech.co/errors/serialization-incompatible-with-timestamp",
  "title": "Serialization Incompatible With Timestamp",
  "status": 400,
  "detail": "A required RFC 3161 signature timestamp mandates JWS JSON Serialization; COMPACT serialization cannot carry the timestamp. Omit the serialization field or request JSON.",
  "instance": "/api/v3/crypto/sign",
  "extensions": { "errorCode": "SERIALIZATION_INCOMPATIBLE_WITH_TIMESTAMP" }
}
  • Content-Type: application/problem+json (RFC 7807).
  • The detail is oracle-safe: it names only the RFC 3161 → JWS-JSON requirement; it never echoes the key id, tenant, or any internal path.
  • No signature is produced — the compact token is not upgraded and the stamp is not dropped.
  • instance is the endpoint that was called (/api/v3/crypto/sign, /resign, or /sign-encrypt); for sign-encrypt no partial JWE is produced on rejection.

Precedence. If the timestamping policy is genuinely unavailable (TSA unreachable, or an unresolved REQUIRED policy), the endpoint returns 503 first — a real outage pre-empts this 400, even for an explicit COMPACT request.


6. What the Client Parses (wire contract)

On the HTTP wire the sign responses always wrap the produced JWS in a typed JWS JSON Serialization object (jwsToken) inside the response metadata envelope (keyRequested, materialVersion, algorithmUsed, warnings). The serialization choice changes only the JWS representation within jwsToken:

  • Compact tier — a single-signature JWS-JSON object with no unprotected header and no sigTst. It is losslessly reducible to the RFC 7515 three-segment compact string; the SDK performs that reduction so a sign call persists a compact token.
  • JSON / JAdES-B-T tier — a JWS-JSON object; when stamped it carries the JAdES timestamp at header.etsiU[].sigTst.tstTokens[].val, for a SIMPLE and a composite key alike. A composite JWS carries one composite signature value, so it has the single-signature shape and the timestamp imprint covers that one value.

A client that needs a compact string can reduce a single-signature, no-unprotected-header JWS-JSON to compact; a stamped JWS-JSON cannot be reduced without discarding the timestamp (which is exactly why stamping forces JSON).


7. Streaming Is Unchanged

The streaming sign endpoints (/api/v3/crypto/stream/sign, /stream/resign) do not accept a serialization parameter. They already always emit detached JWS JSON Serialization — the flattened {"protected","signature"} body with the payload omitted and RFC 7797 b64: false + crit: ["b64"] in the protected header, for SIMPLE and composite keys alike (a composite header carries only alg, b64, crit, kid, ank_kv, iat and, when requested, exp, where a SIMPLE header also carries ver and tid) — stamped or not — they are TSA-native. No compact-detached form exists. See Streaming Operations*.


8. Verify Is Serialization-Agnostic and JAdES-Aware

The verify endpoints — /api/v3/crypto/verify, /api/v3/crypto/stream/verify, and the decrypt-then-verify inside /api/v3/crypto/decrypt-verify — auto-detect the serialization (compact vs JWS-JSON), so a client never has to declare which form it holds. Each verify response surfaces an informational qualifiedTimestamp block:

{
  "present": true,
  "trustStatus": "VALID",
  "genTime": "2026-07-05T10:20:00Z",
  "serialNumber": "84215045",
  "policyOid": "1.3.6.1.4.1.99999.1.1"
}
  • trustStatus ∈ VALID, SIGNATURE_INVALID, IMPRINT_MISMATCH, UNTRUSTED, REVOKED, EXPIRED — the first failing check decides: the token's own signature and certificate identifier, then whether its imprint covers the presented signature, then its certificate as it stood at genTime. The stamp's age is never judged at verify.
  • genTime, serialNumber and policyOid are present only when the token is authenticated and covers the presented signature; for SIGNATURE_INVALID, IMPRINT_MISMATCH and a malformed token the block carries present and trustStatus only.
  • The block is omitted when no sigTst is present.
  • It is purely informational and never flips the boolean valid: a cryptographically valid signature whose timestamp is absent or untrusted still verifies valid=true.

The timestamp semantics live in TSA Architecture — Verify: Extract, Validate, Report*.



Target Audience: Integration Developers, API Consumers, Compliance Officers

* restricted content — see higher-tier documentation