The Sound of Silence: Breaking Clatter's Post-Quantum Promises (CVE-2026-24785)
Jan 28, 2026·7 min read·9 visits
Executive Summary (TL;DR)
Clatter < 2.2.0 implemented 'psk0' handshake patterns incorrectly. It used static Pre-Shared Keys to encrypt data before establishing ephemeral randomness. This turns a fancy Post-Quantum encryption tunnel into a static substitution cipher, allowing anyone with the PSK (or a reused stream) to decrypt the entire session.
A critical protocol compliance failure in the Clatter Rust library allows attackers to decrypt Post-Quantum Noise sessions. By violating the Noise Protocol's PSK Validity Rule, specific handshake patterns fail to mix ephemeral entropy before encryption, leading to catastrophic key reuse.
The Hook: When Quantum Meets Basic Arithmetic
We live in an era where everyone is terrified of the 'Quantum Apocalypse.' Developers are scrambling to slap 'Post-Quantum' (PQ) stickers on their cryptographic libraries like it's organic produce. Enter Clatter: a no_std friendly Rust implementation of the Noise Protocol Framework that promised to secure your embedded devices against computers that don't even really exist yet.
But here is the irony: while Clatter was busy looking 20 years into the future at Shor's algorithm, it tripped over a rule written in the present. Specifically, it ignored the PSK Validity Rule (Section 9.3 of the Noise spec).
In the world of cryptography, randomness is god. If you encrypt something without randomness, you aren't encrypting—you're just encoding. CVE-2026-24785 exposes a fundamental flaw in how Clatter handled Pre-Shared Keys (PSKs) in specific handshake patterns. By processing the PSK too early—before the ephemeral keys were mixed in—Clatter inadvertently created encryption keys that were entirely deterministic based on static secrets. If you thought your noise_pqnn_psk0 tunnel was safe from the NSA's quantum computer, I have bad news: it wasn't even safe from a script kiddie with a Python script.
The Flaw: The Sin of Premature Encryption
To understand this bug, you need to understand the 'State Machine' of a Noise handshake. Noise relies on a SymmetricState that acts like a rolling snowball. Every time you send a token (like a public key or a PSK), it gets mixed into the snowball (ChainingKey). When you want to encrypt a message, you carve a chunk off that snowball to make an encryption key.
The Noise Protocol Framework has a Golden Rule for PSKs: Thou shalt not encrypt until thou hast randomness. specifically:
> "Parties must not send any encrypted data (including their static public keys) after processing a psk token unless they have also sent an ephemeral public key (e or ekem)."
Why? Because a PSK is static. If I derive an encryption key solely from ProtocolName + PSK, that key is the same every single time the protocol runs. This destroys forward secrecy and introduces nonce reuse issues.
Clatter implemented patterns like noise_pqnn_psk0. The psk0 modifier means the PSK is the very first thing processed. In Clatter's vulnerable versions, the library would take the PSK, mix it in, and then immediately allow the Initiator to encrypt a payload or a static key.
Critically, at this stage, no ephemeral key exchange (e or ekem) has occurred. The encryption key is derived purely from static data. If an attacker knows the PSK (which is often shared across a fleet of devices) or if the PSK is ever compromised, they can decrypt the first flight of messages retroactively. Worse, because the state is deterministic, the keystream reuse is catastrophic.
The Code: Reading the Rust
The fix landed in commit b65ae6e9b8019bed5407771e21f89ddff17c5a71. The developers had to essentially introduce a 'runtime cop' to police the handshake patterns.
Previously, Clatter blindly followed the pattern string. If the string said psk -> encrypt, Clatter asked "how high?". The patch introduces validate_psk_rule inside handshakepattern.rs. Here is the logic simplified:
// The new Sheriff in town
fn validate_psk_rule(pattern: &HandshakePattern) -> Result<(), PatternError> {
let mut psk_sent = false;
let mut entropy_mixed = false;
for token in pattern.tokens {
match token {
Token::Psk => psk_sent = true,
// Ephemeral keys provide the required entropy
Token::E | Token::Ekem => entropy_mixed = true,
// If we try to encrypt (S, Skem, or payload) ...
Token::S | Token::Skem => {
// ... and we have a PSK but no entropy ...
if psk_sent && !entropy_mixed {
// ... JAIL.
return Err(PatternError::PskValidityViolation);
}
}
_ => {}
}
}
Ok(())
}They also nuked the offending patterns from the library's registry. Patterns like noise_pqkk_psk0 and noise_pqnn_psk0 are gone. They didn't just fix the code; they removed the foot-guns entirely.
Furthermore, in the write_message function, they added a runtime check. Even if you manually constructed a bad pattern, the library now tracks own_randomness_applied. If you try to write a payload while psk_applied is true but own_randomness_applied is false, it panics (or returns an error, because this is Rust, not C++).
The Exploit: Decrypting the 'Secure' Tunnel
Let's walk through a realistic attack scenario. Imagine an IoT fleet using Clatter for secure command and control. They use the pattern noise_pqnn_psk0 because they want 'Post-Quantum' security (pq) and they want authentication via a Pre-Shared Key (psk).
The Setup:
- Initiator (IoT Bulb) starts the handshake.
- Pattern:
psk0->psk,payload. - State:
ChainingKey=Hash(ProtocolName || PSK). - Action: Initiator encrypts its generic "Hello" payload using a key derived from that
ChainingKey.
The Attack:
- Passive Interception: I record the traffic. I see the handshake packet.
- Knowledge: I managed to dump the firmware of one lightbulb and extracted the fleet-wide PSK (a common sin in IoT).
- Decryption: Since I have the PSK and the Protocol Name is public, I can calculate the exact same
ChainingKeythe lightbulb generated. - Result: I derive the encryption key. I decrypt the payload.
Wait, it gets worse. Because there is no random nonce (ephemeral key) mixed in yet, if the lightbulb reboots and sends the exact same "Hello" message, the ciphertext will be identical. This allows for trivial replay attacks and traffic analysis even without the PSK, purely based on the deterministic nature of the output. The 'Post-Quantum' protection is irrelevant because the math failed at step zero.
The Impact: Why Post-Quantum Didn't Save You
The severity of this is High (CVSS 8.0) for a reason. While it doesn't allow unauthenticated RCE directly, it completely negates the confidentiality and integrity guarantees of the encrypted channel during the initial phase.
For psk0 patterns, the Forward Secrecy property is non-existent until the second flight of messages. If the PSK is compromised, all past and future sessions that relied on that PSK for the initial flight are readable.
This is particularly damning for '0-RTT' style optimisations where data is sent in the first message. If your application sends sensitive credentials, API keys, or critical commands in that first packet, they are effectively sent in plaintext to anyone with the PSK.
In the context of the noise_pq* patterns, the irony is palpable: users selected these patterns specifically for higher security against future threats, only to be vulnerable to present-day logic errors.
Mitigation: Silence the Noise
If you are using Clatter, you have two options:
-
The Good Way: Upgrade to version 2.2.0. The maintainers have done the heavy lifting. They've removed the broken patterns and added runtime checks that will scream at you if you try to define a custom broken pattern.
-
The 'I Can't Upgrade' Way: If you are stuck on an older version (maybe burned into ROM), you MUST change your configuration. Do not use
psk0patterns. Switch topsk2(where the PSK is added at the end of the handshake) or ensuring that your custom pattern sends anetoken before thepsktoken.
Developer Lesson: Implementing a spec like Noise is not just about getting the math right; it's about adhering to the state machine's safety properties. Always validate that your state transitions don't leave you naked before you step outside.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:UAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Clatter Clatter Maintainers | < 2.2.0 | 2.2.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-327 |
| Attack Vector | Network |
| CVSS Score | 8.0 (High) |
| Impact | Confidentiality & Integrity Loss |
| EPSS Score | 0.00015 |
| Vulnerability Type | Cryptographic Logic Error |
MITRE ATT&CK Mapping
Use of a Broken or Risky Cryptographic Algorithm
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.