Skip to content

harden: validate PE output and timestamp tokens - #4

Merged
Le-Syl21 merged 5 commits into
Le-Syl21:mainfrom
Loggableim:agent/harden-pe-output-and-timestamp-token
Jul 22, 2026
Merged

harden: validate PE output and timestamp tokens#4
Le-Syl21 merged 5 commits into
Le-Syl21:mainfrom
Loggableim:agent/harden-pe-output-and-timestamp-token

Conversation

@Loggableim

Copy link
Copy Markdown
Contributor

What changed

  • Reject PE certificate tables whose range is outside the input buffer.
  • Validate the outer CMS SignedData structure of RFC3161 timestamp tokens before embedding.
  • Publish signed outputs through synced same-directory temp files, preserve destination permissions, atomically replace outputs, and never clobber an existing .orig backup.

Explicitly excluded

No timestamp transport-policy change: no --allow-insecure-timestamp, HTTP rejection, Content-Type enforcement, CI, or documentation changes.

@Le-Syl21

Copy link
Copy Markdown
Owner

This is good work — all three items I asked for, and the excluded list matches exactly what I said I didn't want to take. Thanks for keeping the scope tight.

The atomic write path is solid. Creating the temp with create_new gives you O_EXCL, so the predictable name isn't a problem; keeping it in the destination directory is what makes the rename atomic; and publishing the backup before the replace is the right order — if .orig is already there we fail without having touched the original. Using hard_link to get "never clobber" is neat, it fails on an existing destination without a check-then-act race.

Two things.

The bounds check is stricter than it needs to be when the table is empty. It runs before the branch that special-cases a zero-length table:

if l.cert_table_off > pe.len() || cert_end > pe.len() { bail!(...) }
let (cert_start, cert_end) = if l.cert_table_size == 0 {
    (pe.len(), pe.len())        // offset is not used here
} else { ... };

With cert_table_size == 0, cert_end == cert_table_off, so the guard rejects on an offset the code then ignores. A PE that was signed and later stripped can carry a stale offset with a zero size — that hashed fine before and would now be refused. Moving the check into the else branch keeps the protection where it matters. Your test uses size = 0x20, so it doesn't cover this case; a zero-size one with a garbage offset would be worth adding.

The Windows atomic_replace is untested. The flags and the MoveFileExW signature look right to me, but it's hand-rolled FFI and the test only exercises the Unix path, so nothing verifies it. Not a blocker — just flagging that it ships unexercised.

Minor, take it or leave it: there's no directory fsync after the rename. That doesn't affect the failure you're fixing (a process dying mid-write), only durability across a power loss.

Fix the first point and I'll merge this.

@Le-Syl21 Le-Syl21 mentioned this pull request Jul 22, 2026
@Le-Syl21
Le-Syl21 marked this pull request as ready for review July 22, 2026 12:41
@Le-Syl21
Le-Syl21 merged commit a2734c4 into Le-Syl21:main Jul 22, 2026
9 checks passed
Le-Syl21 added a commit that referenced this pull request Jul 22, 2026
CI : ajout de --workspace à clippy et cargo test. Sans lui, le CI ne
lançait qu'UN test (celui du binaire) et n'a jamais exécuté les 16 tests
de ssign-core — y compris ceux ajoutés par #3 et #4. Clippy ne lintait
pas non plus ssign-core. --all-targets sur clippy pour couvrir les tests.

Secrets : la graine TOTP et le code sont désormais portés par
Zeroizing<String> et effacés à leur libération ; les copies laissées par
clap sont effacées dès que le secret est repris. Limite documentée dans
le code : cela ne couvre pas ce qui précède main (argv et environ restent
lisibles dans /proc), d'où le conseil de préférer les variables
d'environnement.

Contenu de la release, via #3 et #4 :
- signature cloud sélectionnée sur le digest exact demandé, comparaison
  insensible à la casse (le contrat de l'API porte sur du hex, pas sur
  une casse) ;
- table de certificats PE bornée, sans rejeter un offset résiduel quand
  la table est vide ;
- validation structurelle CMS du token d'horodatage avant intégration ;
- écriture atomique des sorties : fichier temporaire O_EXCL dans le même
  répertoire, sync, permissions préservées, remplacement atomique, et
  refus d'écraser un .orig existant.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138mtPxwfXPetBypyjp6KwU
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.

2 participants