Record why the session's handshake hash is not cleared on drop

`SymmetricState` gained a `Zeroize` derive with the key-material work, so
the handshake hash it holds is erased when the handshake state drops. The
value does not stay there: `into_session` copies it into `NoiseSession`,
which outlives the handshake by the life of the link and clears nothing.

The copy is fine where it is. The hash is a transcript hash, no key is
derived from it in this crate, and `handshake_hash()` publishes it to any
caller, so treating it as secret in one type while handing it out from
another would be incoherent. What was wrong was that the declaration said
none of this, leaving the reader to guess whether the omission was an
oversight. Say it at the field, and name the change that would make the
answer different.

(cherry picked from commit 88ca5f09c37fbfc7674c8385c611f9094e7d3a92)
This commit is contained in:
Johnathan Corgan
2026-08-25 20:47:00 +01:00
parent a3ea22454f
commit 4e1541f489
+11 -1
View File
@@ -14,7 +14,17 @@ pub struct NoiseSession {
send_cipher: CipherState,
/// Cipher for receiving.
recv_cipher: CipherState,
/// Handshake hash.
/// Handshake hash, copied out of the handshake's `SymmetricState`.
///
/// Deliberately not cleared on drop, unlike the copy it came from. That
/// copy is cleared because it sits beside the chaining key in a type whose
/// contract is that it erases what it holds; this one is a transcript hash
/// that nothing derives a key from, and `handshake_hash()` hands it out to
/// any caller. A type cannot both publish a value and treat it as secret.
///
/// Revisit if the hash ever becomes key-adjacent: feeding it to the AEAD as
/// associated data, or building channel binding or an exporter on it, would
/// make this session's copy the long-lived home of something worth erasing.
handshake_hash: [u8; 32],
/// Remote peer's static public key.
remote_static: PublicKey,