Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9da6d16
refactor: add Reader interface for file and password read
xenOs76 Dec 13, 2025
119d300
ci: add sample keys and certs for testing
xenOs76 Dec 13, 2025
4497c6d
ci: add tests for certinfo and common handlers
xenOs76 Dec 13, 2025
9277434
refactor: methods of structs implementing Reader interface declare on…
xenOs76 Dec 13, 2025
a568733
ci: update error checks and draft ParsePrivateKey test
xenOs76 Dec 13, 2025
14f51b4
docs: redact PEM blocks in testdata README
xenOs76 Dec 13, 2025
a896b25
refactor: GetKeyFromFile takes as argument the ENV var that could hol…
xenOs76 Dec 13, 2025
d2d6677
ci: use tables for most tests related to GetKeyFromFile and certMatch…
xenOs76 Dec 13, 2025
0afe908
fix: useless error assertion
xenOs76 Dec 13, 2025
669b04c
fix: typos and obsolete warning
xenOs76 Dec 13, 2025
9752a2d
refactor: createTmpFileWithContent use named returns to catch file cl…
xenOs76 Dec 13, 2025
2e54372
refactor: add nil checks to functions taking the Reader interface as …
xenOs76 Dec 13, 2025
eda1e0c
refactor: TestMain for better handling of cleanup and testdatadir cre…
xenOs76 Dec 13, 2025
7d7c831
refactor: createTmpFileWithContent adopt stricter permissions
xenOs76 Dec 14, 2025
4e8a929
refactor: GenerateCertificate create self signed CA certs with themse…
xenOs76 Dec 14, 2025
a62f1c0
ci: test for nil Reader
xenOs76 Dec 14, 2025
e133573
fix: typos in tests
xenOs76 Dec 14, 2025
31fe26b
ci: add more tests for GetCertsFromBundle and certMatchPrivateKey
xenOs76 Dec 14, 2025
b2eaa11
fix: incompleteCert typo
xenOs76 Dec 14, 2025
b022811
refactor: PrintCertInfo to use a Writer
xenOs76 Dec 14, 2025
0248027
ci: add test for PrintCertInfo
xenOs76 Dec 14, 2025
4f7c904
fix: typo
xenOs76 Dec 14, 2025
c48df9b
fix: PrintResponseDebug reference Writer from input args
xenOs76 Dec 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.direnv
dist
tests
testdata
internal/requests/testdata
internal/certinfo/testdata/*Cert*
manpages

# ---> Go
Expand Down
7 changes: 7 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ linters:
exclude: [""]
arguments: [15]

# https://github.com/mgechev/revive/blob/HEAD/RULES_DESCRIPTIONS.md#cyclomatic
- name: cyclomatic
severity: warning
disabled: false
exclude: [""]
arguments: [15]

exclusions:
generated: lax
presets:
Expand Down
6 changes: 3 additions & 3 deletions cmd/certinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,19 @@ Examples:

certinfoCfg.SetTLSInsecure(tlsInsecure).SetTLSServerName(tlsServerName)

if err := certinfoCfg.SetCaPoolFromFile(caBundleValue); 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); 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 {
fmt.Printf("Error setting TLS endpoint: %s", err)
}

if err := certinfoCfg.SetPrivateKeyFromFile(keyFileValue); err != nil {
if err := certinfoCfg.SetPrivateKeyFromFile(keyFileValue, fileReader); err != nil {
fmt.Printf("Error importing key from file: %s", err)
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Examples:
fmt.Print(err)
}

if err := requestsCfg.SetCaPoolFromFile(caBundlePath); err != nil {
if err := requestsCfg.SetCaPoolFromFile(caBundlePath, fileReader); err != nil {
fmt.Print(err)
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
_ "github.com/breml/rootcerts"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/xenos76/https-wrench/internal/certinfo"
)

var (
Expand All @@ -43,6 +44,7 @@ var (
caBundlePath string
certBundlePath string
keyFilePath string
fileReader certinfo.InputReader
)

var rootCmd = &cobra.Command{
Expand Down
1 change: 1 addition & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ in {
".gitignore"
".envrc"
"internal/certinfo/common_handlers.go"
"internal/certinfo/testdata"
"completions"
];
hooks = {
Expand Down
56 changes: 43 additions & 13 deletions internal/certinfo/certinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import (
"crypto/x509"
"fmt"
"net"
"os"
"time"

"golang.org/x/term"
)

const (
TLSTimeout = 3 * time.Second
CertExpWarnDays = 40
privateKeyPwEnvVar = "CERTINFO_PKEY_PW"
emptyString = ""
)

type CertinfoConfig struct {
Expand All @@ -32,15 +36,34 @@ type CertinfoConfig struct {
TLSInsecure bool
}

type (
Reader interface {
ReadFile(name string) ([]byte, error)
ReadPassword(fd int) ([]byte, error)
}

InputReader struct{}
)

var (
// TODO: remove
// certsBundle []*x509.Certificate
// privKey any
// tlsEndpoint string
TlsServerName string
TlsInsecure bool
inputReader InputReader
)

func (InputReader) ReadFile(name string) ([]byte, error) {
file, err := os.ReadFile(name)
if err != nil {
return nil, err
}

return file, nil
}

func (InputReader) ReadPassword(fd int) ([]byte, error) {
return term.ReadPassword(fd)
}

func NewCertinfoConfig() (*CertinfoConfig, error) {
defaultCertPool, err := x509.SystemCertPool()
if err != nil {
Expand All @@ -54,9 +77,12 @@ func NewCertinfoConfig() (*CertinfoConfig, error) {
return &c, nil
}

func (c *CertinfoConfig) SetCaPoolFromFile(filePath string) error {
if filePath != "" {
caCertsPool, err := GetRootCertsFromFile(filePath)
func (c *CertinfoConfig) SetCaPoolFromFile(filePath string, fileReader Reader) error {
if filePath != emptyString {
caCertsPool, err := GetRootCertsFromFile(
filePath,
fileReader,
)
if err != nil {
return err
}
Expand All @@ -68,9 +94,9 @@ func (c *CertinfoConfig) SetCaPoolFromFile(filePath string) error {
return nil
}

func (c *CertinfoConfig) SetCertsFromFile(filePath string) error {
if filePath != "" {
certs, err := GetCertsFromBundle(filePath)
func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) error {
if filePath != emptyString {
certs, err := GetCertsFromBundle(filePath, fileReader)
if err != nil {
return err
}
Expand All @@ -82,9 +108,13 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string) error {
return nil
}

func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string) error {
if filePath != "" {
keyFromFile, err := GetKeyFromFile(filePath)
func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, fileReader Reader) error {
if filePath != emptyString {
keyFromFile, err := GetKeyFromFile(
filePath,
privateKeyPwEnvVar,
fileReader,
)
if err != nil {
return err
}
Expand Down
5 changes: 4 additions & 1 deletion internal/certinfo/certinfo_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ func (c *CertinfoConfig) PrintData() {
),
)

rootCerts, err := GetCertsFromBundle(c.CACertsFilePath)
rootCerts, err := GetCertsFromBundle(
c.CACertsFilePath,
inputReader,
)
if err != nil {
fmt.Printf("unable for read Root certificates from %s: %s", c.CACertsFilePath, err)

Expand Down
69 changes: 69 additions & 0 deletions internal/certinfo/certinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package certinfo

import (
"fmt"
"testing"

"github.com/alecthomas/assert/v2"
"github.com/stretchr/testify/require"
)

func TestNewCertinfoConfig(t *testing.T) {
t.Run("NewCertinfoConfig", func(t *testing.T) {
t.Parallel()

cc, err := NewCertinfoConfig()
require.NoError(t, err)

require.NotNil(t, cc.CACertsPool)
})
}

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 TestCertinfo_SetCaPoolFromFile(t *testing.T) {
t.Run("FileReadErrors", func(t *testing.T) {
t.Parallel()

cc, err := NewCertinfoConfig()
require.NoError(t, err)

errEmpty := cc.SetCaPoolFromFile(emptyString, inputReader)
require.NoError(t, errEmpty, "error emptyString")

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",
)

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",
)

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",
)

// TODO: complete, compare struct data with input
errRSACACert := cc.SetCaPoolFromFile(RSACaCertFile, inputReader)
require.NoError(t, errRSACACert, "error generated RSACaCertFile")
})
}
Loading
Loading