Skip to content

feat(tsa): parse RFC 3161 requests and sign timestamp tokens - #352

Merged
Bugs5382 merged 1 commit into
mainfrom
feat/287-tsa-tokens
Oct 6, 2026
Merged

Bugs5382 merged 1 commit into
mainfrom
feat/287-tsa-tokens

Conversation

@Bugs5382

@Bugs5382 Bugs5382 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

What and why

Second of the RFC 3161 time-stamp authority PRs (task 3 of the issue): request parsing and token
building in internal/tsa, on the hand-written CMS package. Still not wired into boot; the listener and
the config mapping follow.

Responder.Respond takes a DER TimeStampReq and always answers with a DER TimeStampResp.

Requests (RFC 3161 section 2.4.1):

  • Version 1 only. The message imprint must be SHA-256, SHA-384 or SHA-512 (parameters absent or NULL)
    with a hash of the right length.
  • SHA-1, MD5 and any other algorithm get badAlg. A different reqPolicy gets unacceptedPolicy.
  • Any request extension gets unacceptedExtension, as section 2.4.1 requires for one the server does
    not recognise. This TSA recognises none.
  • Malformed or trailing bytes get badDataFormat. A signing failure gets systemFailure.
  • The rejection's statusString says what the failure bit means. The detail goes to the log only.

Tokens (section 2.4.2, RFC 5816):

  • The TSTInfo echoes the message imprint byte for byte and the nonce, and names the configured policy.
  • The serial number is 159 random bits from crypto/rand: positive, within the 160 bits requesters must
    handle, and unique per token.
  • genTime is UTC GeneralizedTime to the millisecond, with trailing zeros dropped. encoding/asn1
    only writes whole seconds, so it is hand-encoded.
  • accuracy is seconds plus millis, from the configured value. ordering is left at its default,
    FALSE. The tsa name and extensions are left out.
  • The SignedData (id-ct-TSTInfo, version 3) is built by internal/cms and signed by the TSA key
    through CertManager.WithSigner. The signer is identified by issuer and serial number.
  • The signed attributes are content-type, message-digest and signing-certificate-v2. Its one
    ESSCertIDv2 has the SHA-256 hash of the TSA certificate (the DEFAULT algorithm, so DER leaves it out)
    and the certificate's issuer and serial.
  • The digest is the one the node pairs with the key when it signs certificates (SHA-384 for P-384 and
    RSA 3072 or larger). A P-384 TPM key only signs SHA-384 digests.
  • The TSA certificate is carried only when certReq is set.

Refs #287

Merge order: after #351 (merged). The listener PR follows this one.

No docs change in this PR: nothing is user-facing until the config mapping lands, and the docs PR
ships with that one.

Verification

  • Lint clean
  • Tests pass
  • Build succeeds
  • Documentation updated (if behavior or API changed): not yet user-facing, see above

How this was verified

task ci and task license pass locally.

The tests check the tokens independently of the encoder:

  • They parse the response with their own RFC 3161 structures over encoding/asn1, and verify the
    SignedData with cms.ParseSignedData and Verify.
  • They check the ESSCertIDv2 hash, the issuer and serial, and that the hash algorithm is omitted.
  • They check the imprint, nonce, policy and serial, and the exact genTime and accuracy encodings.
  • They check that the failure bit is the only bit set and the last one in each rejection.
  • They check that 64 tokens get 64 distinct serials, and cover both P-384 and RSA 3072 TSA keys.

openssl ts (OpenSSL 3.6) builds SHA-256, SHA-384 and SHA-512 queries, with and without a nonce,
certReq and the policy. openssl ts -verify checks each reply against the CA, from both the query file
and the data, for both key types. It reports Verification: OK. openssl ts -reply -text shows the
policy, the accuracy and Ordering: no, and reads a SHA-1 query's rejection as an unsupported
algorithm.

As a mutation check, making the EKU non-critical makes openssl ts -verify fail. Dropping the
ESSCertIDv2 issuerSerial fails the parser tests. The OpenSSL tests skip without OpenSSL 3 locally but
fail under CI, like the CMS package's.

Add the responder that turns a TimeStampReq into a TimeStampResp.
Version 1 requests with a SHA-256, SHA-384 or SHA-512 message imprint
are granted; SHA-1, MD5 and other algorithms get badAlg, a different
requested policy gets unacceptedPolicy, extensions get
unacceptedExtension, and malformed requests get badDataFormat.

The token echoes the imprint and nonce, names the configured policy,
has a random 159-bit serial, a millisecond genTime and the configured
accuracy, and is a SignedData built by internal/cms with a
signing-certificate-v2 attribute (RFC 5816) naming the TSA certificate.
Tests check the tokens with the stdlib parser and with openssl ts
-verify.

Refs #287

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382 Bugs5382 self-assigned this Oct 6, 2026
@Bugs5382
Bugs5382 marked this pull request as ready for review October 6, 2026 20:00
@github-actions github-actions Bot added the enhancement New feature (feat). Minor version bump. label Oct 6, 2026
@Bugs5382

Bugs5382 commented Oct 6, 2026

Copy link
Copy Markdown
Contributor Author

Closing summary: adds the RFC 3161 request parser and token builder (Responder) to internal/tsa: SHA-2 imprints only (badAlg otherwise), policy, extension and format checks, TSTInfo with echoed imprint and nonce, 159-bit random serial, millisecond genTime, accuracy, and a SignedData via internal/cms with signing-certificate-v2 (RFC 5816). Tokens are verified in tests by the stdlib parser and by openssl ts -verify. CI green.

@Bugs5382
Bugs5382 merged commit c8ad650 into main Oct 6, 2026
25 checks passed
@Bugs5382
Bugs5382 deleted the feat/287-tsa-tokens branch October 6, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature (feat). Minor version bump.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant