Skip to content

Add tls.allowUnperformableCiphers, because the restriction is itself a fingerprint - #44

Merged
latentharbor merged 1 commit into
mainfrom
allow-unperformable-ciphers
Aug 4, 2026
Merged

Add tls.allowUnperformableCiphers, because the restriction is itself a fingerprint#44
latentharbor merged 1 commit into
mainfrom
allow-unperformable-ciphers

Conversation

@latentharbor

Copy link
Copy Markdown
Owner

1.9.0 started refusing cipher lists containing suites this package cannot perform. The reasoning was
sound and the default is right, but shipping it with no escape hatch removed a capability a
fingerprint package specifically needs
— and did so without saying what it cost.

What it cost, from the captures in this repo

suites offered performable here
curl 8.21.0 30 7
Chromium 15 7

A hello restricted to what can be honoured carries a cipher list less than half the length of any
real client's
— and list length and contents are precisely what a JA3 hash reads.

Refusing to offer an unperformable suite avoids a rare handshake failure by guaranteeing a
permanent fingerprint mismatch.
For this package that is very likely the worse trade, and not one
to make on a caller's behalf in a minor release.

The escape hatch, and its quantified risk

tls.allowUnperformableCiphers offers the accurate list. The first unperformable suite sits at
index 5 of curl's list and 7 of Chromium's, behind the TLS 1.3 suites — so a server with 1.3
available never reaches one.

If a server does select one, the handshake fails, and the negotiation error no longer calls that
"a bug in the offer list"
. With this option set it is not a bug; it is the documented consequence
of a deliberate choice, and the message names the option so the next person to hit it does not go
hunting for a defect that is not there.

The default is unchanged.

What the gap is actually made of

Most of it is not a missing feature:

  • 16 of curl's 23 are CBC — MAC-then-encrypt, unimplementable without a Lucky13 padding oracle
    in JavaScript
  • 2 more are RSA key exchange, no forward secrecy

Both are refusals this package intends to keep. Three are implementable — the TLS 1.2
ChaCha20-Poly1305 suites, whose AEAD is already injectable for 1.3 — and the README now names them
as not-yet-implemented rather than leaving them to look like part of the same refusal.

Version → 1.10.0. 1257 offline tests pass.

🤖 Generated with Claude Code

…a fingerprint

1.9.0 started refusing cipher lists containing suites this package cannot perform. The reasoning was
sound and the default is right, but shipping it with no escape hatch removed a capability that a
fingerprint package specifically needs, and it did so without saying what it cost.

What it costs, from the captures in this repo:

    curl 8.21.0   offers 30 suites, 7 performable here
    Chromium      offers 15 suites, 7 performable here

A hello restricted to what can be honoured therefore carries a cipher list less than half the length
of any real client's — and list length and contents are precisely what a JA3 hash reads. Refusing to
offer an unperformable suite avoids a rare handshake failure by guaranteeing a permanent fingerprint
mismatch. For this package that is very likely the worse trade, and it is not one to make on a
caller's behalf in a minor release.

`tls.allowUnperformableCiphers` offers the accurate list. The risk is narrow and quantified: the
first unperformable suite sits at index 5 of curl's list and 7 of Chromium's, behind the TLS 1.3
suites, so a server with 1.3 available never reaches one. If a server does select one, the handshake
fails — and the negotiation error no longer calls that "a bug in the offer list", because with this
option set it is not a bug, it is the documented consequence of a deliberate choice. The message now
names the option so the next person to hit it does not go looking for a defect that is not there.

The default is unchanged: without the flag, an unperformable suite is still refused at configuration
time rather than discovered on the wire.

The README now states what the gap is made of, because most of it is not a missing feature. Sixteen
of curl's twenty-three are CBC — MAC-then-encrypt, unimplementable without a Lucky13 padding oracle
in JavaScript — and two more are RSA key exchange with no forward secrecy. Those are refusals this
package intends to keep. Three are implementable: the TLS 1.2 ChaCha20-Poly1305 suites, whose AEAD
is already injectable for 1.3. They are named as not-yet-implemented rather than left looking like
part of the same refusal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@latentharbor
latentharbor merged commit 3ba7e05 into main Aug 4, 2026
3 checks passed
@latentharbor
latentharbor deleted the allow-unperformable-ciphers branch August 4, 2026 08:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant