#7665·nuclei

http: expose negotiated TLS key exchange in DSL metadata

Author: AAH20Created Aug 17, 2026Updated Aug 17, 2026

Problem

Nuclei's HTTP protocol exposes useful TLS handshake and certificate metadata to DSL matchers, extractors, workflows, and result events. This includes tls_version, cipher, sni, certificate identity, validity, and fingerprints.

The completed tls.ConnectionState also contains CurveID, which Go documents as the key exchange mechanism used for the connection, but enrichEventWithTLSMetadata currently discards it. As a result, an HTTPS template cannot distinguish an observed X25519 handshake from X25519MLKEM768, even though both can negotiate the same TLS 1.3 cipher suite.

TLSX already exposes this handshake fact as key_exchange, and Nuclei's SSL protocol maps non-zero fields from the TLSX response into its event data. The HTTP and SSL protocol surfaces are therefore inconsistent for the same connection property.

This is useful for cryptographic migration inventory, interoperability debugging, and TLS policy validation. It should be treated as observed handshake evidence, not as proof of every group a server supports.

Proposed solution

Add the negotiated group to HTTP TLS metadata using the existing TLSX field name:

go
if state.CurveID != 0 {
    data["key_exchange"] = state.CurveID.String()
}

This would make key_exchange available to the existing HTTP DSL and output path without another request or TLS handshake.

Example extraction:

yaml
extractors:
  - type: dsl
    dsl:
      - key_exchange

Example matching of the group selected by this probe:

yaml
matchers:
  - type: dsl
    dsl:
      - 'tls_version == "tls13"'
      - 'key_exchange == "X25519MLKEM768"'
    condition: and

Semantics

  • key_exchange is the mechanism selected by the completed handshake, not the client's offered list or the server's full supported-group set;
  • omit the field when CurveID is zero or TLS state is unavailable;
  • preserve Go's CurveID.String() representation so the HTTP field matches TLSX behavior;
  • do not infer quantum_safe, compliance, vulnerability severity, or server-wide PQC support from this field alone;
  • do not change TLS configuration, connection reuse, redirects, or request count.

Implementation and verification

The focused change would:

  • update pkg/protocols/http/tls_metadata.go;
  • extend TestEnrichEventWithTLSMetadata with a classical group;
  • extend TestResponseToDSLMapIncludesTLSMetadata to confirm DSL propagation;
  • add zero/unavailable CurveID coverage;
  • cover X25519MLKEM768 where supported by the repository's Go toolchain;
  • document key_exchange as an SSL/HTTP response field where protocol field definitions are maintained;
  • add an integration fixture only if maintainers prefer it over the existing unit-level TLS state tests.

A detection or inventory template should be reviewed separately. In particular, a template must not label an observed classical selection as a vulnerability or conclude that the endpoint lacks hybrid support without controlled offer/enumeration semantics.

Related work:

  • TLSX already emits key_exchange from ConnectionState.CurveID in its ctls backend.
  • open-telemetry/semantic-conventions#4015 proposes vendor-neutral attributes for negotiated TLS groups.

If this field parity is accepted, I can implement the code, tests, and documentation as a focused PR based on dev.

Disclosure: I use OpenAI Codex as an engineering assistant and will review, understand, test, and take ownership of any submitted contribution. I also maintain related open-source PQC migration work and work through A2Z SOC, which provides scoped attack-surface cryptographic inventory and migration-readiness assessments. Any Nuclei code, tests, and documentation would remain vendor-neutral and contain no service promotion.