From 294ce37ec94d8a9b0196742a340a7c1b189ec3f7 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Wed, 17 Dec 2025 17:57:35 +0100 Subject: [PATCH 1/2] fix: early return in GetRemoteCerts when TLSInsecure is true --- internal/certinfo/certinfo_handlers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/certinfo/certinfo_handlers.go b/internal/certinfo/certinfo_handlers.go index 924395c..2b0ccfe 100644 --- a/internal/certinfo/certinfo_handlers.go +++ b/internal/certinfo/certinfo_handlers.go @@ -159,14 +159,14 @@ func (c *CertinfoConfig) GetRemoteCerts() error { } defer conn.Close() + cs := conn.ConnectionState() + c.TLSEndpointCerts = cs.PeerCertificates + // do not verify server certificates if TLSInsecure if c.TLSInsecure { return nil } - cs := conn.ConnectionState() - c.TLSEndpointCerts = cs.PeerCertificates - opts := x509.VerifyOptions{ DNSName: c.TLSServerName, Roots: c.CACertsPool, From e4d42749f5e3b4664075b636c18503c86aff3d94 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Wed, 17 Dec 2025 17:58:32 +0100 Subject: [PATCH 2/2] ci: add more tests for PrintData to address certificate fetch errors from a TLS endpoint --- cmd/certinfo.go | 7 +- internal/certinfo/certinfo_handlers_test.go | 174 +++++++++++++++----- 2 files changed, 135 insertions(+), 46 deletions(-) diff --git a/cmd/certinfo.go b/cmd/certinfo.go index d83cb04..47429aa 100644 --- a/cmd/certinfo.go +++ b/cmd/certinfo.go @@ -71,8 +71,6 @@ Examples: return } - certinfoCfg.SetTLSInsecure(tlsInsecure).SetTLSServerName(tlsServerName) - if err = certinfoCfg.SetCaPoolFromFile(caBundleValue, fileReader); err != nil { fmt.Printf("Error importing CA Certificate bundle from file: %s", err) } @@ -81,6 +79,11 @@ Examples: fmt.Printf("Error importing Certificate bundle from file: %s", err) } + certinfoCfg.SetTLSInsecure(tlsInsecure).SetTLSServerName(tlsServerName) + + // SetTLSEndpoint may need the SNI/ServerName and insecure options to be set + // before being able to ask details about the certificate we want to a + // webserver using self-signed and valid certificates if err = certinfoCfg.SetTLSEndpoint(tlsEndpoint); err != nil { fmt.Printf("Error setting TLS endpoint: %s", err) } diff --git a/internal/certinfo/certinfo_handlers_test.go b/internal/certinfo/certinfo_handlers_test.go index 6216836..5e85ae9 100644 --- a/internal/certinfo/certinfo_handlers_test.go +++ b/internal/certinfo/certinfo_handlers_test.go @@ -267,14 +267,18 @@ func TestCertinfo_CertsToTables(t *testing.T) { } func TestCertinfo_PrintData(t *testing.T) { - noErrorsTests := []struct { - desc string - keyFile string - certFile string - caCertFile string - keyCertMatch bool - tlsEndpoint string - srvCfg demoHTTPServerConfig + tests := []struct { + desc string + keyFile string + certFile string + caCertFile string + keyCertMatch bool + tlsEndpoint string + tlsInsecure bool + tlsServerName string + srvCfg demoHTTPServerConfig + expectCertsFetchErr bool + expectCertsFetcMsg string }{ { desc: "local CA cert and key", @@ -290,11 +294,12 @@ func TestCertinfo_PrintData(t *testing.T) { keyCertMatch: true, }, { - desc: "local key and remote TLS Endpoint", - keyFile: RSASampleCertKeyFile, - caCertFile: RSACaCertFile, - keyCertMatch: true, - tlsEndpoint: "localhost:46401", + desc: "local key and remote TLS Endpoint, certs validated", + keyFile: RSASampleCertKeyFile, + caCertFile: RSACaCertFile, + keyCertMatch: true, + tlsEndpoint: "localhost:46401", + tlsServerName: "example.com", srvCfg: demoHTTPServerConfig{ serverAddr: "localhost:46401", serverName: "example.com", @@ -302,9 +307,69 @@ func TestCertinfo_PrintData(t *testing.T) { serverKeyFile: RSASampleCertKeyFile, }, }, + { + desc: "local key and remote TLS Endpoint, certs NOT validated", + keyFile: RSASampleCertKeyFile, + caCertFile: emptyString, + tlsEndpoint: "localhost:46402", + tlsServerName: "example.com", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46402", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + expectCertsFetchErr: true, + expectCertsFetcMsg: "unable to get endpoint certificates: TLS handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority", + }, + + { + desc: "local key and remote TLS Endpoint, TLS Insecure", + keyFile: RSASampleCertKeyFile, + caCertFile: emptyString, + keyCertMatch: true, + tlsEndpoint: "localhost:46403", + tlsInsecure: true, + tlsServerName: "example.com", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46403", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + }, + { + desc: "local key and remote TLS Endpoint, missing TLS ServerName", + keyFile: RSASampleCertKeyFile, + caCertFile: RSACaCertFile, + tlsEndpoint: "localhost:46404", + tlsServerName: emptyString, + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46404", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + expectCertsFetchErr: true, + expectCertsFetcMsg: "unable to get endpoint certificates: TLS handshake failed: tls: failed to verify certificate: x509: certificate is valid for example.com, example.net, example.de, not localhost", + }, + { + desc: "local key and remote TLS Endpoint, no key match", + keyFile: ED25519SamplePlaintextPrivateKey, + caCertFile: RSACaCertFile, + keyCertMatch: false, + tlsEndpoint: "localhost:46405", + tlsServerName: "example.com", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46405", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + }, } - for _, tc := range noErrorsTests { + for _, tc := range tests { tt := tc t.Run("No errors test - "+tt.desc, func(t *testing.T) { t.Parallel() @@ -324,32 +389,32 @@ func TestCertinfo_PrintData(t *testing.T) { defer ts.Close() - cc.SetTLSServerName(tt.srvCfg.serverName) - cc.SetTLSEndpoint(tt.tlsEndpoint) + cc.SetTLSServerName(tt.tlsServerName) + cc.SetTLSInsecure(tt.tlsInsecure) + + // in most of these test cases SetTLSEndpoint depends + // on SetTLSServerName and/or SetTLSInsecure to be set + // before being able to fetch certificates from the TLS + // endpoint. + // The dependency is addressed with the order of method calls + // in cmd/certinfo.go. + // In this test, we call SetTLSServerName and SetTLSInsecure before + // SetTLSEndpoint to be sure the dependency is being addressed the + // same way. + err = cc.SetTLSEndpoint(tt.tlsEndpoint) + if !tt.expectCertsFetchErr { + require.NoError(t, err, "SetTLSEndpoint require NoError") + } + + if tt.expectCertsFetchErr { + require.EqualError(t, err, tt.expectCertsFetcMsg) + } } errPrint := cc.PrintData(&buffer) require.NoError(t, errPrint) got := buffer.String() - for _, want := range []string{ - "Certinfo", - "Certificate", - "Subject", - "Issuer", - "NotBefore", - "NotAfter", - "Expiration", - "IsCA", - "AuthorityKeyId", - "SubjectKeyId", - "PublicKeyAlgorithm", - "SignatureAlgorithm", - "SerialNumber", - "Fingerprint SHA-256", - } { - require.Contains(t, got, want) - } if tt.keyFile != emptyString { require.Contains(t, got, "PrivateKey file: "+tt.keyFile) @@ -363,16 +428,37 @@ func TestCertinfo_PrintData(t *testing.T) { require.Contains(t, got, "CA Certificates file: "+tt.caCertFile) } - if tt.keyFile != emptyString && tt.keyCertMatch { - require.Contains(t, got, "PrivateKey match: true") - } else { - require.Contains(t, got, "PrivateKey match: false") - } - - if tt.tlsEndpoint != emptyString { - require.Contains(t, got, "TLSEndpoint Certificates") - require.Contains(t, got, "Endpoint: "+tt.tlsEndpoint) - require.Contains(t, got, "ServerName: "+tt.srvCfg.serverName) + if !tt.expectCertsFetchErr { + for _, want := range []string{ + "Certinfo", + "Certificate", + "Subject", + "Issuer", + "NotBefore", + "NotAfter", + "Expiration", + "IsCA", + "AuthorityKeyId", + "SubjectKeyId", + "PublicKeyAlgorithm", + "SignatureAlgorithm", + "SerialNumber", + "Fingerprint SHA-256", + } { + require.Contains(t, got, want) + } + + if tt.keyFile != emptyString && tt.keyCertMatch { + require.Contains(t, got, "PrivateKey match: true") + } else { + require.Contains(t, got, "PrivateKey match: false") + } + + if tt.tlsEndpoint != emptyString { + require.Contains(t, got, "TLSEndpoint Certificates") + require.Contains(t, got, "Endpoint: "+tt.tlsEndpoint) + require.Contains(t, got, "ServerName: "+tt.tlsServerName) + } } }) }