diff --git a/diagnose.go b/diagnose.go index ea6cba5..501df38 100644 --- a/diagnose.go +++ b/diagnose.go @@ -4,6 +4,7 @@ import ( "cmp" "context" "crypto/tls" + "crypto/x509" "errors" "fmt" "log/slog" @@ -28,6 +29,10 @@ type DiagnoseResult struct { Region string // Round-trip latency of the SrvInfo call. Latency time.Duration + // PeerCertificates is the certificate chain presented by the server + // during the TLS handshake, with the leaf certificate first. Populated + // whenever the TLS handshake succeeded, even if a later probe step failed. + PeerCertificates []*x509.Certificate } // diagnoseError is returned by [Diagnoser.Diagnose] when a probe step fails. @@ -165,6 +170,7 @@ func (a *agent) probeAddr(ctx context.Context, logger *slog.Logger, dialer Diale if err := tlsConn.HandshakeContext(ctx); err != nil { return result, &diagnoseError{Step: "tls", Err: err} } + result.PeerCertificates = tlsConn.ConnectionState().PeerCertificates // Muxado + SrvInfo muxSess := muxado.Client(tlsConn, nil) diff --git a/diagnose_test.go b/diagnose_test.go index fb677a7..3f8d97c 100644 --- a/diagnose_test.go +++ b/diagnose_test.go @@ -125,6 +125,7 @@ func TestDiagnoseMuxadoSuccess(t *testing.T) { assert.Equal(t, l.Addr().String(), result.Addr) assert.Equal(t, testRegion, result.Region) assert.Greater(t, result.Latency, time.Duration(0)) + assert.NotEmpty(t, result.PeerCertificates, "peer certificate chain should be captured after TLS handshake") } // TestDiagnoseOnline connects to a live tunnel server and verifies the full