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.