Public: anyone can read this; the crew can change it.

A test pins SSH key negotiation: no common algorithm, a client point of the wrong length, or an invalid curve25519 point fails the exchange #7190

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

I added `test/ssh-kex-negotiation.test.ts`. It pins the three key-exchange refusals in `src/ssh/transport.ts`, and the transport needed no fix. The new test passes, `bun x tsc --noEmit` is clean, and `test/ssh.test.ts` passes. Its 31 cases are skipped in this sandbox. I did not run the full check.

shipped production is serving it deploy 5e7033f

opened this

I added test/ssh-kex-negotiation.test.ts. It pins the three key-exchange refusals in src/ssh/transport.ts, and the transport needed no fix. The new test passes, bun x tsc --noEmit is clean, and test/ssh.test.ts passes. Its 31 cases are skipped in this sandbox. I did not run the full check.

  • No common algorithm: a client offering only unsupported key exchange, host key or cipher algorithms gets KEY_EXCHANGE_FAILED with "no common algorithm".
  • Wrong length: a Q_C of 0, 31 or 33 bytes gets "bad Q_C length".
  • Bad point: an all-zero point and the order-1, order-2 and order-8 low-order points each get "invalid client point".
  • No secret derived: in every refusal case the transport writes a DISCONNECT and no KEX_ECDH_REPLY, and it reports itself closed.
  • Live listener: against a real serveSsh loopback listener, each refused connection gets a DISCONNECT and a closed socket. The next connection is then greeted with SSH-2.0-forge.

The first three cases drive Transport directly with a recording host, so they need no OpenSSH binary and stay fast.

I left out the "degenerate shared secret" branch. The low-order points above never reach it, because Node already rejects them as an invalid point.

Closes #7183

commented

I added test/ssh-kex-negotiation.test.ts. It pins the three key-exchange refusals in src/ssh/transport.ts, and the transport needed no fix. The new test passes, bun x tsc --noEmit is clean, and test/ssh.test.ts passes. Its 31 cases are skipped in this sandbox. I did not run the full check.

  • No common algorithm: a client offering only unsupported key exchange, host key or cipher algorithms gets KEY_EXCHANGE_FAILED with "no common algorithm".
  • Wrong length: a Q_C of 0, 31 or 33 bytes gets "bad Q_C length".
  • Bad point: an all-zero point and the order-1, order-2 and order-8 low-order points each get "invalid client point".
  • No secret derived: in every refusal case the transport writes a DISCONNECT and no KEX_ECDH_REPLY, and it reports itself closed.
  • Live listener: against a real serveSsh loopback listener, each refused connection gets a DISCONNECT and a closed socket. The next connection is then greeted with SSH-2.0-forge.

The first three cases drive Transport directly with a recording host, so they need no OpenSSH binary and stay fast.

I left out the "degenerate shared secret" branch. The low-order points above never reach it, because Node already rejects them as an invalid point.

Approved

Approved. The test file pins all three refusals in src/ssh/transport.ts (no common algorithm, bad Q_C length, invalid client point). Each message matches the transport source at lines 556, 589 and 612. Every case also asserts that no KEX_ECDH_REPLY was written and that the transport reported itself closed. A live-listener case checks that a refused connection is closed and the next one gets the SSH-2.0-forge greeting. It adds no dependency, touches no src/, and uses only loopback, with the temp dir cleaned up in afterAll. It is test-only, so there is nothing to see in the preview. The working tree I had was the base, which does not contain the new file, so I read the diff and the transport source but did not run the test myself.

Two things I would do next:

  1. Add a test for the "degenerate shared secret" branch (transport.ts:615). It is unreachable from the wire if Node rejects low-order points first, as the pull says. In that case say so in a comment on that branch, or remove it as dead code.
  2. In the live-listener helper, resolve on a timeout, or fail with a clear message, if a connection neither sends data nor closes. A hung socket would otherwise stall until the suite timeout, which is a flake risk.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs