From cb29af66156f90ba1f61e6de3e5527ed6757c49c Mon Sep 17 00:00:00 2001 From: usernametooshort Date: Sat, 7 Mar 2026 08:29:14 +0100 Subject: [PATCH] feat(tls): add key_exchange field to JSON output Read ConnectionState.CurveID after TLS handshake and expose it as key_exchange in JSON output. Supports TLS 1.3 post-quantum key exchange detection (X25519MLKEM768 etc). Closes #935 --- pkg/tlsx/clients/clients.go | 2 ++ pkg/tlsx/tls/tls.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/pkg/tlsx/clients/clients.go b/pkg/tlsx/clients/clients.go index 2787b5c0..16c0c9fb 100644 --- a/pkg/tlsx/clients/clients.go +++ b/pkg/tlsx/clients/clients.go @@ -206,6 +206,8 @@ type Response struct { Version string `json:"tls_version,omitempty"` // Cipher is the cipher for the tls request Cipher string `json:"cipher,omitempty"` + // KeyExchange is the negotiated key exchange/curve + KeyExchange string `json:"key_exchange,omitempty"` // CertificateResponse is the leaf certificate embedded in json *CertificateResponse `json:",inline"` // TLSConnection is the client used for TLS connection diff --git a/pkg/tlsx/tls/tls.go b/pkg/tlsx/tls/tls.go index c07a5ed2..74906fa2 100644 --- a/pkg/tlsx/tls/tls.go +++ b/pkg/tlsx/tls/tls.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "crypto/x509" "errors" + "fmt" "net" "os" "time" @@ -142,6 +143,7 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C } tlsVersion := versionToTLSVersionString[connectionState.Version] tlsCipher := tls.CipherSuiteName(connectionState.CipherSuite) + tlsKeyExchange := curveIDToString(connectionState.CurveID) leafCertificate := connectionState.PeerCertificates[0] certificateChain := connectionState.PeerCertificates[1:] @@ -160,6 +162,7 @@ func (c *Client) ConnectWithOptions(hostname, ip, port string, options clients.C Port: port, Version: tlsVersion, Cipher: tlsCipher, + KeyExchange: tlsKeyExchange, TLSConnection: "ctls", CertificateResponse: clients.Convertx509toResponse(c.options, hostname, leafCertificate, c.options.Cert), ServerName: config.ServerName, @@ -298,3 +301,29 @@ func (c *Client) getConfig(hostname, ip, port string, options clients.ConnectOpt } return config, nil } + +// curveIDToString converts CurveID to a human-readable string +func curveIDToString(curveID tls.CurveID) string { + // Standard curve IDs from crypto/tls + switch curveID { + case 23: + return "CurveP256" + case 24: + return "CurveP384" + case 25: + return "CurveP521" + case 29: + return "X25519" + case 4588: + return "X25519MLKEM768" + case 0: + return "" // No curve negotiated + default: + // Try using the built-in String() method if available + // Otherwise return the numeric form + if curveID > 0 { + return fmt.Sprintf("CurveID(%d)", curveID) + } + return "" + } +}