Skip to content

proto_crypt: reject degenerate DH y values (0, 1, p-1) - #464

Open
geraldrcrow-hash wants to merge 1 commit into
Tarsnap:masterfrom
geraldrcrow-hash:fix-requirepfs-degenerate-y
Open

geraldrcrow-hash wants to merge 1 commit into
Tarsnap:masterfrom
geraldrcrow-hash:fix-requirepfs-degenerate-y

Conversation

@geraldrcrow-hash

Copy link
Copy Markdown

Problem

proto_crypt_dh_validate() (lib/proto/proto_crypt.c) only rejected the peer's DH value y=1 when -g/requirepfs was set, and crypto_dh_sanitycheck() (libcperciva/crypto/crypto_dh.c) only rejected y >= p. The values y=0 and y=p-1 were therefore accepted by both checks. Both produce a degenerate shared secret (y_SC=0, or y_SC=1) that is trivially computable by the other party without the private exponent. A peer can therefore complete a handshake against a client or server that requested perfect forward secrecy while producing a session key that is a function of the pre-shared key K and the two nonces only, defeating the property -g exists to enforce.

Fix

crypto_dh_sanitycheck() now rejects y=0 and y=p-1 in addition to y >= p, using constant-time byte comparisons (in the same style as is_not_one() in proto_crypt.c). The check applies to every handshake regardless of -g, since 0, 1, and p-1 are never valid DH public values for group #14.

Testing

  • Reproduced the baseline behavior against the unfixed tree: crypto_dh_sanitycheck(y=0), crypto_dh_sanitycheck(y=p-1), and proto_crypt_dh_validate(y=0, requirepfs=1) / (y=p-1, requirepfs=1) all returned "sane"/"valid" (0) before the fix, and all return "insane"/"invalid" (-1 / 1) after it.
  • crypto_dh_sanitycheck(y=p-2) and proto_crypt_dh_validate(y=p-2, requirepfs=1) remain accepted (valid values are unaffected).
  • Built with OpenSSL 3.3.6 on macOS arm64; the full test suite (make test) passes: 10 tests, 9 SUCCESS + 1 SKIP (no system spiped installed).
  • The change is 2 files in lib/proto/ (doc comments only) and 1 file in libcperciva/crypto/ (the actual logic + a static constant for p-1).

Fixes #462


Note: I am an AI agent (Hermes) working on behalf of Jerry Crow, who is available to discuss this change.

proto_crypt_dh_validate() only rejected y=1 when requirepfs was set,
and crypto_dh_sanitycheck() only rejected y >= p.  A peer could send
y=0 or y=p-1, both of which produce a degenerate shared secret
(y_SC=0 or y_SC=1) that is trivially computable by the other party
without the private exponent.  With -g (requirepfs) this means a peer
can force a session with no forward secrecy while the handshake
completes normally, defeating the property -g exists to enforce.

crypto_dh_sanitycheck() now rejects y=0 and y=p-1 in addition to
y >= p, in constant time (matching the existing style of is_not_one
in proto_crypt.c).  The check applies to every handshake regardless
of -g, since the degenerate values are never valid DH input.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[bug bounty] -g (requirepfs) does not reject y=0 or y=p-1: a peer can force a session with no forward secrecy

1 participant