fix(kerykeion): accept only the PSK shapes the protocol defines - #436
Merged
Conversation
added 2 commits
August 21, 2026 00:58
resolve_psk matched empty, a single index byte of 1 to 10, and then everything else fell into `_ => Some(psk.to_vec())`. Its own doc said 16 or 32 bytes; the code handed AES whatever it was given. A single byte of 0 or 11, a seven-byte blob, a 31-byte near-miss -- each became a "key" of its own length. The effect was not a crash but a disappearance. AES rejected the bad key, the caller skipped that channel, and a misconfigured channel became indistinguishable from one that carries no encryption. Nothing said otherwise. resolve_psk now returns three states rather than an Option, so the two are distinct. Receiving logs the undefined case and moves on; sending refuses it, because returning the plaintext there would transmit in the clear on a channel the operator believes is encrypted. ChannelPsk carried the same rule as a comment and accepted any length. It is now checked at deserialization, so a mistyped key fails where it was written instead of travelling to AES. The single-byte index form is deliberately not accepted there: that shorthand arrives over the air, and an operator writing a config means a key. Both accepted key lengths are named once and shared, rather than written as literals in two files free to disagree. Refs #229
clippy::useless_vec: a repeat expression with a const length is an array; only the heterogeneous-length loop above genuinely needs Vec.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two more of #229's clauses: PSK index handling, and
ChannelPsk.pskaccepting any length.What the catch-all did
The doc directly above said "16 or 32 bytes → used as-is." The code accepted every length. A single
byte of
0or11, a seven-byte blob, a 31-byte near-miss — each became an AES key of its own length.The effect was a disappearance, not a crash. AES rejected the bad key, the caller
continued, anda misconfigured channel became indistinguishable from one carrying no encryption. Nothing said
otherwise, and the operator saw a quiet channel either way.
Three states, not two
resolve_pskreturnsUnencrypted/Key/Undefined { len }, so the two cases are distinguishableat last. The direction matters at the call sites:
operator believes is encrypted, which is the worse failure of the two.
Undefinedcarries only the length. The bytes are key material and do not belong in a log line.The config boundary
ChannelPsk.pskcarried the same rule as a comment and accepted any length. It is now checked atdeserialization, so a mistyped key fails in the file where it was written rather than travelling to AES
to be quietly dropped.
The single-byte index shorthand is deliberately not accepted there: that form arrives over the air,
and an operator writing a config means a key. The two contracts differ on purpose, and now each
enforces its own.
Both key lengths are named once in
cryptoand shared, rather than being literals in two files free todisagree.
Tests
Seven undefined shapes are rejected with their own length reported — index 0, index 11, index 255, and
lengths 7, 15, 31, 33. Every defined shape still resolves: all ten indices, plus both key lengths. The
config boundary refuses seven wrong lengths and accepts the three documented ones.
Each rejection set has that anti-vacuity partner because a resolver that refused everything would
satisfy all the rejections while silently disabling encryption everywhere.
Refs #229