Skip to content

Expose the RFC 9266 tls-exporter channel binding via TlsInfo#3048

Open
mike-marcacci wants to merge 2 commits into
seanmonstar:masterfrom
eidola-ai:channel-binding
Open

Expose the RFC 9266 tls-exporter channel binding via TlsInfo#3048
mike-marcacci wants to merge 2 commits into
seanmonstar:masterfrom
eidola-ai:channel-binding

Conversation

@mike-marcacci

@mike-marcacci mike-marcacci commented Jun 5, 2026

Copy link
Copy Markdown

Adds TlsInfo::tls_exporter_channel_binding(), returning the RFC 9266 tls-exporter channel binding for a connection (the TLS exporter with label EXPORTER-Channel-Binding, empty context, 32 bytes).

The channel binding lets applications cryptographically tie higher-layer authentication to the underlying TLS session to detect a man-in-the-middle that terminates and re-originates TLS, etc. This change simply exposes the value that rustls already computes.

A few caveats:

  • Populated for the rustls backend; None for native-tls (no keying-material export is exposed).
  • Gated to TLS 1.3. The exporter is only sound for channel binding on TLS 1.3 or TLS 1.2 with Extended Master Secret (RFC 7627), and the state of EMS cannot be known here. This returns None on anything below 1.3 rather than risk handing back a potentially unsound Some(_).

Adds `TlsInfo::tls_exporter()`, returning the RFC 9266 tls-exporter channel
binding for a connection (the TLS exporter with label EXPORTER-Channel-Binding,
empty context, 32 bytes).

The channel binding lets applications cryptographically tie higher-layer
authentication to the underlying TLS session to detect a man-in-the-middle
that terminates and re-originates TLS, etc. This change simply exposes the
value that `rustls` already computes.

A few caveats:

- Populated for the rustls backend; None for native-tls (no keying-material
  export is exposed).
- Gated to TLS 1.3. The exporter is only sound for channel binding on TLS 1.3
  or TLS 1.2 with Extended Master Secret (RFC 7627), and the state of EMS
  cannot be known here. This returns None on anything below 1.3 rather
  than risk handing back a potentially unsound `Some(_)`.
@mike-marcacci mike-marcacci marked this pull request as ready for review June 5, 2026 05:46
Comment thread src/tls.rs Outdated
The previous name was imprecise and implied the general keying-
material exporter. However, it's not possible to expose this
without a more substantial refactor, since TlsInfo is detached
from the live connection. This change clarifies the limited
behavior actually implemented.
@mike-marcacci mike-marcacci marked this pull request as draft June 5, 2026 17:44
@mike-marcacci

mike-marcacci commented Jun 5, 2026

Copy link
Copy Markdown
Author

I want to elaborate on the TLS 1.3+ decision. I'm not particularly experienced in the underlying details of the various TLS versions, but here's my general understanding:

  • TLS 1.3 has tls-exporter built in; any Some(value) provides a sound value, while None() indicated an anomaly
  • TLS 1.2 without EMS available does not support tls-exporter channel blinding; None() is expected and Some(_) should be impossible
  • TLS 1.2 with EMS available is the complicated scenario:
    • EMS gets negotiated per-connection, but rustls provides no mechanism for us to determine the negotiation's resolution for the current connection
    • If EMS was used, the value returned is sound
    • If EMS was not used, it's my understanding that the value is not sound

The approach I took here avoids ever returning an unsound value. However, I want to highlight that even when a sound value was used in TLS 1.2 with EMS, None() will be returned.

I believe this is the right approach.

@mike-marcacci

Copy link
Copy Markdown
Author

I submitted an upstream PR to rustls to expose the EMS state, which would allow safe channel binding under TLS 1.2 with EMS: the conn.protocol_version() check can be augmented with a check for conn.extended_master_secret() once that lands.

mike-marcacci added a commit to eidola-ai/eidola that referenced this pull request Jun 5, 2026
This is an initial draft implementing [a proposed improvement to
tinfoil's attestation](tinfoilsh/cvmimage#160).

This depends on an [upstream improvement to reqwest](seanmonstar/reqwest#3048)
which could be further improved by [an improvement to rustls](rustls/rustls#3083).
mike-marcacci added a commit to eidola-ai/eidola that referenced this pull request Jun 6, 2026
This is an initial draft implementing [a proposed improvement to
tinfoil's attestation](tinfoilsh/cvmimage#160).

This depends on an [upstream improvement to reqwest](seanmonstar/reqwest#3048)
which could be further improved by [an improvement to rustls](rustls/rustls#3083).
mike-marcacci added a commit to eidola-ai/rustls that referenced this pull request Jun 10, 2026
Adds `ConnectionOutputs::extended_master_secret() -> Option<bool>`,
reporting whether the Extended Master Secret extension (RFC 7627)
was negotiated.

A TLS exporter is only a sound channel binding (RFC 9266 tls-exporter)
on TLS 1.3, or on TLS 1.2 with Extended Master Secret; without EMS the
TLS 1.2 exporter is vulnerable to the triple-handshake attack.

Currently, rustls exposes the exporter and protocol_version(), but not
EMS status, so a caller doing channel binding currently has no way to
tell whether a TLS 1.2 exporter is safe to use.

See [this concrete use case in reqwest](seanmonstar/reqwest#3048).
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.

2 participants