Developer guide ยท signature format version 2

Verifying an audit-trail reason signature

An audit-trail response contains a readable JSON array, a detached RSA signature, the signer certificate chain, and an independent RFC 3161 timestamp. Verification establishes that the signed content is unchanged, that the signature was created by the stated certificate, and that the signature existed no later than the trusted timestamp.

1. Signed response structure

The relevant part of a response has the following shape:

{
"reason": [ ... ],
"reasonSignature": {
"protected": {
"version": 2,
"alg": "RS256",
"canonicalization": "RFC8785",
"signMode": "SHA256withRSA",
"signedAt": "2026-09-06T13:24:47.158548463Z",
"certSha256": "Wj_Bn4-_TYGNFIE4OeaxOXd-924NKzbNRe0m6Iv2U-I",
"issuer": "Betrust",
"environment": "test",
"keyAlias": "betrust-aatl-qualified-2029",
"documentation": "https://www.oksign.be/en/blog/verify-signature-json/",
"critical": [
"version", "alg", "canonicalization",
"signedAt", "certSha256"
],
"rootissuerurl": "http://trust.quovadisglobal.com/qvrca1g3.crt"
},
"x5c": [
"base64-end-certificate",
"base64-intermediate-certificate"
],
"signature": "base64url-rsa-signature",
"timestamp": {
"type": "RFC3161",
"target": "signature",
"hashAlgorithm": "SHA-256",
"token": "base64-rfc3161-token"
}
}
}
FieldMeaning
reasonThe JSON array whose integrity is protected.
protectedMetadata included in the JSON signature calculation.
protected.signModeImplementation-level name for the signature operation. For this example it is SHA256withRSA, consistent with alg = RS256.
protected.issuerInformational label for the signing organization.
protected.environmentInformational label for the deployment environment that produced the signature.
protected.keyAliasInformational identifier for the signing-key configuration.
protected.documentationInformational link to documentation for this signature format or service.
protected.rootissuerurlInformational certificate-source hint. It must not be fetched or trusted automatically.
x5cEnd certificate first, followed by available intermediate CA certificates. These certificates are evidence, not trust anchors.
signatureUnpadded Base64url encoding of the RSA signature.
timestamp.tokenBase64 encoding of a DER RFC 3161 timestamp token covering the decoded signature bytes.

2. Required trust configuration

The verifier needs two independently configured sets of trust anchors:

Configured Timestamp Authorities

The service normally requests its RFC 3161 timestamp from the primary Timestamp Authority. If that service is unavailable, it may use the backup Timestamp Authority.

RoleRFC 3161 endpoint
Primary https://tsa.firmaprofesional.com/tsaec
Backup https://tsa.esigna.es/TSA/

The RFC 3161 token cryptographically identifies the TSA that actually issued it through the embedded TSA signer certificate. The endpoint used to request the token is therefore operational information and is not a trust decision. The verifier must accept the token only when the TSA signer certificate builds to one of the verifier's independently configured TSA trust anchors.

API documentation may link to each provider's official certificate repository to help administrators obtain the applicable root and intermediate certificates. Such links are onboarding and maintenance aids only. Administrators must authenticate the source and verify the certificate fingerprints through an independent approved channel before adding a root to the TSA trust store. Verification software must not automatically download or trust a root certificate from a URL in the response, the timestamp token, or a certificate extension.

Do not create trust from the response itself. A certificate, issuer URL, AIA URL, or fingerprint transported inside the response cannot independently make its own signature trustworthy. Trust anchors must be obtained through approved API documentation, controlled deployment, customer onboarding, or another authenticated channel.

3. Verification procedure

  1. Parse and validate the envelope

    Parse the response as JSON and obtain reason and reasonSignature. Reject malformed input, unsupported versions, unsupported algorithms, missing required fields, duplicate JSON property names, and unknown fields declared critical.

    Preserve every member of protected exactly as received. All protected members, including non-critical informational fields, are covered by the signature. The critical array tells a verifier which protected fields it must understand; fields absent from that array must still be retained during canonicalization.

    For format version 2, require:

    • alg = RS256
    • canonicalization = RFC8785
    • If signMode is present, it is consistent with alg (for this example, SHA256withRSA).
    • timestamp.type = RFC3161
    • timestamp.target = signature
    • timestamp.hashAlgorithm = SHA-256
  2. Decode the signer certificates

    Decode each x5c entry using standard Base64 and parse it as an X.509 certificate. The first certificate is the end-entity signer. Check that its public key is RSA and that its Key Usage permits digitalSignature or contentCommitment. Apply the Extended Key Usage and certificate-policy requirements defined by the relying party.

  3. Bind the end certificate to the protected metadata

    Calculate SHA-256 over the complete DER encoding of the first certificate. Compare the result, using a constant-time comparison, with the unpadded Base64url-decoded protected.certSha256.

  4. Reconstruct the signed JSON value

    Create a temporary JSON object with exactly these two members:

    {
    "protected": <received protected object>,
    "reason": <received reason array> }

    Do not add application fields to this temporary object. Do not remove or reinterpret protected fields before canonicalization.

    Values such as issuer, environment, keyAlias, documentation, and rootissuerurl are authenticated only as values supplied by the signer. They must not be used as trust anchors or as authority to download certificates or change verifier policy.
  5. Canonicalize and verify the JSON signature

    Canonicalize the temporary object according to RFC 8785 (JCS), encode the canonical result as UTF-8, and verify the decoded signature using SHA256withRSA. The RS256 identifier means RSASSA-PKCS1-v1_5 with SHA-256.

    Object-property order and insignificant whitespace do not affect JCS output. Array order, property presence, JSON types, strings, and values remain significant.
  6. Verify the RFC 3161 timestamp token

    Decode timestamp.token using standard Base64 and parse the resulting DER value as an RFC 3161 timestamp token. Then:

    • Calculate SHA-256 over the decoded signature bytes.
    • Compare it with the timestamp token's message imprint.
    • Verify the timestamp token's CMS signature.
    • Check the TSA certificate's timestamping Extended Key Usage.
    • Build and validate its certificate path against independently trusted TSA roots approved for the primary or backup provider.
    • Apply the configured TSA revocation and certificate-policy rules at the token generation time.
    • Reject a generation time unreasonably later than the verifier's clock.
  7. Validate the JSON signer at the trusted timestamp time

    Once the timestamp is trusted, use its generation time as the PKIX validation time for the JSON signer certificate path. Build the path from the end certificate and intermediates in x5c to an independently configured signer trust anchor. Apply revocation and policy requirements appropriate for historical validation.

    The protected signedAt is a signer assertion. It should not be later than the trusted RFC 3161 time beyond the permitted clock tolerance, but it is not itself the trusted source of time.

4. High-level Java invocation

When using the supplied Java implementation, the caller parses the JSON and provides separate PKIX configurations for the signer and the TSA:

JSONObject auditTrail = new JSONObject(receivedJson); JSONArray reason = auditTrail.getJSONArray("reason"); JSONObject reasonSignature =
auditTrail.getJSONObject("reasonSignature"); PKIXParameters signerTrust = createSignerPkixParameters(); PKIXParameters tsaTrust = createTsaPkixParameters(); VerificationResult result = ReasonSignature.verifyReason(
reason,
reasonSignature,
signerTrust,
tsaTrust
); System.out.println("Signer: "
+ result.signerCertificate().getSubjectX500Principal()); System.out.println("Timestamp: " + result.timestampedAt()); System.out.println("TSA: "
+ result.tsaCertificate().getSubjectX500Principal());

A successful return means that all checks implemented by the configured verifier completed successfully. A verification or path-building exception must be treated as a failed verification, not as a warning.

5. What successful verification establishes

Successful verification establishes: the signed reason and protected metadata are unchanged; the RSA signature matches the validated end certificate; the timestamp token covers that signature; and a trusted TSA asserts that the signature existed at the returned generation time.

It does not, by itself, establish:

6. Operational requirements