Verifying a timestamp

Every token embeds its own signing chain, so verification is a self-contained check — it never requires contacting CNX. The steps below work on any .tsr token produced by either TSA endpoint.

1. Decode the token

openssl ts -reply -in report.pdf.tsr -text

This shows the status, policy OID, message hash, serial number, generation time, and TSA identity.

2. Check the policy OID

Confirm the OID matches a version listed at tsa.cnx.net.kh/policy.html — see Request and token format for what the current OID is and why an older token can legitimately carry a different one.

3. Extract the chain

openssl ts -reply -in report.pdf.tsr -token_out \
  | openssl pkcs7 -inform DER -print_certs > chain.pem

chain.pem now holds three certificates in order: the signer (specific to the TSA node that issued the token), the TSA intermediate, and the root CA.

4. Pin the root

Don't trust the root certificate in chain.pem on its own — it came from the same response you're verifying. Pin it against the value CNX publishes independently:

# Extract the root's public key and hash it
awk '/-----BEGIN/{c++} c==3' chain.pem > root.crt
openssl x509 -in root.crt -pubkey -noout \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary | base64

# Compare against the DNSSEC-signed published pin
dig +dnssec TXT _ca.cnx.net.kh

See The trust anchor for what's being compared here and why.

5. Verify

openssl ts -verify -data report.pdf -in report.pdf.tsr \
  -untrusted chain.pem -CAfile root.crt

Verification: OK confirms three things together: CNX issued the token, the hash matches report.pdf exactly as it exists now, and the root you verified against in step 4 is the one that signed the chain.