Skip to content

evidence(time): RFC 3161 time stamps of the signed records v0.2.1, v0.2.0 and v0.1.5 (DigiCert, 2026-11-03) - #32

Merged
sinkog merged 2 commits into
develfrom
evidence/time-digicert-2026-11-03
Nov 3, 2026
Merged

sinkog merged 2 commits into
develfrom
evidence/time-digicert-2026-11-03

Conversation

@sinkog

@sinkog sinkog commented Nov 3, 2026

Copy link
Copy Markdown
Member

What

The T3 decision of the trust model plan (cic-primitive-forms docs/P5-TRUST-MODEL-PLAN.md, section 4): independent evidence that the three signed records existed before the certificate chain that signed them ends on 2026-12-31. Data and a verification script only: no code, no change to the records, the verifier or the release branches.

Why this could not wait

Every certificate of the chain ends on 2026-12-31 (13:22 UTC for the three that matter), and the present verifier judges validity at the moment of checking, so from 2027-01-01 it refuses these records (measured, forms #149). Evidence of existence before the end has to be made before it. A token obtained in 2027 would show a date after the end.

What was done

Three RFC 3161 requests to the DigiCert public time-stamp authority (http://timestamp.digicert.com), one per record. Sent: a SHA-256 digest, the algorithm, a random nonce and a request for the TSA certificate: 70 bytes each. Nothing else left the machine.

Release Record SHA-256 Stamp (UTC)
v0.2.1 e5d8268aede0d24f... 2026-11-03 22:07:48
v0.2.0 955fab2d6d9638c7... 2026-11-03 22:09:44
v0.1.5 284e44bfcf62d79f... 2026-11-03 22:09:45

Verified offline, before reporting (and the verification can fail)

  • Each reply: Granted, the digest equals the request, the nonce equals the request (the reply belongs to it), the TSA certificate has the critical Time Stamping key usage.
  • openssl ts -verify against the request, against the record file read from git at its release commit, and at the time of the stamp (not "now"): OK for all three.
  • The trust anchor is the self-signed DigiCert Trusted Root G4 (valid to 2038), not the cross-signed copy the reply carries (valid to 2031). Its SHA-256, SHA-1 and serial were checked against two independent sources: the operating system store (Ubuntu ca-certificates) and the figures DigiCert publishes. All three values are equal.
  • Negative controls, each refused: a record changed by one byte, a wrong anchor, a damaged reply, a request with another nonce, a time before the TSA certificate existed. One expectation of mine was wrong and is recorded rather than hidden: verification without the intermediates still passes, because the reply carries them and OpenSSL uses them; the anchor is what matters, and the wrong-anchor control proves that.
  • verify.sh re-checks everything from the stored files and the git history, and --selftest re-runs the negative controls. verify.sh itself was shown to fail on a damaged token, a changed manifest, a replaced anchor and missing commits.

What it proves, and what it does not

Each digest existed no later than the stamped time, according to DigiCert, bound to the request and to the exact bytes of the record. It does not show who signed, that a signature was valid that day, that the signer was allowed, or that the release is genuine; it supplies the one piece that could not be made late. The rest of the plan (anchor policy, authority, revocation, an as-of verifier mode) is unchanged and still open.

Limits, written down in the README

  • No terms of use for the public endpoint were found. The search and DigiCert's documentation pages state neither permission nor restriction for uses other than code signing. The decision named "the endpoint and the terms to be checked"; the endpoint works, the terms could not be confirmed. Only digests of public records were sent. Another authority needs a new decision.
  • Plain HTTP (port 443 did not answer from that machine); the replies are signed, so the check does not depend on the transport.
  • The time rests on DigiCert and on one root, checked against the operating system store and the publisher's page.
  • The decision also asks for a second, independent copy; that is not done here. The GitHub repository is one copy; where the second lives is for the owner to name.

Merge is for the owner.

Gábor Z Sinkó added 2 commits November 3, 2026 23:12
….2.0 and v0.1.5 (DigiCert, 2026-11-03)

Every certificate of the chain that signed these records ends on 2026-12-31 and the present
verifier judges validity at the moment of checking, so from 2027-01-01 it refuses them. Evidence
that a record existed before the chain ended has to be made before it ends; a token obtained in
2027 only shows a date after the end.

Three tokens from the DigiCert public time-stamp authority over the SHA-256 of the three signed
records, each answering its own request (nonce), the digest equal to the record read from git at
its release commit. Stamp times 2026-11-03 22:07:48, 22:09:44 and 22:09:45 UTC.

Kept: the 70-byte requests, the replies, the TSA chains the replies carry, the self-signed
DigiCert Trusted Root G4 as the trust anchor (taken from the operating system store and equal to
the SHA-256, SHA-1 and serial DigiCert publishes), a manifest, and verify.sh which re-checks all
of it offline, the token at the time stamp and not at "now", and with --selftest shows that a
record changed by one byte and a wrong anchor are refused. verify.sh itself was shown to fail on
a damaged token, a changed manifest, a replaced anchor and missing commits.

What it proves and what it does not is in the README: existence no later than the stamp, nothing
about who signed or whether the release is genuine. Only digests were sent. No terms of use for
the public endpoint were found; that and the other limits are written down.

Data only: no code, no change to the records, the verifier or the release branches.

---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEQCIEGVAQvbflcRoVWqTgEiYLxyydPT/eLCNt8ZGdh0F9YwAiB0l+DJ729D8qMY8PNvzRQCCPSCRj0EUprD+kbJ1ELqSg==
hash-algorithm = sha256
digest = tes803bYhBOgd/AfIUp13mZJU2O1LITc24L/NOpeet4=

[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
…icates, at the time of the token; the CIC chain is kept

The review of the first commit found two gaps in verify.sh, both real:

1. It checked the validity of the TSA chain at the stamp, not that of the certificates that
   signed the records, which is what the whole evidence is for. It now keeps the four public CIC
   certificates (developer, Developer CA, Source CA, root; the Developer CA is carried by none of
   the records) and shows that the stamp lies inside the validity of EACH, twice: openssl verify
   -attime over both chains, and plain arithmetic on the dates. The developer, Source CA and root
   kept here are checked against the ones each record carries; the Developer CA is checked to be
   the issuer of the developer certificate, not assumed. Margin at the stamp: 57 to 58 days.

2. Two token checks ran at the current time, and the stamp time came from the editable manifest.
   The time is now read from the token, every token check runs at it, and the manifest value is
   only compared with it.

Found while doing it: the root_certificate field of the v0.2.0 and v0.2.1 records is the printed
text of openssl x509 -text with the PEM at its end, the v0.1.5 field is plain PEM. The same
certificate, two spellings, so the comparison is by DER and not by text. The records are not
changed for it.

New negative controls, each refused: a stamp after the end of the CIC chain, before its beginning,
when only SOME of the CIC certificates are still valid, a wrong issuer of the developer
certificate. verify.sh itself was shown to fail on an edited manifest time, a swapped Developer CA,
a swapped developer certificate, a changed manifest fingerprint and a damaged token.

The .tsq, .tsr, the trust anchor and the records are byte-identical to the first commit. Still
open and said so in the README: the second, independent copy.

---
[signing-metadata]
key = cic-my-sign-key
signature = vault:v1:MEQCIGxR75C7r9w8o3dAS5UaeBsuPjX0bxM6jkJcCgwGlXGcAiBW2qpIFYuyEAcdw0jmMzfQA8CopW6nXW4eOR5m/T186w==
hash-algorithm = sha256
digest = A0nuoEVjsw5n6pmyg4D82XEbOm5H6rah6dpZBtVCx9k=

[certificate]
-----BEGIN CERTIFICATE-----
MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw
QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV
BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy
MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb
MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D
AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe
i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU
yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS
WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF
BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0
lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW
-----END CERTIFICATE-----
@sinkog
sinkog merged commit 1cb6391 into devel Nov 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant