From dec35074dfc1ffb30240db022c1cb954b5a5cf6c Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Mon, 15 Dec 2025 18:56:00 +0100 Subject: [PATCH 01/10] refactor: SetPrivateKeyFromFile take as argument the env variable holding the key's password --- cmd/certinfo.go | 7 ++++++- internal/certinfo/certinfo.go | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/certinfo.go b/cmd/certinfo.go index 009e6eb..30c9594 100644 --- a/cmd/certinfo.go +++ b/cmd/certinfo.go @@ -16,6 +16,7 @@ var ( tlsEndpoint string tlsServerName string tlsInsecure bool + keyPwEnvVar = "CERTINFO_PKEY_PW" ) var certinfoCmd = &cobra.Command{ @@ -83,7 +84,11 @@ Examples: fmt.Printf("Error setting TLS endpoint: %s", err) } - if err := certinfoCfg.SetPrivateKeyFromFile(keyFileValue, fileReader); err != nil { + if err := certinfoCfg.SetPrivateKeyFromFile( + keyFileValue, + keyPwEnvVar, + fileReader, + ); err != nil { fmt.Printf("Error importing key from file: %s", err) } diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index 07e4c23..e1b16f9 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -108,11 +108,11 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) er return nil } -func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, fileReader Reader) error { +func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, keyPwEnvVar string, fileReader Reader) error { if filePath != emptyString { keyFromFile, err := GetKeyFromFile( filePath, - privateKeyPwEnvVar, + keyPwEnvVar, fileReader, ) if err != nil { From 894f90862d919e257726d1b99426f431751fee0f Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Mon, 15 Dec 2025 18:57:01 +0100 Subject: [PATCH 02/10] ci: add tests for the Ca Cert, Cert and key file import. Test TLS insecure and ServerName methods --- internal/certinfo/certinfo_test.go | 330 ++++++++++++++++++++++++++--- 1 file changed, 298 insertions(+), 32 deletions(-) diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index 45de323..ca7222b 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -4,10 +4,19 @@ import ( "fmt" "testing" - "github.com/alecthomas/assert/v2" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" ) +type mockReader struct { + readError error +} + +func (mr mockReader) ReadFile(name string) ([]byte, error) { + mr.readError = fmt.Errorf("unable to read file %s", name) + return nil, mr.readError +} + func TestNewCertinfoConfig(t *testing.T) { t.Run("NewCertinfoConfig", func(t *testing.T) { t.Parallel() @@ -19,51 +28,308 @@ func TestNewCertinfoConfig(t *testing.T) { }) } -type mockReader struct { - readError error -} +var certinfoConfigFileReadErrorTests = []struct { + desc string + caCertFile string + certFile string + keyFile string + reader Reader + expectError bool + expectMsg map[string]string +}{ + { + desc: "emptyString", + caCertFile: emptyString, + certFile: emptyString, + keyFile: emptyString, + reader: inputReader, + expectError: false, + }, + { + desc: "unreadableFile", + caCertFile: unreadableFile, + certFile: unreadableFile, + keyFile: unreadableFile, + reader: mockErrReader, + expectError: true, + expectMsg: map[string]string{ + "caPool": "failed to read CA bundle file: unable to read file testdata/unreadable-file.txt", + "certs": "error reading certificate file: unable to read file testdata/unreadable-file.txt", + "key": "unable to read file testdata/unreadable-file.txt", + }, + }, + { + desc: "not exist", + caCertFile: "testdata/not-exist", + certFile: "testdata/not-exist", + keyFile: "testdata/not-exist", + reader: inputReader, + expectError: true, + expectMsg: map[string]string{ + "caPool": "failed to read CA bundle file: open testdata/not-exist: no such file or directory", + "certs": "error reading certificate file: open testdata/not-exist: no such file or directory", + "key": "open testdata/not-exist: no such file or directory", + }, + }, + { + desc: "wrong file", + caCertFile: sampleTextFile, + certFile: sampleTextFile, + keyFile: sampleTextFile, + reader: inputReader, + expectError: true, + expectMsg: map[string]string{ + "caPool": "unable to create CertPool from file", + "certs": "no valid certificates found in file testdata/sample-text.txt", + "key": "failed to decode PEM", + }, + }, + { + desc: "wrong PEM encoded file", -func (mr mockReader) ReadFile(name string) ([]byte, error) { - mr.readError = fmt.Errorf("unable to read file %s", name) - return nil, mr.readError + // PEM encoded keys get discarded by (_ *CertPool) AppendCertsFromPEM() + // but do not trigger errors (ok == false) + caCertFile: RSASamplePKCS8PlaintextPrivateKey, + certFile: ECDSASamplePlaintextPrivateKey, + keyFile: ED25519SampleCertificate, + reader: inputReader, + expectError: true, + expectMsg: map[string]string{ + "caPool": "unable to create CertPool from file", + "certs": "no valid certificates found in file testdata/ecdsa-plaintext-private-key.pem", + "key": "unsupported key format or invalid password", + }, + }, } func TestCertinfo_SetCaPoolFromFile(t *testing.T) { - t.Run("FileReadErrors", func(t *testing.T) { + for _, tc := range certinfoConfigFileReadErrorTests { + tt := tc + t.Run("File Read Error Test "+tt.desc, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetCaPoolFromFile(tt.caCertFile, tt.reader) + + // CertinfoConfig methods do nothing if an empty string is passed + // as filePath + if tt.caCertFile == emptyString { + require.NoError(t, err) + return + } + + require.Error(t, err) + require.EqualError( + t, + err, + tt.expectMsg["caPool"], + ) + }) + } + + t.Run("File Read Success Test", func(t *testing.T) { t.Parallel() - cc, err := NewCertinfoConfig() + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetCaPoolFromFile( + RSACaCertFile, + inputReader, + ) + require.NoError(t, err) - errEmpty := cc.SetCaPoolFromFile(emptyString, inputReader) - require.NoError(t, errEmpty, "error emptyString") + require.Equal(t, RSACaCertFile, cc.CACertsFilePath) - errNoRead := cc.SetCaPoolFromFile(unreadableFile, mockErrReader) - require.Error(t, errNoRead, "error unreadableFile") - assert.Equal(t, - "failed to read CA bundle file: unable to read file testdata/unreadable-file.txt", - errNoRead.Error(), - "check unreadableFile", + wantPool, errWantPool := GetRootCertsFromFile( + RSACaCertFile, + inputReader, ) + require.NoError(t, errWantPool) + + require.True(t, wantPool.Equal(cc.CACertsPool)) + + if diff := cmp.Diff(wantPool, cc.CACertsPool); diff != "" { + t.Errorf( + "SetCaPoolFromFile: pool mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +func TestCertinfo_SetCertsFromFile(t *testing.T) { + for _, tc := range certinfoConfigFileReadErrorTests { + tt := tc + t.Run("File Read Error Test "+tt.desc, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetCertsFromFile(tt.certFile, tt.reader) + + // CertinfoConfig methods do nothing if an empty string is passed + // as filePath + if tt.certFile == emptyString { + require.NoError(t, err) + return + } + + require.Error(t, err) + require.EqualError( + t, + err, + tt.expectMsg["certs"], + ) + }) + } + + t.Run("File Read Success Test", func(t *testing.T) { + t.Parallel() - errNoExist := cc.SetCaPoolFromFile("testdata/not-exist", inputReader) - require.Error(t, errNoExist, "error file not-exist") - assert.Equal(t, - "failed to read CA bundle file: open testdata/not-exist: no such file or directory", - errNoExist.Error(), - "read not-exist file", + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetCertsFromFile( + RSASamplePKCS8Certificate, + inputReader, + ) + + require.NoError(t, err) + + require.Equal(t, RSASamplePKCS8Certificate, cc.CertsBundleFilePath) + + wantCerts, errWantCrt := GetCertsFromBundle( + RSASamplePKCS8Certificate, + inputReader, + ) + require.NoError(t, errWantCrt) + + if diff := cmp.Diff(wantCerts, cc.CertsBundle); diff != "" { + t.Errorf( + "SetCertsFromFile: pool mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +func TestCertinfo_SetPrivateKeyFromFile(t *testing.T) { + for _, tc := range certinfoConfigFileReadErrorTests { + tt := tc + t.Run("File Read Error Test "+tt.desc, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetPrivateKeyFromFile( + tt.keyFile, + privateKeyPwEnvVar, + tt.reader, + ) + + // CertinfoConfig methods do nothing if an empty string is passed + // as filePath + if tt.keyFile == emptyString { + require.NoError(t, err) + return + } + + require.Error(t, err) + require.EqualError( + t, + err, + tt.expectMsg["key"], + ) + }) + } + + t.Run("File Read Success Test", func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetPrivateKeyFromFile( + ED25519SamplePlaintextPrivateKey, + privateKeyPwEnvVar, + inputReader, ) - errWrongCert := cc.SetCaPoolFromFile(RSACaCertKeyFile, inputReader) - require.Error(t, errWrongCert, "error wrong cert") - assert.Equal(t, - "unable to create CertPool from file", - errWrongCert.Error(), - "check wrong cert", + require.NoError(t, err) + + require.Equal(t, ED25519SamplePlaintextPrivateKey, cc.PrivKeyFilePath) + + wantKey, errKey := GetKeyFromFile( + ED25519SamplePlaintextPrivateKey, + privateKeyPwEnvVar, + inputReader, ) + require.NoError(t, errKey) - // TODO: complete, compare struct data with input - errRSACACert := cc.SetCaPoolFromFile(RSACaCertFile, inputReader) - require.NoError(t, errRSACACert, "error generated RSACaCertFile") + if diff := cmp.Diff(wantKey, cc.PrivKey); diff != "" { + t.Errorf( + "SetCertsFromFile: pool mismatch (-want +got):\n%s", + diff, + ) + } }) } + +func TestCertinfo_SetTLSInsecure(t *testing.T) { + tests := []bool{ + true, + false, + } + + for _, tc := range tests { + tt := tc + testname := fmt.Sprintf("%v", tt) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + cc.SetTLSInsecure(tt) + + require.Equal( + t, + tt, + cc.TLSInsecure, + ) + }) + } +} + +func TestCertinfo_SetTLSServeName(t *testing.T) { + tests := []string{ + emptyString, + "test", + "example.com", + } + + for _, tc := range tests { + tt := tc + testname := fmt.Sprintf("%v", tt) + t.Run(testname, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + cc.SetTLSServerName(tt) + + require.Equal( + t, + tt, + cc.TLSServerName, + ) + }) + } +} From f1ccf0e0ccd32f5e1d5f10beb740ae929cbdd5e6 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Mon, 15 Dec 2025 19:57:03 +0100 Subject: [PATCH 03/10] fix: typos --- internal/certinfo/certinfo_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index ca7222b..f2111dc 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -274,7 +274,7 @@ func TestCertinfo_SetPrivateKeyFromFile(t *testing.T) { if diff := cmp.Diff(wantKey, cc.PrivKey); diff != "" { t.Errorf( - "SetCertsFromFile: pool mismatch (-want +got):\n%s", + "SetPrivateKeyFromFile: key mismatch (-want +got):\n%s", diff, ) } @@ -307,7 +307,7 @@ func TestCertinfo_SetTLSInsecure(t *testing.T) { } } -func TestCertinfo_SetTLSServeName(t *testing.T) { +func TestCertinfo_SetTLSServerName(t *testing.T) { tests := []string{ emptyString, "test", From 95e03b5d286e6806e902091c2903fe61545d8c21 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 12:17:40 +0100 Subject: [PATCH 04/10] refactor: SetTLSEndpoint improve error handling and delay assignment of TLSEndpoint to receiver --- internal/certinfo/certinfo.go | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index e1b16f9..81c3cf8 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -31,7 +31,6 @@ type CertinfoConfig struct { TLSEndpointPort string TLSEndpointCerts []*x509.Certificate TLSEndpointCertsFromKey bool - TLSEndpointCertsValid bool TLSServerName string TLSInsecure bool } @@ -126,18 +125,21 @@ func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, keyPwEnvVar stri return nil } -func (c *CertinfoConfig) SetTLSEndpoint(e string) error { - if e != "" { - c.TLSEndpoint = e - - eHost, ePort, err := net.SplitHostPort(c.TLSEndpoint) +func (c *CertinfoConfig) SetTLSEndpoint(hostport string) error { + if hostport != emptyString { + eHost, ePort, err := net.SplitHostPort(hostport) if err != nil { return fmt.Errorf("invalid TLS endpoint %q: %w", c.TLSEndpoint, err) } + c.TLSEndpoint = hostport c.TLSEndpointHost = eHost c.TLSEndpointPort = ePort - c.GetRemoteCerts() + + err = c.GetRemoteCerts() + if err != nil { + return fmt.Errorf("unable to get endpoint certificates: %w", err) + } } return nil From 46eba8b860576d82f3953dc825a47a25e5e74b0e Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 12:18:50 +0100 Subject: [PATCH 05/10] refactor: GetRemoteCerts improve error handling --- internal/certinfo/certinfo_handlers.go | 32 +++++++++++++++++--------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/internal/certinfo/certinfo_handlers.go b/internal/certinfo/certinfo_handlers.go index 34cd217..7202e51 100644 --- a/internal/certinfo/certinfo_handlers.go +++ b/internal/certinfo/certinfo_handlers.go @@ -124,11 +124,13 @@ func (c *CertinfoConfig) PrintData() { } } -// TODO: return an error on fails -func (c *CertinfoConfig) GetRemoteCerts() { - tlsConfig := &tls.Config{RootCAs: c.CACertsPool, InsecureSkipVerify: c.TLSInsecure} +func (c *CertinfoConfig) GetRemoteCerts() error { + tlsConfig := &tls.Config{ + RootCAs: c.CACertsPool, + InsecureSkipVerify: c.TLSInsecure, + } - if c.TLSServerName != "" { + if c.TLSServerName != emptyString { tlsConfig.ServerName = c.TLSServerName } @@ -138,14 +140,22 @@ func (c *CertinfoConfig) GetRemoteCerts() { Timeout: TLSTimeout, } - conn, err := tls.DialWithDialer(dialer, "tcp", serverAddr, tlsConfig) + conn, err := tls.DialWithDialer( + dialer, + "tcp", + serverAddr, + tlsConfig, + ) if err != nil { - fmt.Fprintf(os.Stderr, "TLS handshake failed: %v\n", err) - - return + return fmt.Errorf("TLS handshake failed: %w", err) } defer conn.Close() + // do not verify server certificates if TLSInsecure + if c.TLSInsecure { + return nil + } + cs := conn.ConnectionState() c.TLSEndpointCerts = cs.PeerCertificates @@ -160,10 +170,10 @@ func (c *CertinfoConfig) GetRemoteCerts() { } if _, err := c.TLSEndpointCerts[0].Verify(opts); err != nil { - fmt.Println(err) - } else { - c.TLSEndpointCertsValid = true + return fmt.Errorf("certificate verify: %w", err) } + + return nil } func CertsToTables(w io.Writer, certs []*x509.Certificate) { From 018abb4394039f6aa16e02dc7a3a5d6b7d8cefcc Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 12:22:08 +0100 Subject: [PATCH 06/10] ci: import NewHTTPSTestServer and add key/cert options --- internal/certinfo/main_test.go | 93 +++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index 85df457..379c135 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -3,6 +3,7 @@ package certinfo import ( "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" @@ -10,9 +11,13 @@ import ( "fmt" "math/big" "net" + "net/http" + "net/http/httptest" "os" "testing" "time" + + "github.com/pires/go-proxyproto" ) type ( @@ -26,9 +31,19 @@ type ( parent *x509.Certificate } - MockErrReader struct{} + demoHTTPServerConfig struct { + serverAddr string + proxyprotoEnabled bool + serverName string + tlsCipherSuites []uint16 + tlsMaxVersion uint16 + serverCertFile string + serverKeyFile string + } + MockErrReader struct{} MockInputReader struct{} + mockReader struct{} ) var ( @@ -141,6 +156,10 @@ func (MockErrReader) ReadPassword(fd int) ([]byte, error) { }(fd) } +func (mockReader) ReadFile(name string) ([]byte, error) { + return nil, fmt.Errorf("unable to read file %s", name) +} + func generateRSACertificateData() { var err error @@ -342,3 +361,75 @@ func RSAPrivateKeyToPEM(key *rsa.PrivateKey) []byte { return keyPEM } + +func NewHTTPSTestServer(cfg demoHTTPServerConfig) (*httptest.Server, error) { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "DemoHTTPSServer Handler - client output\n") + fmt.Fprint(w, "Host requested: ", r.Host, "\n") + + fmt.Println("DemoHTTPSServer Handler - shell output") + }) + + ts := httptest.NewUnstartedServer(handler) + ts.EnableHTTP2 = true + + if cfg.serverAddr != emptyString && !cfg.proxyprotoEnabled { + listener, err := net.Listen("tcp", cfg.serverAddr) + if err != nil { + return nil, fmt.Errorf("error creating listener: %w", err) + } + + ts.Listener = listener + } + + if cfg.serverAddr != emptyString && cfg.proxyprotoEnabled { + ln, err := net.Listen("tcp", cfg.serverAddr) + if err != nil { + return nil, fmt.Errorf("error creating proxyproto enabled listener: %w", err) + } + + proxyListener := &proxyproto.Listener{ + Listener: ln, + ReadHeaderTimeout: 10 * time.Second, + } + + ts.Listener = proxyListener + } + + cert, err := tls.LoadX509KeyPair( + cfg.serverCertFile, + cfg.serverKeyFile, + ) + if err != nil { + return nil, err + } + + // Set default TLS CipherSuites to TLS 1.3 cipher suites + // https://pkg.go.dev/crypto/tls#pkg-constants + tlsCipherSuites := []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + } + + if len(cfg.tlsCipherSuites) > 0 { + tlsCipherSuites = cfg.tlsCipherSuites + } + + // Set default TLS MaxVersion to 1.3 + var tlsMaxVersion uint16 = tls.VersionTLS13 + + if cfg.tlsMaxVersion > 0 { + tlsMaxVersion = cfg.tlsMaxVersion + } + + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + CipherSuites: tlsCipherSuites, + MaxVersion: tlsMaxVersion, + } + + ts.StartTLS() + + return ts, nil +} From e8ec34e9181f1941787fc166fc902787a27ab453 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 12:22:32 +0100 Subject: [PATCH 07/10] ci: add tests for GetRemoteCerts --- internal/certinfo/certinfo_handlers_test.go | 133 ++++++++++++++++++++ internal/certinfo/certinfo_test.go | 9 -- 2 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 internal/certinfo/certinfo_handlers_test.go diff --git a/internal/certinfo/certinfo_handlers_test.go b/internal/certinfo/certinfo_handlers_test.go new file mode 100644 index 0000000..3314491 --- /dev/null +++ b/internal/certinfo/certinfo_handlers_test.go @@ -0,0 +1,133 @@ +package certinfo + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCertinfo_GetRemoteCerts(t *testing.T) { + tests := []struct { + desc string + srvCfg demoHTTPServerConfig + caCertFile string + insecure bool + expectSrvHost string + expectSrvPort string + expectError bool + expectMsg string + }{ + { + desc: "RSA Cert Success", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46301", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: RSACaCertFile, + expectSrvHost: "localhost", + expectSrvPort: "46301", + }, + { + desc: "Error Secure and No CA Cert", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46302", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: emptyString, + expectError: true, + expectMsg: "TLS handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority", + }, + + { + desc: "Malfomed Server Certificate", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46303", + serverName: "example.com", + serverCertFile: RSASamplePKCS8Certificate, + serverKeyFile: RSASamplePKCS8PlaintextPrivateKey, + }, + caCertFile: RSACaCertFile, + expectSrvHost: "localhost", + expectSrvPort: "46303", + expectError: true, + expectMsg: "TLS handshake failed: tls: failed to verify certificate: x509: certificate relies on legacy Common Name field, use SANs instead", + }, + { + desc: "No CA Cert and Insecure", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46304", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + insecure: true, + expectSrvHost: "localhost", + expectSrvPort: "46304", + caCertFile: emptyString, + }, + { + desc: "Wrong CA Cert and Secure", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46305", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: RSASamplePKCS8Certificate, + expectSrvHost: "localhost", + expectSrvPort: "46305", + expectError: true, + expectMsg: "TLS handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority", + }, + { + desc: "Wrong CA Cert and Insecure", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46306", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: RSASamplePKCS8Certificate, + insecure: true, + expectSrvHost: "localhost", + expectSrvPort: "46306", + }, + } + + for _, tc := range tests { + tt := tc + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + ts, err := NewHTTPSTestServer(tt.srvCfg) + require.NoError(t, err) + + defer ts.Close() + + cc, err := NewCertinfoConfig() + require.NoError(t, err) + + cc.SetTLSServerName(tt.srvCfg.serverName) + cc.SetCaPoolFromFile(tt.caCertFile, inputReader) + cc.SetTLSEndpoint(tt.srvCfg.serverAddr) + cc.SetTLSInsecure(tt.insecure) + + err = cc.GetRemoteCerts() + if !tt.expectError { + require.NoError(t, err, "check error not expected") + require.Equal(t, tt.srvCfg.serverName, cc.TLSServerName, "check TLSServerName") + require.Equal(t, tt.expectSrvHost, cc.TLSEndpointHost, "check TLSEndpointHost") + require.Equal(t, tt.expectSrvPort, cc.TLSEndpointPort, "check TLSEndpointPort") + require.Equal(t, tt.insecure, cc.TLSInsecure, "check TLSInsecure") + + return + } + + require.EqualError(t, err, tt.expectMsg, "check error expected") + }) + } +} diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index f2111dc..cbe12f6 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -8,15 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -type mockReader struct { - readError error -} - -func (mr mockReader) ReadFile(name string) ([]byte, error) { - mr.readError = fmt.Errorf("unable to read file %s", name) - return nil, mr.readError -} - func TestNewCertinfoConfig(t *testing.T) { t.Run("NewCertinfoConfig", func(t *testing.T) { t.Parallel() From f0607e10c734701e730c9bee3e6d7dc87fd9c9c9 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 22:00:53 +0100 Subject: [PATCH 08/10] refactor: PrintData take a writer as argument --- cmd/certinfo.go | 13 +++--- internal/certinfo/certinfo.go | 21 ++++++--- internal/certinfo/certinfo_handlers.go | 60 +++++++++++++++----------- internal/style/style_handlers.go | 7 +-- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/cmd/certinfo.go b/cmd/certinfo.go index 30c9594..d83cb04 100644 --- a/cmd/certinfo.go +++ b/cmd/certinfo.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" + "os" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -72,19 +73,19 @@ Examples: certinfoCfg.SetTLSInsecure(tlsInsecure).SetTLSServerName(tlsServerName) - if err := certinfoCfg.SetCaPoolFromFile(caBundleValue, fileReader); err != nil { + if err = certinfoCfg.SetCaPoolFromFile(caBundleValue, fileReader); err != nil { fmt.Printf("Error importing CA Certificate bundle from file: %s", err) } - if err := certinfoCfg.SetCertsFromFile(certBundleValue, fileReader); err != nil { + if err = certinfoCfg.SetCertsFromFile(certBundleValue, fileReader); err != nil { fmt.Printf("Error importing Certificate bundle from file: %s", err) } - if err := certinfoCfg.SetTLSEndpoint(tlsEndpoint); err != nil { + if err = certinfoCfg.SetTLSEndpoint(tlsEndpoint); err != nil { fmt.Printf("Error setting TLS endpoint: %s", err) } - if err := certinfoCfg.SetPrivateKeyFromFile( + if err = certinfoCfg.SetPrivateKeyFromFile( keyFileValue, keyPwEnvVar, fileReader, @@ -93,7 +94,9 @@ Examples: } // dump.Print(certinfoCfg) - certinfoCfg.PrintData() + if err = certinfoCfg.PrintData(os.Stdout); err != nil { + fmt.Printf("error printing Certinfo data: %s", err) + } }, } diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index 81c3cf8..7682d86 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -95,7 +95,10 @@ func (c *CertinfoConfig) SetCaPoolFromFile(filePath string, fileReader Reader) e func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) error { if filePath != emptyString { - certs, err := GetCertsFromBundle(filePath, fileReader) + certs, err := GetCertsFromBundle( + filePath, + fileReader, + ) if err != nil { return err } @@ -107,7 +110,11 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) er return nil } -func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, keyPwEnvVar string, fileReader Reader) error { +func (c *CertinfoConfig) SetPrivateKeyFromFile( + filePath string, + keyPwEnvVar string, + fileReader Reader, +) error { if filePath != emptyString { keyFromFile, err := GetKeyFromFile( filePath, @@ -145,14 +152,14 @@ func (c *CertinfoConfig) SetTLSEndpoint(hostport string) error { return nil } -func (c *CertinfoConfig) SetTLSInsecure(b bool) *CertinfoConfig { - c.TLSInsecure = b +func (c *CertinfoConfig) SetTLSInsecure(skipVerify bool) *CertinfoConfig { + c.TLSInsecure = skipVerify return c } -func (c *CertinfoConfig) SetTLSServerName(s string) *CertinfoConfig { - if s != "" { - c.TLSServerName = s +func (c *CertinfoConfig) SetTLSServerName(serverName string) *CertinfoConfig { + if serverName != emptyString { + c.TLSServerName = serverName } return c diff --git a/internal/certinfo/certinfo_handlers.go b/internal/certinfo/certinfo_handlers.go index 7202e51..924395c 100644 --- a/internal/certinfo/certinfo_handlers.go +++ b/internal/certinfo/certinfo_handlers.go @@ -12,7 +12,6 @@ import ( "fmt" "io" "net" - "os" "strconv" "strings" "time" @@ -22,29 +21,29 @@ import ( "github.com/xenos76/https-wrench/internal/style" ) -func (c *CertinfoConfig) PrintData() { +func (c *CertinfoConfig) PrintData(w io.Writer) error { ks := style.ItemKey.PaddingBottom(0).PaddingTop(1).PaddingLeft(1) sl := style.CertKeyP4.Bold(true) sv := style.CertValue.Bold(false) - fmt.Println() - fmt.Println(style.LgSprintf(style.Cmd, "Certinfo")) - fmt.Println() + fmt.Fprintln(w) + fmt.Fprintln(w, style.LgSprintf(style.Cmd, "Certinfo")) + fmt.Fprintln(w) if c.PrivKey != nil { - fmt.Println(style.LgSprintf(ks, "PrivateKey")) - fmt.Println(style.LgSprintf( + fmt.Fprintln(w, style.LgSprintf(ks, "PrivateKey")) + fmt.Fprintln(w, style.LgSprintf( sl.PaddingTop(1), "PrivateKey file: %v", sv.Render(c.PrivKeyFilePath), )) - style.PrintKeyInfoStyle(c.PrivKey) + style.PrintKeyInfoStyle(w, c.PrivKey) } if len(c.CertsBundle) > 0 { - fmt.Println(style.LgSprintf(ks, "Certificates")) + fmt.Fprintln(w, style.LgSprintf(ks, "Certificates")) - fmt.Println(style.LgSprintf( + fmt.Fprintln(w, style.LgSprintf( sl.PaddingTop(1), "Certificate bundle file: %v", sv.Render(c.CertsBundleFilePath), @@ -53,31 +52,34 @@ func (c *CertinfoConfig) PrintData() { if c.PrivKey != nil { certMatch, err := certMatchPrivateKey(c.CertsBundle[0], c.PrivKey) if err != nil { - fmt.Print(err) + return fmt.Errorf( + "unable to check if private key matches local certificate: %w", + err, + ) } - fmt.Println(style.LgSprintf( + fmt.Fprintln(w, style.LgSprintf( sl, "PrivateKey match: %v", style.BoolStyle(certMatch), )) } - CertsToTables(os.Stdout, c.CertsBundle) + CertsToTables(w, c.CertsBundle) } if len(c.TLSEndpointCerts) > 0 { endpoint := sv.Render(c.TLSEndpointHost + ":" + c.TLSEndpointPort) - fmt.Println(style.LgSprintf(ks, "TLSEndpoint Certificates")) - fmt.Println(style.LgSprintf( + fmt.Fprintln(w, style.LgSprintf(ks, "TLSEndpoint Certificates")) + fmt.Fprintln(w, style.LgSprintf( sl.PaddingTop(1), "Endpoint: %v", endpoint, )) - if c.TLSServerName != "" { - fmt.Println(style.LgSprintf( + if c.TLSServerName != emptyString { + fmt.Fprintln(w, style.LgSprintf( sl, "ServerName: %v", sv.Render(c.TLSServerName), @@ -87,22 +89,25 @@ func (c *CertinfoConfig) PrintData() { if c.PrivKey != nil { tlsMatch, err := certMatchPrivateKey(c.TLSEndpointCerts[0], c.PrivKey) if err != nil { - fmt.Print(err) + return fmt.Errorf( + "unable to check if private key matches remote TLS Endpoint certificate: %w", + err, + ) } - fmt.Println(style.LgSprintf( + fmt.Fprintln(w, style.LgSprintf( sl, "PrivateKey match: %v", style.BoolStyle(tlsMatch), )) } - CertsToTables(os.Stdout, c.TLSEndpointCerts) + CertsToTables(w, c.TLSEndpointCerts) } if len(c.CACertsFilePath) > 0 { - fmt.Println(style.LgSprintf(ks, "CA Certificates")) - fmt.Println( + fmt.Fprintln(w, style.LgSprintf(ks, "CA Certificates")) + fmt.Fprintln(w, style.LgSprintf( sl.PaddingTop(1).PaddingBottom(1), "CA Certificates file: %v", @@ -115,13 +120,16 @@ func (c *CertinfoConfig) PrintData() { inputReader, ) if err != nil { - fmt.Printf("unable for read Root certificates from %s: %s", c.CACertsFilePath, err) - - return + return fmt.Errorf( + "unable for read Root certificates from %s: %w", + c.CACertsFilePath, + err, + ) } - CertsToTables(os.Stdout, rootCerts) + CertsToTables(w, rootCerts) } + return nil } func (c *CertinfoConfig) GetRemoteCerts() error { diff --git a/internal/style/style_handlers.go b/internal/style/style_handlers.go index 6ca04c3..3ca05d2 100644 --- a/internal/style/style_handlers.go +++ b/internal/style/style_handlers.go @@ -7,6 +7,7 @@ import ( "crypto/ed25519" "crypto/rsa" "fmt" + "io" "strconv" "strings" @@ -54,7 +55,7 @@ func BoolStyle(b bool) string { return LgSprintf(BoolFalse, "false") } -func PrintKeyInfoStyle(privKey crypto.PrivateKey) { +func PrintKeyInfoStyle(w io.Writer, privKey crypto.PrivateKey) { sl := CertKeyP4.Render sv := CertValue.Render t := table.New().Border(lipgloss.HiddenBorder()) @@ -73,7 +74,7 @@ func PrintKeyInfoStyle(privKey crypto.PrivateKey) { t.Row(sl("Curve"), sv(curve)) case ed25519.PrivateKey: - t.Row(sl("Type"), sv("Ed25519")) + t.Row(sl("Type"), sv("ED25519")) size := fmt.Sprintf("%d bytes", len(k)) t.Row(sl("Key Size"), sv(size)) @@ -83,7 +84,7 @@ func PrintKeyInfoStyle(privKey crypto.PrivateKey) { t.Row(sv(unknMsg)) } - fmt.Println(t.Render()) + fmt.Fprintln(w, t.Render()) t.ClearRows() } From 111aa5e6648b53d8b426b8c06b3da311e6a95779 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 22:01:41 +0100 Subject: [PATCH 09/10] ci: add tests for CertsToTables and PrintData --- internal/certinfo/certinfo_handlers_test.go | 246 ++++++++++++++++++++ internal/certinfo/certinfo_test.go | 84 +++++++ 2 files changed, 330 insertions(+) diff --git a/internal/certinfo/certinfo_handlers_test.go b/internal/certinfo/certinfo_handlers_test.go index 3314491..bc33a4b 100644 --- a/internal/certinfo/certinfo_handlers_test.go +++ b/internal/certinfo/certinfo_handlers_test.go @@ -1,6 +1,8 @@ package certinfo import ( + "bytes" + "crypto/x509" "testing" "github.com/stretchr/testify/require" @@ -96,6 +98,30 @@ func TestCertinfo_GetRemoteCerts(t *testing.T) { expectSrvHost: "localhost", expectSrvPort: "46306", }, + { + desc: "IPV6 Enpoint RSA Cert Success", + srvCfg: demoHTTPServerConfig{ + serverAddr: "[::1]:46307", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: RSACaCertFile, + expectSrvHost: "::1", + expectSrvPort: "46307", + }, + { + desc: "Error wrong ServerName", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46308", + serverName: "example.co.uk", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + caCertFile: RSACaCertFile, + expectError: true, + expectMsg: "TLS handshake failed: tls: failed to verify certificate: x509: certificate is valid for example.com, example.net, example.de, not example.co.uk", + }, } for _, tc := range tests { @@ -131,3 +157,223 @@ func TestCertinfo_GetRemoteCerts(t *testing.T) { }) } } + +func TestCertinfo_CertsToTables(t *testing.T) { + rsaSampleCert, err := GetCertsFromBundle( + RSASampleCertFile, + inputReader, + ) + require.NoError(t, err) + + ecdsaCert, err := GetCertsFromBundle( + ECDSASampleCertificate, + inputReader, + ) + require.NoError(t, err) + + ed25519Cert, err := GetCertsFromBundle( + ED25519SampleCertificate, + inputReader, + ) + require.NoError(t, err) + + tests := []struct { + desc string + cert *x509.Certificate + subject string + isCA string + expiration string + dnsNames string + publicKeyAlgorithm string + signatureAlgorithm string + }{ + // TODO: add expired cert case + { + desc: "RSA CA Cert", + cert: RSACaCertParent, + subject: "Subject CN=RSA Testing CA", + isCA: "IsCA true", + expiration: "Expiration 23 hours from now", + dnsNames: "DNSNames []", + publicKeyAlgorithm: "PublicKeyAlgorithm RSA", + signatureAlgorithm: "SignatureAlgorithm SHA256-RSA", + }, + { + desc: "RSA Cert", + cert: rsaSampleCert[0], + subject: "Subject CN=RSA Testing Sample Certificate", + isCA: "IsCA false", + expiration: "Expiration 23 hours from now", + dnsNames: "DNSNames [example.com, example.net, example.de]", + publicKeyAlgorithm: "PublicKeyAlgorithm RSA", + signatureAlgorithm: "SignatureAlgorithm SHA256-RSA", + }, + { + desc: "ECDSA CA Cert", + cert: ecdsaCert[0], + subject: "Subject CN=example.com,O=Example Org", + isCA: "IsCA true", + dnsNames: "DNSNames []", + publicKeyAlgorithm: "PublicKeyAlgorithm ECDSA", + signatureAlgorithm: "SignatureAlgorithm ECDSA-SHA256", + }, + { + desc: "ED25519 CA Cert", + cert: ed25519Cert[0], + subject: "Subject CN=example.com,O=Example Org", + isCA: "IsCA true", + dnsNames: "DNSNames []", + publicKeyAlgorithm: "PublicKeyAlgorithm Ed25519", + signatureAlgorithm: "SignatureAlgorithm Ed25519", + }, + } + + for _, tc := range tests { + tt := tc + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + buffer := bytes.Buffer{} + certs := []*x509.Certificate{ + tt.cert, + } + CertsToTables(&buffer, certs) + + got := buffer.String() + + for _, want := range []string{ + "Certificate", + "Subject", + "Issuer", + "NotBefore", + "NotAfter", + "Expiration", + "IsCA", + "AuthorityKeyId", + "SubjectKeyId", + "PublicKeyAlgorithm", + "SignatureAlgorithm", + "SerialNumber", + "Fingerprint SHA-256", + tt.isCA, + tt.dnsNames, + tt.publicKeyAlgorithm, + tt.signatureAlgorithm, + } { + require.Contains(t, got, want) + } + }) + } +} + +func TestCertinfo_PrintData(t *testing.T) { + noErrorsTests := []struct { + desc string + keyFile string + certFile string + caCertFile string + keyCertMatch bool + tlsEndpoint string + srvCfg demoHTTPServerConfig + }{ + { + desc: "local CA cert and key", + keyFile: RSACaCertKeyFile, + certFile: RSACaCertFile, + keyCertMatch: true, + }, + { + desc: "local cert and key with CA", + keyFile: RSASampleCertKeyFile, + certFile: RSASampleCertFile, + caCertFile: RSACaCertFile, + keyCertMatch: true, + }, + { + desc: "local key and remote TLS Enpoint", + keyFile: RSASampleCertKeyFile, + caCertFile: RSACaCertFile, + keyCertMatch: true, + tlsEndpoint: "localhost:46401", + srvCfg: demoHTTPServerConfig{ + serverAddr: "localhost:46401", + serverName: "example.com", + serverCertFile: RSASampleCertFile, + serverKeyFile: RSASampleCertKeyFile, + }, + }, + } + + for _, tc := range noErrorsTests { + tt := tc + t.Run("No errors test - "+tt.desc, func(t *testing.T) { + t.Parallel() + + buffer := bytes.Buffer{} + + cc, err := NewCertinfoConfig() + require.NoError(t, err) + + cc.SetPrivateKeyFromFile(tt.keyFile, "notSet", inputReader) + cc.SetCertsFromFile(tt.certFile, inputReader) + cc.SetCaPoolFromFile(tt.caCertFile, inputReader) + + if tt.tlsEndpoint != emptyString { + ts, errSrv := NewHTTPSTestServer(tt.srvCfg) + require.NoError(t, errSrv) + + defer ts.Close() + + cc.SetTLSServerName(tt.srvCfg.serverName) + cc.SetTLSEndpoint(tt.tlsEndpoint) + } + + 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) + } + + if tt.certFile != emptyString { + require.Contains(t, got, "Certificate bundle file: "+tt.certFile) + } + + if tt.caCertFile != emptyString { + 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) + } + }) + } +} diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index cbe12f6..64ff9b3 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -324,3 +324,87 @@ func TestCertinfo_SetTLSServerName(t *testing.T) { }) } } + +func TestCertinfo_SetTLSEndpoint(t *testing.T) { + tests := []struct { + desc string + endpoint string + expectEnpoint string + expectHost string + expectPort string + processErr bool + expectMsg string + }{ + { + desc: "success", + endpoint: "localhost:443", + expectEnpoint: "localhost:443", + expectHost: "localhost", + expectPort: "443", + }, + { + desc: "success IPV6", + endpoint: "[::1]:443", + expectEnpoint: "[::1]:443", + expectHost: "::1", + expectPort: "443", + }, + { + desc: "success IPV4", + endpoint: "127.0.0.1:443", + expectEnpoint: "127.0.0.1:443", + expectHost: "127.0.0.1", + expectPort: "443", + }, + { + desc: "error malformed host", + endpoint: "localh#$%ost:443", + processErr: true, + expectMsg: "unable to get endpoint certificates: TLS handshake failed: dial tcp: lookup localh#$%ost: no such host", + }, + { + desc: "error missing port", + endpoint: "localhost", + processErr: true, + expectMsg: "invalid TLS endpoint \"\": address localhost: missing port in address", + }, + { + desc: "error missing host", + endpoint: ":80443", + processErr: true, + expectMsg: "unable to get endpoint certificates: TLS handshake failed: dial tcp: address 80443: invalid port", + }, + { + desc: "error endpoint includes scheme", + endpoint: "https://localhost:80443", + processErr: true, + expectMsg: "invalid TLS endpoint \"\": address https://localhost:80443: too many colons in address", + }, + } + + for _, tc := range tests { + tt := tc + t.Run(tt.desc, func(t *testing.T) { + t.Parallel() + + cc, errNew := NewCertinfoConfig() + require.NoError(t, errNew) + + err := cc.SetTLSEndpoint(tt.endpoint) + + if !tt.processErr { + // skip requiring NoError since SetTLSEndpoint will always return network errors + // in this case. See tests related to GetRemoteCerts for more + + // require.NoError(t, err) + require.Equal(t, tt.expectEnpoint, cc.TLSEndpoint, "check TLSEndpoint") + require.Equal(t, tt.expectHost, cc.TLSEndpointHost, "check TLSEndpointHost") + require.Equal(t, tt.expectPort, cc.TLSEndpointPort, "check TLSEndpointPort") + + return + } + + require.EqualError(t, err, tt.expectMsg) + }) + } +} From 2bcfc4c20c2e5b425af1f455c7bcbcb2a0311bbb Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Tue, 16 Dec 2025 22:34:16 +0100 Subject: [PATCH 10/10] fix: typos --- devenv.nix | 24 +++++----- internal/certinfo/certinfo.go | 2 +- internal/certinfo/certinfo_handlers_test.go | 6 +-- internal/certinfo/certinfo_test.go | 50 ++++++++++----------- 4 files changed, 41 insertions(+), 41 deletions(-) diff --git a/devenv.nix b/devenv.nix index e953d06..5c244f1 100644 --- a/devenv.nix +++ b/devenv.nix @@ -452,65 +452,65 @@ in { ''; scripts.test-certinfo-tlsendpoint.exec = '' - gum format "## test certinfo tlsEnpoint" + gum format "## test certinfo tlsEndpoint" ./dist/https-wrench certinfo --tls-endpoint repo.os76.xyz:443 ''; scripts.test-certinfo-tlsendpoint-wrong-ca-file.exec = '' - gum format "## test certinfo tlsEnpoint with wrong CA file" + gum format "## test certinfo tlsEndpoint with wrong CA file" set +o pipefail ./dist/https-wrench certinfo --tls-endpoint repo.os76.xyz:443 --ca-bundle $CAROOT/rootCA.pem 2>&1 | grep 'certificate signed by unknown authority' ''; scripts.test-certinfo-tlsendpoint-servername.exec = '' - gum format "## test certinfo tlsEnpoint servername" + gum format "## test certinfo tlsEndpoint servername" ./dist/https-wrench certinfo --tls-endpoint repo.os76.xyz:443 --tls-servername www.os76.xyz ''; scripts.test-certinfo-tlsendpoint-timeout.exec = '' - gum format "## test certinfo tlsEnpoint timeout" + gum format "## test certinfo tlsEndpoint timeout" set +o pipefail ./dist/https-wrench certinfo --tls-endpoint repo.os76.xyz:344 2>&1 | grep timeout ''; scripts.test-certinfo-tlsendpoint-malformed.exec = '' - gum format "## test certinfo tlsEnpoint malformed (missing port)" + gum format "## test certinfo tlsEndpoint malformed (missing port)" set +o pipefail ./dist/https-wrench certinfo --tls-endpoint repo.os76.xyz | grep 'missing port in address' ''; scripts.test-certinfo-tlsendpoint-insecure.exec = '' - gum format "## test certinfo tlsEnpoint Insecure" + gum format "## test certinfo tlsEndpoint Insecure" ./dist/https-wrench certinfo --tls-endpoint localhost:9443 --tls-insecure | grep 'certificate signed by unknown authority' ''; scripts.test-certinfo-tlsendpoint-ca-bundle.exec = '' - gum format "## test certinfo tlsEnpoint + ca-bundle" + gum format "## test certinfo tlsEndpoint + ca-bundle" ./dist/https-wrench certinfo --tls-endpoint localhost:9443 --ca-bundle $CAROOT/rootCA.pem ''; scripts.test-certinfo-tlsendpoint-ca-bundle-ipv4.exec = '' - gum format "## test certinfo IPv4 tlsEnpoint + ca-bundle" + gum format "## test certinfo IPv4 tlsEndpoint + ca-bundle" ./dist/https-wrench certinfo --tls-endpoint 127.0.0.1:9443 --ca-bundle $CAROOT/rootCA.pem ''; scripts.test-certinfo-tlsendpoint-ca-bundle-ipv6.exec = '' - gum format "## test certinfo IPV6 tlsEnpoint + ca-bundle " + gum format "## test certinfo IPV6 tlsEndpoint + ca-bundle " ./dist/https-wrench certinfo --tls-endpoint [::1]:9443 --ca-bundle $CAROOT/rootCA.pem ''; scripts.test-certinfo-tlsendpoint-rsa-key-cert.exec = '' - gum format "## test certinfo tlsEnpoint: RSA key + cert" + gum format "## test certinfo tlsEndpoint: RSA key + cert" ./dist/https-wrench certinfo --tls-endpoint localhost:9443 --tls-insecure --tls-servername example.com --key-file $CAROOT/key.pem | grep 'PrivateKey match: true' ''; scripts.test-certinfo-tlsendpoint-ecdsa-key-cert.exec = '' - gum format "## test certinfo tlsEnpoint: ECDSA key + cert" + gum format "## test certinfo tlsEndpoint: ECDSA key + cert" ./dist/https-wrench certinfo --tls-endpoint localhost:9446 --tls-insecure --tls-servername example.com --key-file $ECDSA_DIR/ecdsa.key | grep 'PrivateKey match: true' ''; scripts.test-certinfo-tlsendpoint-ed25519-key-cert.exec = '' - gum format "## test certinfo tlsEnpoint: ED25519 key + cert" + gum format "## test certinfo tlsEndpoint: ED25519 key + cert" ./dist/https-wrench certinfo --tls-endpoint localhost:9445 --tls-insecure --tls-servername example.com --key-file $ED25519_DIR/ed25519.key | grep 'PrivateKey match: true' ''; diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index 7682d86..243d3ec 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -136,7 +136,7 @@ func (c *CertinfoConfig) SetTLSEndpoint(hostport string) error { if hostport != emptyString { eHost, ePort, err := net.SplitHostPort(hostport) if err != nil { - return fmt.Errorf("invalid TLS endpoint %q: %w", c.TLSEndpoint, err) + return fmt.Errorf("invalid TLS endpoint %q: %w", hostport, err) } c.TLSEndpoint = hostport diff --git a/internal/certinfo/certinfo_handlers_test.go b/internal/certinfo/certinfo_handlers_test.go index bc33a4b..6216836 100644 --- a/internal/certinfo/certinfo_handlers_test.go +++ b/internal/certinfo/certinfo_handlers_test.go @@ -45,7 +45,7 @@ func TestCertinfo_GetRemoteCerts(t *testing.T) { }, { - desc: "Malfomed Server Certificate", + desc: "Malformed Server Certificate", srvCfg: demoHTTPServerConfig{ serverAddr: "localhost:46303", serverName: "example.com", @@ -99,7 +99,7 @@ func TestCertinfo_GetRemoteCerts(t *testing.T) { expectSrvPort: "46306", }, { - desc: "IPV6 Enpoint RSA Cert Success", + desc: "IPV6 Endpoint RSA Cert Success", srvCfg: demoHTTPServerConfig{ serverAddr: "[::1]:46307", serverName: "example.com", @@ -290,7 +290,7 @@ func TestCertinfo_PrintData(t *testing.T) { keyCertMatch: true, }, { - desc: "local key and remote TLS Enpoint", + desc: "local key and remote TLS Endpoint", keyFile: RSASampleCertKeyFile, caCertFile: RSACaCertFile, keyCertMatch: true, diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index 64ff9b3..74d3cd4 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -327,34 +327,34 @@ func TestCertinfo_SetTLSServerName(t *testing.T) { func TestCertinfo_SetTLSEndpoint(t *testing.T) { tests := []struct { - desc string - endpoint string - expectEnpoint string - expectHost string - expectPort string - processErr bool - expectMsg string + desc string + endpoint string + expectEndpoint string + expectHost string + expectPort string + processErr bool + expectMsg string }{ { - desc: "success", - endpoint: "localhost:443", - expectEnpoint: "localhost:443", - expectHost: "localhost", - expectPort: "443", + desc: "success", + endpoint: "localhost:443", + expectEndpoint: "localhost:443", + expectHost: "localhost", + expectPort: "443", }, { - desc: "success IPV6", - endpoint: "[::1]:443", - expectEnpoint: "[::1]:443", - expectHost: "::1", - expectPort: "443", + desc: "success IPV6", + endpoint: "[::1]:443", + expectEndpoint: "[::1]:443", + expectHost: "::1", + expectPort: "443", }, { - desc: "success IPV4", - endpoint: "127.0.0.1:443", - expectEnpoint: "127.0.0.1:443", - expectHost: "127.0.0.1", - expectPort: "443", + desc: "success IPV4", + endpoint: "127.0.0.1:443", + expectEndpoint: "127.0.0.1:443", + expectHost: "127.0.0.1", + expectPort: "443", }, { desc: "error malformed host", @@ -366,7 +366,7 @@ func TestCertinfo_SetTLSEndpoint(t *testing.T) { desc: "error missing port", endpoint: "localhost", processErr: true, - expectMsg: "invalid TLS endpoint \"\": address localhost: missing port in address", + expectMsg: "invalid TLS endpoint \"localhost\": address localhost: missing port in address", }, { desc: "error missing host", @@ -378,7 +378,7 @@ func TestCertinfo_SetTLSEndpoint(t *testing.T) { desc: "error endpoint includes scheme", endpoint: "https://localhost:80443", processErr: true, - expectMsg: "invalid TLS endpoint \"\": address https://localhost:80443: too many colons in address", + expectMsg: "invalid TLS endpoint \"https://localhost:80443\": address https://localhost:80443: too many colons in address", }, } @@ -397,7 +397,7 @@ func TestCertinfo_SetTLSEndpoint(t *testing.T) { // in this case. See tests related to GetRemoteCerts for more // require.NoError(t, err) - require.Equal(t, tt.expectEnpoint, cc.TLSEndpoint, "check TLSEndpoint") + require.Equal(t, tt.expectEndpoint, cc.TLSEndpoint, "check TLSEndpoint") require.Equal(t, tt.expectHost, cc.TLSEndpointHost, "check TLSEndpointHost") require.Equal(t, tt.expectPort, cc.TLSEndpointPort, "check TLSEndpointPort")