Skip to content

seal: the archival timestamp is never bound to the seal it reports on #330

Description

@LKSNDRTMLKV

cades::archival_freshness reports whether a B-LTA seal's long-term protection is still live, and it is served on the seal route, in the evidence dossier and in odal seal status. It reads the newest archive-time-stamp-v3 (or -v2) it can find in the unsigned attributes, checks that the token's own signature holds and that the digest inside its signed attributes matches its content, and reports the authority certificate's notAfter.

It never checks that the token is a timestamp of this seal.

archival_token compares nothing against the enclosing signature. A well-formed archive timestamp taken from a different seal — internally consistent, signed by a real authority, protecting something else entirely — is accepted, and this passport is reported as archived until that other seal's authority certificate expires.

This is the same defect that was found and fixed for the signature timestamp during #322: attested_sealing_time now compares the token's messageImprint against the SHA-256 of this SignerInfo's signature value (EN 319 122-1 clause 5.3), and refuses a token that does not match. The archive timestamp did not get the same treatment, and unsigned attributes are exactly where such a swap is free to make.

Why it was not simply fixed alongside

The imprint an archive-time-stamp-v3 covers is not a single field. EN 319 122-1 clause 5.5.3 builds it from a concatenation over the SignedData — the content, the certificates, the CRLs, the signer's fields and the signed attributes — and admits an ats-hash-index-v3 attribute that names exactly which elements were included, so a later-added certificate does not invalidate the stamp. Computing the expected value means implementing both.

Our own local backend does not produce that either: its archive-time-stamp-v3 imprint is taken over the signer's encoded form, and it emits no ats-hash-index-v3. That is documented in local::sealer as a deliberate departure — so implementing the check would make this workspace's own B-LTA seals report as unbound, correctly, and the local sealer has to move with it.

The meaning to fix in the meantime

Until the imprint is computed, the field means "an archival token is present, it is internally sound, and its authority's certificate expires on X" — not "this seal is protected until X". The route's documentation, the dossier's and the CLI's all read as the second.

Two ways out, and they are not exclusive:

  1. Implement clause 5.5.3 (and ats-hash-index-v3) in the reader, and make the local backend produce a conformant stamp so the workspace's own seals still pass.
  2. Narrow what is reported — say the token was not bound to this seal, the way binding distinguishes what the bytes prove from what the records claim, so the honest reading is on the wire rather than in a doc comment.

Severity

Not currently reachable by accident: the only way a foreign token lands in a seal is if someone edits the stored envelope, which is the corruption-at-rest threat the seal audit exists for. But it is precisely the direction that audit cannot catch — the seal still verifies, still binds to the passport, and the lie is in a field an authority reads to decide whether the evidence survives the signing certificate.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions