BlackMesa Labs

A signature proves who and what. Never when.

The "signed at" field of a signed document comes off your own clock. The signature covers it, so it cannot be edited afterwards — and it can be chosen beforehand. Backdating costs nothing, and a chain of signatures does not help either. The answer is four moves, it checks with stock openssl, and it divides across jurisdictions for thirty-two bytes.

A signed report answers two questions: which key signed, and which findings it signed. It is generally taken to answer a third, which is not in there.

The signed_at field is present, and the signature covers it — so it cannot be edited afterwards without breaking the seal. But it is read off the clock of the machine that signs. Yours. Backdating a signed document costs nothing at all: sign with whatever clock you like.

And chaining does not rescue this. A report naming the digest of the one it replaces makes a fine audit trail — as long as somebody else holds the keys. If you hold them, you can rebuild the whole chain in order, and it will verify perfectly.

So: what does a date nobody can argue with look like?

The answer, in four moves

It fits in a sentence: take the date out of your own house. In detail, four moves, none of which asks anybody to trust us.

  1. Have a third party attest a digest, not the document. Thirty-two bytes leave, nothing else. That third party has no stake in your conclusions, and that is what gives its signature value.
  2. Choose a third party whose root is already public, so your reader checks with stock openssl and nothing that came from you.
  3. Compare the imprint before discussing trust. It is a hash comparison; it needs no certificate, no root, no permission.
  4. Keep the earliest attestation, and re-attest while the scheme that signs it still holds.

What you get is precise, and deserves to be stated precisely: this digest existed no later than this date, according to somebody who is not you. That is an upper bound — and it is exactly the shape of the claim “we had inventoried before 31 December 2026”.

The rest of this piece is why each of the four moves is necessary, what it costs, and where it stops.

What a timestamping authority actually does

RFC 3161. You send it a digest, never the document. Thirty-two bytes. It returns a signed token saying this digest was presented to it at this instant.

That is the whole mechanism, and the whole elegance of it: an authority can attest a date without being told what it is dating. None of your code, your file names or your findings leaves the machine — a digest does not run backwards.

The authority also has no stake in your conclusions, which is precisely what gives its signature value.

What we have attested, and why that choice

Three candidates: the file, the signature, or the digest of the findings. We attest the digest, for a reason that fits in one command.

It is printed in the report. So anybody checks the date without ever touching our tool:

openssl ts -verify -digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
  -in report.html.tsr -CAfile <certificate store>

Simplified form, to show the mechanism. That -CAfile is the most expensive trap in this whole piece, and the section “and on which machine” below gives the version that works everywhere.

No file to canonicalise, no field order to agree on, no format of ours. openssl ts -verify -digest takes a hexadecimal digest directly.

And it survives an ephemeral key. When a report is signed by a pair created for it alone and then destroyed, the key no longer exists — the attested date does.

It is also why the token is a file of its own, raw DER beside the signature: that is exactly what the stock openssl ts command reads. An artefact only our tool can open is not evidence, it is a promise.

The trap: openssl ts -verify does not answer the question you think

This is the part that took measurement rather than reading.

openssl ts -verify stops at a certificate chain it cannot build — before comparing the imprint. The consequence: two radically different situations come back with the same failure message.

  • “I cannot get up to a trusted root” — the authority is self-signed, or an intermediate is missing. Not a forgery.
  • “This token attests a different document” — that one is serious.

On a self-signed authority, both print Verification: FAILED. We established it by deliberately passing a wrong digest: the same answer as for the right one.

The fix is to compare the imprint yourself, first, by reading it out of the token. It is a hash comparison: it needs no certificate, no root, no trust. Only then comes the question of the chain.

Which gives four states, not two:

state what it means
verified the authority’s signature holds, up to a root this machine trusts
chain unverified the imprint does match this document; no chain could be built. Self-signed authority or missing intermediate — not a forgery
invalid the token attests a different digest, or it was tampered with
not checkable here no openssl, or no certificate store

Collapsing them turns a missing root certificate into an accusation of forgery. Or, far worse, the reverse.

Which authority? That is a decision, not a default

We put no default authority in the tool. Who attests your dates is a decision of the same order as the regulatory regime you adopt — a default would make it for you, in a jurisdiction you did not pick.

Here is what measurement gives, on free authorities:

authority result
Certum / Asseco (Poland, EU) verified with the system store, RSA-4096
GlobalSign (Belgium, EU) verified with the system store, RSA-3072
DigiCert (United States) verified with the system store, RSA-4096
Sectigo (UK) a token whose signer openssl cannot resolve
FreeTSA (Germany) self-signed: its root must be shipped for anybody to check
Belgian federal TSA self-signed as well
timestamp.apple.com answers, but a self-signed chain and a declared policy of 1.2.3 — not a public service

The deciding criterion is not reputation, it is the public root: a reader checks with stock openssl and nothing that came from you. The moment a root certificate has to travel with your document, your evidence carries a part you supplied yourself — which removes much of the point.

“You have just moved the trust around”

This is the serious objection, and it deserves its strong form: you replaced “believe my clock” with “believe Certum’s clock”. The problem is not solved, it changed address.

It is partly right. Here are the three reasons the move is not neutral, and what is left once they are granted.

1. The shape of the claim changes. Your signed_at is a date you choose. The token is a bound somebody else sets. To backdate, setting a clock is no longer enough: an authority has to issue a false token, or you have to manufacture one — that is, break RSA-4096. We have gone from a free gesture to an attack.

2. The incentives are not the same. The authority does not know what it is dating: it only sees a digest. It therefore has no way to favour one conclusion over another, and its entire business rests on its signature meaning something. Backdating for you means risking that whole business on your file. For an eIDAS qualified authority those obligations are audited, and that is exactly why its root sits in the certificate stores.

3. And above all: this trust divides. One authority is still a single point of trust. But nothing in RFC 3161 prevents presenting the same digest to several authorities — they only see those thirty-two bytes, they have nothing to coordinate, and none of them needs to know it is not alone. We did it on the digest of this site’s inventory, presenting the same imprint to three of them:

authority jurisdiction attested date published token
Certum / Asseco Poland (EU) 6 Oct 2026 21:58 UTC report.html.tsr
GlobalSign Belgium (EU) 8 Oct 2026 02:19 UTC report.html.2.tsr
DigiCert United States 8 Oct 2026 02:19 UTC report.html.3.tsr

Those three files are online beside the report, and all three verify with the recipe below. The marginal cost of an authority is one HTTP request: an authority only sees thirty-two bytes, it has nothing to coordinate, and it does not need to know it is not alone.

The dates differ, and that is the mechanism showing itself: Certum holds the earliest attestation of these findings and keeps it — the other two were added the day we wrote this piece. What this buys is precise, and deserves to be: each authority sets an independent bound. So the date defensible without extending trust to any single operator is the latest of the three, 8 October; the one defensible if you trust Certum is the 6th. Forging the earliest no longer detaches this document from a date: the other two bound it on their own.

What is left, and must be said. A token gives an upper bound, never a lower one: it attests that these findings existed no later than that date, and it will never say they did not exist before. If your need runs the other way — proving a priority somebody disputes — this is not the tool.

And nobody attests retroactively. An authority will not sign last week’s date today, and that is the entire point. The corollary is unpleasant: the date has to be asked for at the moment the document is produced. A timestamp you have to remember to go and fetch is a timestamp nobody fetches — which is the only reason this mechanism belongs in the tool that produces the report, and not in a procedure.

Doing it yourself, today, without us

None of this needs our tool. Four commands, openssl and curl, on any artefact you want to be able to date — an SBOM, an audit report, a release tarball, minutes of a meeting.

# 1. the digest of what you want to date
openssl dgst -sha256 -r artefact.json | cut -d' ' -f1
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672

# 2. the request — "-cert" asks the authority to include its certificate,
#    without which nobody can verify without asking you for it
openssl ts -query -digest 7643cc3f… -sha256 -cert -out timestamp.tsq

# 3. send it. 69 bytes leave, a 6 kB token comes back
curl -s -H 'Content-Type: application/timestamp-query' \
  --data-binary @timestamp.tsq http://time.certum.pl > timestamp.tsr

# 4. the verification — this is the command your reader will run. The first
#    lines find their certificate store and the chain the token already
#    carries; the section "and on which machine" explains why
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in timestamp.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

openssl ts -verify -digest 7643cc3f… -in timestamp.tsr \
  -CAfile "$CA" -untrusted chain.pem
Verification: OK

File the .tsr beside the artefact and print the digest in the document. That is all: your reader needs neither you, nor a house format, nor a library.

To read what a token says — the date, and the imprint it attests:

openssl ts -reply -in timestamp.tsr -text | grep '^Time stamp'
Time stamp: Oct  7 20:50:11 2026 GMT

openssl ts -reply -in timestamp.tsr -text \
  | awk '/^Message data:/{f=1;next} /^[A-Za-z]/{f=0} f' \
  | sed -E 's/^ *[0-9a-f]{4} - //; s/   .*$//' | tr -dc '0-9a-f'
7643cc3ff2957a137b15c509382463efe5fe29c26a14224009462266adf77672

That last line is the practical answer to the trap described above: it compares the imprint without asking anybody for anything, and it works even when the certificate chain does not build.

A note on the tr -dc '0-9a-f', because it illustrates the rest of this piece better than any paragraph could: we first wrote tr -d ' -\n', which works perfectly well on a Mac. GNU’s tr reads those three characters as a range, from space to newline, finds it reversed, and refuses. We found out by replaying this article in an empty Debian container before publishing it. Every command on this page went through it.

With a public-root authority, openssl ts -verify makes the distinction on its own — we altered the document and re-ran the command:

message imprint mismatch
Verification: FAILED

A clear, specific message, which you do not get from a self-signed authority. That is the most concrete argument for choosing one whose root is already in the certificate stores.

In Sablier, all of this is one flag — --timestamp=<url> — because a good practice you have to remember to perform by hand is a good practice nobody performs. The flag repeats, or takes a comma-separated list, and each authority receives the same digest:

sablier scan . --sign=sablier.key \
  --timestamp=http://time.certum.pl,http://timestamp.digicert.com

Each token goes to its own slot — report.html.tsr, report.html.2.tsr, in the order the authorities were named — and the slot belongs to the authority from one run to the next: one that was unreachable today leaves its slot empty instead of shifting the others down and losing their antecedence. sablier verify walks the slots rather than reading the first, because a reader handed three attestations and a command that checks one would have checked a third of what they hold, and concluded on all of it.

But the tokens produced are ordinary files, verifiable with the same commands, by somebody who has never installed the tool.

And you can try it right now, on a real document: this site’s cryptographic inventory is published with its signature and its token.

curl -sO https://mesa.black/audit/report.html
curl -sO https://mesa.black/audit/report.html.tsr

O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

openssl ts -verify \
  -digest 8ecb829ccc278ef7ab3138f56e07ee13319cbb72ce1dabbfc58ee7b4d6b670b5 \
  -in report.html.tsr -CAfile "$CA" -untrusted chain.pem
Verification: OK

The other two tokens sit beside it — report.html.2.tsr and report.html.3.tsr, GlobalSign and DigiCert — and verify with the same command by changing the file name. The next section explains why this block is six lines instead of one.

The digest is printed in the report itself, and the date you will get is 6 October 2026 at 21:58 UTC — the first time these findings were attested, and not the last time we republished the page.

And on which machine? The command everybody publishes is wrong

This is the part we did not see coming, and which took a run of measurements rather than a read of the documentation.

The command found everywhere for verifying an RFC 3161 token is this one:

openssl ts -verify -digest <digest> -in timestamp.tsr \
  -CAfile /etc/ssl/certs/ca-certificates.crt

It contains two false assumptions.

The certificate store is not at that path. ca-certificates.crt is a Debian convention. On Fedora, on Rocky, on openSUSE, that file does not exist — and when -CAfile names a file that is absent, openssl ts -verify does not say “that file does not exist”: it answers Verification: FAILED. Your reader concludes your token is bad.

And the token carries its chain, but not everybody reads it. A token requested with -cert contains the certificates that climb to the root — we checked, ours carries three. OpenSSL 3 uses them. LibreSSL, which is to say the openssl shipped with macOS, does not, and answers:

Verify error:unable to get local issuer certificate
Verification: FAILED

Same failure message. Same wrong conclusion for the reader.

So we looked for a recipe that works everywhere, and measured it on eight targets:

# 1. your openssl's certificate store, wherever it is
O=$(openssl version -d | sed 's/.*"\(.*\)"/\1/')
for f in "$O/cert.pem" "$O/certs/ca-certificates.crt" /etc/ssl/ca-bundle.pem; do
  [ -f "$f" ] && CA="$f" && break
done

# 2. the chain the token already carries, extracted so it can be handed over
openssl ts -reply -in report.html.tsr -token_out -out token.der
openssl pkcs7 -inform DER -in token.der -print_certs -out chain.pem

# 3. the verification
openssl ts -verify -digest <digest> -in report.html.tsr \
  -CAfile "$CA" -untrusted chain.pem
target openssl store found result
Debian 13 OpenSSL 3.5.7 /usr/lib/ssl/cert.pem OK
Ubuntu 24.04 OpenSSL 3.0.13 /usr/lib/ssl/cert.pem OK
Alpine 3.22 OpenSSL 3.5.9 /etc/ssl/cert.pem OK
Fedora 42 OpenSSL 3.2.6 /etc/pki/tls/cert.pem OK
Rocky 9 OpenSSL 3.5.8 /etc/pki/tls/cert.pem OK
openSUSE Leap 15.6 OpenSSL 3.1.4 /etc/ssl/ca-bundle.pem OK
macOS + Homebrew OpenSSL 3.6.0 /opt/homebrew/etc/openssl@3/cert.pem OK
macOS, Apple’s openssl LibreSSL 3.3.6 /private/etc/ssl/cert.pem OK

openssl version -d gives the directory that particular binary was built against, which covers six cases out of eight; openSUSE files its bundle elsewhere, hence the third entry in the loop. And -untrusted chain.pem changes nothing where it was not needed — which is what makes one recipe possible rather than one per system.

It is now the one the report prints, with the note explaining the three steps. Because evidence that can only be checked on the distribution of whoever produced it is not portable evidence, and a reader whose command fails does not learn that their distribution differs: they learn that our seal is broken.

The limit, printed in the document

A timestamping authority signs with RSA or ECDSA. Which is exactly what a post-quantum inventory classes as vulnerable.

A tool that spends forty pages explaining that signatures expire does not grant itself an exception for the one it depends on. So the report writes it next to the date:

What the token does not prove: nothing beyond RSA-4096, the scheme that signs it. A date to be relied on later must be re-attested while that scheme still holds.

A timestamp is evidence for a dispute in the next few years. Not for 2040.

The design trap: the earliest attestation is the only one worth having

This one deserves stating because it is very naturally taken backwards.

A site republishing its inventory happily requests a fresh token at every publication. It is the gesture that looks cleanest — and it destroys the evidence it was meant to build.

Because the only claim that counts is: these findings existed no later than this date. Overwriting the first attestation with a more recent one erases exactly that. You end up with a document proving it existed this morning, when what you wanted to prove is that it existed in October.

The rule that follows is short: the seal does not move while the findings do not move. A token that already attests this digest is kept; a different digest requests a new one. Same for the signature: re-signing it identically produces a chain link joining a digest to itself, which is noise dressed as history.

Why now

The NIS Cooperation Group’s coordinated roadmap, of 23 June 2025, makes the cryptographic inventory a First Step due on 31 December 2026.

“We had inventoried before the deadline” is a claim about a date. It is the only argument that convinced us to add everything above: without a third party, that sentence is worth no more than the word of whoever says it — and the field that carries it comes off their clock.

Good practice

  • Do not confuse “signed at” with “dated”. The first comes off your clock, the second from a third party with no stake in your conclusions.
  • Attest a digest, not a file. It prints in the document, it checks with a standard command, and it survives a destroyed key.
  • Compare the token’s imprint before raising the question of trust. It is a hash comparison, it needs no certificate — and without it, “chain unverifiable” and “this token is about something else” look alike.
  • Choose an authority with a public root, or your evidence travels with a part you supplied yourself.
  • Test the verification command somewhere other than your own machine. The certificate store path and the installed openssl both vary, and both fail with the same Verification: FAILED as a forged token.
  • Keep the earliest attestation. Requesting a token at every publication erases the only thing it established.
  • Divide the trust if the stakes justify it. The same digest can go to two or three authorities in different jurisdictions, for one more HTTP request each. The date defensible without trusting anybody in particular is then the latest in the list.
  • Ask for the date at the moment you produce the document. Nobody will attest it retroactively — that is the point of the mechanism, and its constraint.

Things to watch

  • A chain of signatures held by a single key is not an audit trail: it rebuilds in full, in order, and it verifies.
  • A timestamp rests on a classical signature. It has to be re-attested while the scheme holds, and that has to be said in the document rather than letting an eternal proof be assumed.
  • The digest leaves the machine. It reveals nothing, but if your document promises that nothing leaves, that promise becomes “nothing leaves, unless you request a timestamp” — and that has to be an explicit option, never a default.
  • A token that has to travel with the authority’s root certificate is half-portable evidence.
  • A timestamp bounds from above, never from below. It will never prove a document did not exist before the attested date, and presenting an upper bound as an exact date is the kind of approximation that turns around in front of somebody attentive.

← All posts