From 9da6d1675b4098f60cb1d2d24d6856c59b2305ed Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 10:51:53 +0100 Subject: [PATCH 01/23] refactor: add Reader interface for file and password read --- cmd/certinfo.go | 6 +- cmd/requests.go | 2 +- cmd/root.go | 2 + internal/certinfo/certinfo.go | 56 ++++++++++--- internal/certinfo/certinfo_handlers.go | 5 +- internal/certinfo/common_handlers.go | 107 +++++++------------------ internal/requests/requests.go | 7 +- internal/requests/requests_test.go | 7 +- 8 files changed, 93 insertions(+), 99 deletions(-) diff --git a/cmd/certinfo.go b/cmd/certinfo.go index 3a6f312..009e6eb 100644 --- a/cmd/certinfo.go +++ b/cmd/certinfo.go @@ -71,11 +71,11 @@ 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) } @@ -83,7 +83,7 @@ Examples: 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) } diff --git a/cmd/requests.go b/cmd/requests.go index 52e1749..2422317 100644 --- a/cmd/requests.go +++ b/cmd/requests.go @@ -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) } diff --git a/cmd/root.go b/cmd/root.go index 4276a4a..f552bc7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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 ( @@ -43,6 +44,7 @@ var ( caBundlePath string certBundlePath string keyFilePath string + fileReader certinfo.InputReader ) var rootCmd = &cobra.Command{ diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index 530dfbf..eb52853 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -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 { @@ -32,15 +36,38 @@ type CertinfoConfig struct { TLSInsecure bool } +type ( + Reader interface { + ReadFile(name string) ([]byte, error) + ReadPassword(fd int) ([]byte, error) + } + + InputReader struct { + readError error + } +) + var ( - // TODO: remove - // certsBundle []*x509.Certificate - // privKey any - // tlsEndpoint string TlsServerName string TlsInsecure bool + inputReader InputReader ) +func (ir InputReader) ReadFile(name string) ([]byte, error) { + ir.readError = nil + + file, err := os.ReadFile(name) + if err != nil { + return nil, ir.readError + } + + return file, nil +} + +func (ir InputReader) ReadPassword(fd int) ([]byte, error) { + return term.ReadPassword(fd) +} + func NewCertinfoConfig() (*CertinfoConfig, error) { defaultCertPool, err := x509.SystemCertPool() if err != nil { @@ -54,9 +81,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 } @@ -68,9 +98,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 } @@ -82,9 +112,9 @@ 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, fileReader) if err != nil { return err } diff --git a/internal/certinfo/certinfo_handlers.go b/internal/certinfo/certinfo_handlers.go index 0a70141..34cd217 100644 --- a/internal/certinfo/certinfo_handlers.go +++ b/internal/certinfo/certinfo_handlers.go @@ -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) diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index b76eda4..c33edfe 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -13,7 +13,6 @@ import ( "time" "github.com/youmark/pkcs8" - "golang.org/x/term" ) func PrintCertInfo(cert *x509.Certificate, depth int) { @@ -34,55 +33,6 @@ func PrintCertInfo(cert *x509.Certificate, depth int) { fmt.Println() } -func printKeyInfo(privKey crypto.PrivateKey) { - fmt.Println("----- Private Key Info -----") - - switch k := privKey.(type) { - case *rsa.PrivateKey: - fmt.Println("Type: RSA") - fmt.Printf("Key Size: %d bits\n", k.N.BitLen()) - case *ecdsa.PrivateKey: - fmt.Println("Type: ECDSA") - fmt.Printf("Curve: %s\n", k.Curve.Params().Name) - case ed25519.PrivateKey: - fmt.Println("Type: Ed25519") - fmt.Printf("Key Size: %d bytes\n", len(k)) - default: - fmt.Println("Unknown key type") - } - - fmt.Println() -} - -// Check if the first Certificate from a slice has been created with the -// PrivateKey passed as argument. -func certsFromPrivateKey(c []*x509.Certificate, key crypto.PrivateKey) (bool, error) { - if len(c) == 0 { - return false, errors.New("empty Certificate slice provided") - } - - match := false - - switch pub := c[0].PublicKey.(type) { - case *rsa.PublicKey: - if k, ok := key.(*rsa.PrivateKey); ok && k.PublicKey.N.Cmp(pub.N) == 0 && k.PublicKey.E == pub.E { - match = true - } - case *ecdsa.PublicKey: - if k, ok := key.(*ecdsa.PrivateKey); ok && k.PublicKey.X.Cmp(pub.X) == 0 && k.PublicKey.Y.Cmp(pub.Y) == 0 { - match = true - } - case ed25519.PublicKey: - if k, ok := key.(ed25519.PrivateKey); ok && k.Public().(ed25519.PublicKey).Equal(pub) { - match = true - } - default: - return false, errors.New("unsupported public key type in certificate") - } - - return match, nil -} - // Check if the PublicKey of a Certificate matches the PrivateKey. func certMatchPrivateKey(cert *x509.Certificate, key crypto.PrivateKey) (bool, error) { if cert == nil { @@ -115,35 +65,43 @@ func certMatchPrivateKey(cert *x509.Certificate, key crypto.PrivateKey) (bool, e return match, nil } -// TODO: add comment explaining why returning an empty pool in case of empty string -func GetRootCertsFromFile(caBundlePath string) (*x509.CertPool, error) { - rootCAPool := x509.NewCertPool() +func GetRootCertsFromFile(caBundlePath string, fileReader Reader) (*x509.CertPool, error) { + if caBundlePath == emptyString { + return nil, errors.New("empty string provided as caBundlePath") + } - certsFromFile, err := os.ReadFile(caBundlePath) + certsFromFile, err := fileReader.ReadFile(caBundlePath) if err != nil { return nil, fmt.Errorf("failed to read CA bundle file: %w", err) } + rootCAPool := x509.NewCertPool() if ok := rootCAPool.AppendCertsFromPEM(certsFromFile); !ok { - fmt.Println("Certs from file not appended, using system certs only") + return nil, errors.New("unable to create CertPool from file") } return rootCAPool, nil } func GetRootCertsFromString(caBundleString string) (*x509.CertPool, error) { + if caBundleString == emptyString { + return nil, errors.New("empty string provided as caBundleString") + } + rootCAPool := x509.NewCertPool() - if caBundleString != "" { - if ok := rootCAPool.AppendCertsFromPEM([]byte(caBundleString)); !ok { - return nil, errors.New("no valid certs in caBundle config string") - } + if ok := rootCAPool.AppendCertsFromPEM([]byte(caBundleString)); !ok { + return nil, errors.New("no valid certs in caBundle config string") } return rootCAPool, nil } -func GetCertsFromBundle(certBundlePath string) ([]*x509.Certificate, error) { - certPEM, err := os.ReadFile(certBundlePath) +func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certificate, error) { + if certBundlePath == emptyString { + return nil, errors.New("empty string provided as caBundlePath") + } + + certPEM, err := fileReader.ReadFile(certBundlePath) if err != nil { return nil, fmt.Errorf("error reading certificate file: %w", err) } @@ -180,15 +138,6 @@ func GetCertsFromBundle(certBundlePath string) ([]*x509.Certificate, error) { return certs, nil } -func readFile(path string) ([]byte, error) { - bytes, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("error reading file: %w", err) - } - - return bytes, nil -} - // IsPrivateKeyEncrypted checks if the given PEM key is encrypted. // It returns true if encrypted, false otherwise, and an error if decoding fails. func IsPrivateKeyEncrypted(key []byte) (bool, error) { @@ -208,7 +157,7 @@ func IsPrivateKeyEncrypted(key []byte) (bool, error) { } } -func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string) ([]byte, error) { +func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string, pwReader Reader) ([]byte, error) { if !isEncrypted { return nil, nil } @@ -220,7 +169,7 @@ func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string) ([]byte, error) { fmt.Print("Private key is encrypted, please enter passphrase: ") - pw, trErr := term.ReadPassword(int(os.Stdin.Fd())) + pw, trErr := pwReader.ReadPassword(int(os.Stdin.Fd())) fmt.Println() @@ -246,7 +195,7 @@ func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string) ([]byte, error) { // // The function returns a descriptive error if the PEM cannot be decoded, decryption/parsing fails, // or the key format is unsupported. -func ParsePrivateKey(keyPEM []byte, pwEnvKey string) (crypto.PrivateKey, error) { +func ParsePrivateKey(keyPEM []byte, pwEnvKey string, pwReader Reader) (crypto.PrivateKey, error) { keyBlock, _ := pem.Decode(keyPEM) if keyBlock == nil { return nil, errors.New("failed to decode PEM") @@ -254,7 +203,7 @@ func ParsePrivateKey(keyPEM []byte, pwEnvKey string) (crypto.PrivateKey, error) isEncrypted, _ := IsPrivateKeyEncrypted(keyPEM) - pass, err := getPassphraseIfNeeded(isEncrypted, pwEnvKey) + pass, err := getPassphraseIfNeeded(isEncrypted, pwEnvKey, pwReader) if err != nil { return nil, err } @@ -294,13 +243,17 @@ func ParsePrivateKey(keyPEM []byte, pwEnvKey string) (crypto.PrivateKey, error) return nil, errors.New("unsupported key format or invalid password") } -func GetKeyFromFile(keyFilePath string) (crypto.PrivateKey, error) { - keyPEM, err := readFile(keyFilePath) +func GetKeyFromFile(keyFilePath string, inputReader Reader) (crypto.PrivateKey, error) { + if keyFilePath == emptyString { + return nil, errors.New("empty string provided as keyFilePath") + } + + keyPEM, err := inputReader.ReadFile(keyFilePath) if err != nil { return nil, err } - key, err := ParsePrivateKey(keyPEM, privateKeyPwEnvVar) + key, err := ParsePrivateKey(keyPEM, privateKeyPwEnvVar, inputReader) if err != nil { return nil, err } diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 7cb3972..966324b 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -165,9 +165,12 @@ func (r *RequestsMetaConfig) SetCaPoolFromYAML(s string) error { return nil } -func (r *RequestsMetaConfig) SetCaPoolFromFile(filePath string) error { +func (r *RequestsMetaConfig) SetCaPoolFromFile(filePath string, fileReader certinfo.Reader) error { if filePath != "" { - caCertsPool, err := certinfo.GetRootCertsFromFile(filePath) + caCertsPool, err := certinfo.GetRootCertsFromFile( + filePath, + fileReader, + ) if err != nil { return err } diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 95630ad..ba96917 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -15,6 +15,7 @@ import ( "github.com/pires/go-proxyproto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/xenos76/https-wrench/internal/certinfo" ) func TestNewRequestsMetaConfig(t *testing.T) { @@ -26,7 +27,7 @@ func TestNewRequestsMetaConfig(t *testing.T) { require.Error(t, err, "error when calling NewRequestsMetaConfig()") } - assert.NoError(t, err) + require.NoError(t, err) var i any = rmc @@ -147,8 +148,10 @@ func TestRequestsMetaConfig_SetCaPoolFromFile(t *testing.T) { t.Run(testname, func(t *testing.T) { t.Parallel() + var fr certinfo.InputReader + rmc, _ := NewRequestsMetaConfig() - err := rmc.SetCaPoolFromFile(tt.certFile) + err := rmc.SetCaPoolFromFile(tt.certFile, fr) require.NoError(t, err) var pool any = rmc.CACertsPool From 119d30059256e97a6edd8dbc2a6ca95b166a3550 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 10:53:04 +0100 Subject: [PATCH 02/23] ci: add sample keys and certs for testing --- internal/certinfo/testdata/README.md | 164 ++++++++++++++++++ internal/certinfo/testdata/ecdsa-crt.pem | 12 ++ .../ecdsa-encrypted-broken-private-key.pem | 8 + .../testdata/ecdsa-encrypted-private-key.pem | 8 + .../testdata/ecdsa-plaintext-private-key.pem | 5 + internal/certinfo/testdata/ed25519-crt.pem | 10 ++ .../ed25519-encrypted-broken-private-key.pem | 6 + .../ed25519-encrypted-private-key.pem | 6 + .../ed25519-plaintext-private-key.pem | 3 + internal/certinfo/testdata/rsa-pkcs1-crt.pem | 15 ++ internal/certinfo/testdata/rsa-pkcs1-csr.pem | 11 ++ ...rsa-pkcs1-encrypted-broken-private-key.pem | 18 ++ .../rsa-pkcs1-encrypted-private-key.pem | 18 ++ .../rsa-pkcs1-plaintext-private-key.pem | 15 ++ internal/certinfo/testdata/rsa-pkcs8-crt.pem | 32 ++++ internal/certinfo/testdata/rsa-pkcs8-csr.pem | 27 +++ ...rsa-pkcs8-encrypted-broken-private-key.pem | 54 ++++++ .../rsa-pkcs8-encrypted-private-key.pem | 54 ++++++ .../rsa-pkcs8-plaintext-private-key.pem | 52 ++++++ internal/certinfo/testdata/sample-text.txt | 3 + 20 files changed, 521 insertions(+) create mode 100644 internal/certinfo/testdata/README.md create mode 100644 internal/certinfo/testdata/ecdsa-crt.pem create mode 100644 internal/certinfo/testdata/ecdsa-encrypted-broken-private-key.pem create mode 100644 internal/certinfo/testdata/ecdsa-encrypted-private-key.pem create mode 100644 internal/certinfo/testdata/ecdsa-plaintext-private-key.pem create mode 100644 internal/certinfo/testdata/ed25519-crt.pem create mode 100644 internal/certinfo/testdata/ed25519-encrypted-broken-private-key.pem create mode 100644 internal/certinfo/testdata/ed25519-encrypted-private-key.pem create mode 100644 internal/certinfo/testdata/ed25519-plaintext-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs1-crt.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs1-csr.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs1-encrypted-broken-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs1-encrypted-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs1-plaintext-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs8-crt.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs8-csr.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs8-encrypted-broken-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs8-encrypted-private-key.pem create mode 100644 internal/certinfo/testdata/rsa-pkcs8-plaintext-private-key.pem create mode 100644 internal/certinfo/testdata/sample-text.txt diff --git a/internal/certinfo/testdata/README.md b/internal/certinfo/testdata/README.md new file mode 100644 index 0000000..1aa2acf --- /dev/null +++ b/internal/certinfo/testdata/README.md @@ -0,0 +1,164 @@ +# Certinfo testdata + +Sample private keys generated with Openssl. Plaintext and encrypted versions. +*Warning*: the keys stored in this folder are meant to be used for testing only. + +## Create sample RSA private keys + +### PKCS1 + +Generating PKCS1 RSA private key with Openssl requires version 1.1.1. +Get it with Nix: + +```shell +> export NIXPKGS_ALLOW_INSECURE=1 + +> nix-shell -p openssl_1_1 + +> openssl version +OpenSSL 1.1.1w 11 Sep 2023 +``` + +Create an encrypted key: + +```shell +> openssl genrsa -aes128 -out rsa-pkcs1-encrypted-private-key.pem 1024 +Generating RSA private key, 1024 bit long modulus (2 primes) +......................................+++++ +............+++++ +e is 65537 (0x010001) +Enter pass phrase for rsa-pkcs1-encrypted-private-key.pem: +Verifying - Enter pass phrase for rsa-pkcs1-encrypted-private-key.pem: + +> head -n 6 rsa-pkcs1-encrypted-private-key.pem +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,314A1EF1E544F10E9741F8A9C57384C8 + +8CpkWf5vuUxv15p6c7LJPgNO2gc1M3K+1ncRmJOblOGrpjv/Z5P1kE5wjEMIl/8Q +3BcapXsV+yuAnA+eBglHKBIrUf3Em6KxjjlcHP95qKSitvj7iOXtGBTJtkkswgR5 +``` + +Decrypt the key: + +```shell +> openssl rsa -in rsa-pkcs1-encrypted-private-key.pem -out rsa-pkcs1-plaintext-private-key.pem +Enter pass phrase for rsa-pkcs1-encrypted-private-key.pem: +writing RSA key +``` + +### PKCS8 + +Recent versions of Openssl create private keys in PKCS8 format: + +```shell +> openssl version +OpenSSL 3.6.0 1 Oct 2025 (Library: OpenSSL 3.6.0 1 Oct 2025 +``` + +Create an encrypted key: + +```shell +> openssl genrsa -aes256 -out rsa-pkcs8-encrypted-private-key.pem 4096 +Enter PEM pass phrase: +Verifying - Enter PEM pass phrase: + +> head -n 3 rsa-pkcs8-encrypted-private-key.pem +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIJtTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ188U9zxSGVXQrj4+ +8z3z1QICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDutok8Bf7Wu5Pc6 +``` + +Decrypt the key: + +```shell +> openssl rsa -in rsa-pkcs8-encrypted-private-key.pem -out rsa-pkcs8-plaintext-private-key.pem +Enter pass phrase for rsa-pkcs8-encrypted-private-key.pem: +writing RSA key + +> head -n 3 rsa-pkcs8-plaintext-private-key.pem +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC2O/PumNzqJcUd +4I0SBD0WP/AsCtDZGa6qF7YuqjO3hXx8jx66KRUQLFccgXRwOgmZ3Jj2hTvVUpbe +``` +## Create sample RSA certificates + +Create cert with PKCS1 key: + +```shell +> openssl req -new -key rsa-pkcs1-plaintext-private-key.pem -out rsa-pkcs1-csr.pem +[...] +> openssl x509 -req -days 3650 -in rsa-pkcs1-csr.pem -signkey rsa-pkcs1-plaintext-private-key.pem -out rsa-pkcs1-crt.pem +``` + +Create cert with PKCS8 key: + +```shell +> openssl req -new -key rsa-pkcs8-plaintext-private-key.pem -out rsa-pkcs8-csr.pem +[...] +> openssl x509 -req -days 3650 -in rsa-pkcs8-csr.pem -signkey rsa-pkcs8-plaintext-private-key.pem -out rsa-pkcs8-crt.pem +Certificate request self-signature ok +subject=C=DE, ST=Some-State, L=Berlin, O=example Ltd, CN=example.com +``` + +## Create sample ECDSA private keys + +Create the plaintext key: + +```shell + > openssl ecparam -name prime256v1 -genkey -noout -out ecdsa-plaintext-private-key.pem + +> head -n 2 ecdsa-plaintext-private-key.pem +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILVvywgqKFoKYCDNLNehJSbvwxCdtDr1UT+QID0hxqa+oAoGCCqGSM49 +``` + +Encrypt the key: + +```shell +> openssl ec -in ecdsa-plaintext-private-key.pem -out ecdsa-encrypted-private-key.pem -aes256 -passout pass:testpassword + +> head -n 6 ecdsa-encrypted-private-key.pem +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B1D5B0AFFB8F76B80B16373F8D81F9C3 + +1+K+hCOmodqpbb5s+GlWnW9J5VlnaR4gHyD/WKmIy5xo8iu/0OTHX4FjOc0TpMqi +UqHGMOYI078StHO7kexCwUC26QaU61RVx1P6AQX21ErSfJaSpO/48fkI+/3mgAK+ +``` + +## Create sample ECDSA certificate + +```shell +> openssl req -new -x509 -key ecdsa-plaintext-private-key.pem -days 3650 -out ecdsa-crt.pem -subj "/CN=example.com/O=Example Org" +``` + +## Create sample ED25519 private keys + +Create the plaintext key: + +```shell +> openssl genpkey -algorithm Ed25519 -out ed25519-plaintext-private-key.pem + +> head -n2 ed25519-plaintext-private-key.pem +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIEGYgM/XLll7BGmu+g7BdEOD21o+B7w/lZ6YbMOAiJ2s +``` + +Encrypt the key: + +```shell +> openssl pkey -in ed25519-plaintext-private-key.pem -out ed25519-encrypted-private-key.pem -aes256 -passout pass:testpassword + +> head -n 3 ed25519-encrypted-private-key.pem +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbN/Oyvx9FWCW4Cq/Y +Ea20AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQaUVczU8lT61K28bk +``` +## Create sample ED25519 certificate + +```shell +> openssl req -new -x509 -key ed25519-plaintext-private-key.pem -days 3650 -out ed25519-crt.pem -subj "/CN=example.com/O=Example Org" +``` + + diff --git a/internal/certinfo/testdata/ecdsa-crt.pem b/internal/certinfo/testdata/ecdsa-crt.pem new file mode 100644 index 0000000..01ba59e --- /dev/null +++ b/internal/certinfo/testdata/ecdsa-crt.pem @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBrjCCAVOgAwIBAgIUXbvY9v584PKSjfALJUnGkY6p7jMwCgYIKoZIzj0EAwIw +LDEUMBIGA1UEAwwLZXhhbXBsZS5jb20xFDASBgNVBAoMC0V4YW1wbGUgT3JnMB4X +DTI1MTIxMjIyMTYwNFoXDTM1MTIxMDIyMTYwNFowLDEUMBIGA1UEAwwLZXhhbXBs +ZS5jb20xFDASBgNVBAoMC0V4YW1wbGUgT3JnMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEBMp/9bvORyapawPVTX1IXmhXVWktoizPVi3Ft/G+Lz2jSx0UzYU5NgGQ +oK5OWM9UNWxzxl6aOv4rrw9aLLNxw6NTMFEwHQYDVR0OBBYEFKe7Tj/806/uNFWi +i3xxLzw9+mOKMB8GA1UdIwQYMBaAFKe7Tj/806/uNFWii3xxLzw9+mOKMA8GA1Ud +EwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhALqqkAUwHddIEa7WUxxa3vEV +qz7/Ukn++pwwebgp1cixAiEA8lvIuiliqGnGkNdTPt9F/dK9xp/8FgfgWuHJvzLu +Nz0= +-----END CERTIFICATE----- diff --git a/internal/certinfo/testdata/ecdsa-encrypted-broken-private-key.pem b/internal/certinfo/testdata/ecdsa-encrypted-broken-private-key.pem new file mode 100644 index 0000000..f34b040 --- /dev/null +++ b/internal/certinfo/testdata/ecdsa-encrypted-broken-private-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B1D5B0AFFB8F76B80B16373F8D81F9C3 + +1+K+hCOmodqpbb5s+GlWnW9J5VlnaR4gHyD/WKmIy5xo8iu/0OTHX4FjOc0AAAAA +UqHGMOYI078StHO7kexCwUC26QaU61RVx1P6AQX21ErSfJaSpO/48fkI+/3BBBBB +p6P4gXxkEK9JmbxlvFp0Mk08Pz5PGfHZtnWvZWmaL1I= +-----END EC PRIVATE KEY----- diff --git a/internal/certinfo/testdata/ecdsa-encrypted-private-key.pem b/internal/certinfo/testdata/ecdsa-encrypted-private-key.pem new file mode 100644 index 0000000..fb963cf --- /dev/null +++ b/internal/certinfo/testdata/ecdsa-encrypted-private-key.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,B1D5B0AFFB8F76B80B16373F8D81F9C3 + +1+K+hCOmodqpbb5s+GlWnW9J5VlnaR4gHyD/WKmIy5xo8iu/0OTHX4FjOc0TpMqi +UqHGMOYI078StHO7kexCwUC26QaU61RVx1P6AQX21ErSfJaSpO/48fkI+/3mgAK+ +p6P4gXxkEK9JmbxlvFp0Mk08Pz5PGfHZtnWvZWmaL1I= +-----END EC PRIVATE KEY----- diff --git a/internal/certinfo/testdata/ecdsa-plaintext-private-key.pem b/internal/certinfo/testdata/ecdsa-plaintext-private-key.pem new file mode 100644 index 0000000..abcb09b --- /dev/null +++ b/internal/certinfo/testdata/ecdsa-plaintext-private-key.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEILVvywgqKFoKYCDNLNehJSbvwxCdtDr1UT+QID0hxqa+oAoGCCqGSM49 +AwEHoUQDQgAEBMp/9bvORyapawPVTX1IXmhXVWktoizPVi3Ft/G+Lz2jSx0UzYU5 +NgGQoK5OWM9UNWxzxl6aOv4rrw9aLLNxww== +-----END EC PRIVATE KEY----- diff --git a/internal/certinfo/testdata/ed25519-crt.pem b/internal/certinfo/testdata/ed25519-crt.pem new file mode 100644 index 0000000..a7bce4a --- /dev/null +++ b/internal/certinfo/testdata/ed25519-crt.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBbTCCAR+gAwIBAgIUB0RH50OU9Xe+hyfmfQtiUYUZD+wwBQYDK2VwMCwxFDAS +BgNVBAMMC2V4YW1wbGUuY29tMRQwEgYDVQQKDAtFeGFtcGxlIE9yZzAeFw0yNTEy +MTIyMjEzNTlaFw0zNTEyMTAyMjEzNTlaMCwxFDASBgNVBAMMC2V4YW1wbGUuY29t +MRQwEgYDVQQKDAtFeGFtcGxlIE9yZzAqMAUGAytlcAMhAERFtN66Tv+wIlppswOd +L7tYHDXCXuUJlX6ANhQdPvToo1MwUTAdBgNVHQ4EFgQUCrQ53PydhesCRqI7xY+t +a/UmTmYwHwYDVR0jBBgwFoAUCrQ53PydhesCRqI7xY+ta/UmTmYwDwYDVR0TAQH/ +BAUwAwEB/zAFBgMrZXADQQBb28crB8snKhxtWG7jQRX8sPMYYv9gHIFReFVDetYs +hDWfC1miojGTfnEvXOCGOxwNeg24iUwmvocwvsfzeFQM +-----END CERTIFICATE----- diff --git a/internal/certinfo/testdata/ed25519-encrypted-broken-private-key.pem b/internal/certinfo/testdata/ed25519-encrypted-broken-private-key.pem new file mode 100644 index 0000000..bf85749 --- /dev/null +++ b/internal/certinfo/testdata/ed25519-encrypted-broken-private-key.pem @@ -0,0 +1,6 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbN/Oyvx9FWCAAAAAA +Ea20AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQaUVczU8lT6BBBBBB +G8DjLQRAt64Z296HjlVVjJ8zuK5KT6nbyMRXMW05Pj6KylRu94hfptON6sO9grK0 +ZqfJyeXPWtE1PyfH6F/+GK7o/BTJ+A== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/internal/certinfo/testdata/ed25519-encrypted-private-key.pem b/internal/certinfo/testdata/ed25519-encrypted-private-key.pem new file mode 100644 index 0000000..67adb86 --- /dev/null +++ b/internal/certinfo/testdata/ed25519-encrypted-private-key.pem @@ -0,0 +1,6 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbN/Oyvx9FWCW4Cq/Y +Ea20AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQaUVczU8lT61K28bk +G8DjLQRAt64Z296HjlVVjJ8zuK5KT6nbyMRXMW05Pj6KylRu94hfptON6sO9grK0 +ZqfJyeXPWtE1PyfH6F/+GK7o/BTJ+A== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/internal/certinfo/testdata/ed25519-plaintext-private-key.pem b/internal/certinfo/testdata/ed25519-plaintext-private-key.pem new file mode 100644 index 0000000..5a8d01e --- /dev/null +++ b/internal/certinfo/testdata/ed25519-plaintext-private-key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIEGYgM/XLll7BGmu+g7BdEOD21o+B7w/lZ6YbMOAiJ2s +-----END PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs1-crt.pem b/internal/certinfo/testdata/rsa-pkcs1-crt.pem new file mode 100644 index 0000000..3abe1de --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs1-crt.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICaDCCAdGgAwIBAgIUXlcOM7E5G5gSEPCx11zteR0EWxYwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM +BkJlcmxpbjEUMBIGA1UECgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMB4XDTI1MTIxMjIyMDAyOFoXDTM1MTIxMDIyMDAyOFowXzELMAkGA1UEBhMC +REUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcMBkJlcmxpbjEUMBIGA1UE +CgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDZzK3SoBWgwCU/SP4efOJsQM58PVH+wPIIpqx9Dmr6 +F2Yua727MrjcZQBcaz7jCV4lW12X1AhsonNfEe09x6HfW4Y//pgwyabu03nbeQUZ +8QuosbLEhZuP/SKavWnAtk31kV2A6aBuTg6R4aMdx55nhtPYpalWnkCknyNK3+1c +/wIDAQABoyEwHzAdBgNVHQ4EFgQUKhiijW26e0iSY5YzJ07fQGA92ywwDQYJKoZI +hvcNAQELBQADgYEAiCc+n4FqW3JkZgNOgURFpP+vvL73OxZ/5fxFgbLL5kYf0Jek +ag9EGBW/A4I9EPcNcollD07G/5dExazolNbOtBL1e1qvhxH6icYWPysf29gZ9QlH +MPdz/MigbH7JaxdDJ9JHFJk5THF4PN4axs3QMWg7hq5Gs6zNhpR7ykoFZ5s= +-----END CERTIFICATE----- diff --git a/internal/certinfo/testdata/rsa-pkcs1-csr.pem b/internal/certinfo/testdata/rsa-pkcs1-csr.pem new file mode 100644 index 0000000..5144989 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs1-csr.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBnzCCAQgCAQAwXzELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUx +DzANBgNVBAcMBkJlcmxpbjEUMBIGA1UECgwLZXhhbXBsZSBMdGQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDZzK3SoBWg +wCU/SP4efOJsQM58PVH+wPIIpqx9Dmr6F2Yua727MrjcZQBcaz7jCV4lW12X1Ahs +onNfEe09x6HfW4Y//pgwyabu03nbeQUZ8QuosbLEhZuP/SKavWnAtk31kV2A6aBu +Tg6R4aMdx55nhtPYpalWnkCknyNK3+1c/wIDAQABoAAwDQYJKoZIhvcNAQELBQAD +gYEAleHJ57a0MVUxGEq6Y+9afGUtGCLsxBoq7rpgvj9hITyx6YfVN+ZrSewwJb/S +3rfviVIu6nhHWEMde/3gE303Cm1Cs8UvXOorMz9bH42O4dBawkMaw48JMcqQHgiA +NaKDWL1HQ5DF1NxQ9CwMdlrp1w3cP3S//yvSHjKLffjlLIo= +-----END CERTIFICATE REQUEST----- diff --git a/internal/certinfo/testdata/rsa-pkcs1-encrypted-broken-private-key.pem b/internal/certinfo/testdata/rsa-pkcs1-encrypted-broken-private-key.pem new file mode 100644 index 0000000..4358e64 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs1-encrypted-broken-private-key.pem @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,314A1EF1E544F10E9741F8A9C57384C8 + +8CpkWf5vuUxv15p6c7LJPgNO2gc1M3K+1ncRmJOblOGrpjv/Z5P1kE5wjEAAAAAA +3BcapXsV+yuAnA+eBglHKBIrUf3Em6KxjjlcHP95qKSitvj7iOXtGBTJtkBBBBBB +VOwxtzEntGTzGBJ+mO1C0PEODMnmQk5AffBcY+k6KscOXpq7b65/TG5bDACCCCCC +mdbpblX3o028vWfcTXzhzl25f6nsULwLH2tiRe9jcKEZlh1EJUVkF8+kEab8pKPu +JnIDdZY6CLjydhjMWZW96ZuHjzJ0j205uagJdWC0pGCD3Cq21J9mhBRZCDe4xVso +OHI5S1LzAgqdBJzm9C5yq5myxx/9u+BquJztCtY6yTgCGWIDM8dPSumt0Bh6CIe8 +hMJkLlprEx0RM7i6izX41o6noOftqpnBeJ/CUJq+DwrTXEHQHlFi6XH9aEFca6mr +nNk8+hYoXK6bnXvxaiwJSVc0sjtugeeT/gJbHs9wEVdCfqyDhr8LiRzJP6wbsZHe +0gPueYYrWHZpb+HVec+Ynzc/6p6wUdDukHMhYDKoZmigYLlsoahHHnDeDWmqiOMA +WA/8CopxlYtOeuOfwdXDTNSUun493LYMpKaE5bzKkF1vNE4azd9WdFfT7iYGWukz +e8CpGvp/549ZJAJ5FPYubgZR+cLkkks0f0qj9pC3Hx3yH/j3uyAMzNQgMEU0+OHP +llocIa83BKTC79/P4HdaWcv15hSDn6kxe19CocjEMnANpePqG53JomqjJndfYesf +JIVJWuc448EhVljLbYzXFhDRJhgN1HDFs6pPNex9sFHfw0Bs6zFwViB9eV0HVd2z +-----END RSA PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs1-encrypted-private-key.pem b/internal/certinfo/testdata/rsa-pkcs1-encrypted-private-key.pem new file mode 100644 index 0000000..361a2c5 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs1-encrypted-private-key.pem @@ -0,0 +1,18 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,314A1EF1E544F10E9741F8A9C57384C8 + +8CpkWf5vuUxv15p6c7LJPgNO2gc1M3K+1ncRmJOblOGrpjv/Z5P1kE5wjEMIl/8Q +3BcapXsV+yuAnA+eBglHKBIrUf3Em6KxjjlcHP95qKSitvj7iOXtGBTJtkkswgR5 +VOwxtzEntGTzGBJ+mO1C0PEODMnmQk5AffBcY+k6KscOXpq7b65/TG5bDAYIBOtL +mdbpblX3o028vWfcTXzhzl25f6nsULwLH2tiRe9jcKEZlh1EJUVkF8+kEab8pKPu +JnIDdZY6CLjydhjMWZW96ZuHjzJ0j205uagJdWC0pGCD3Cq21J9mhBRZCDe4xVso +OHI5S1LzAgqdBJzm9C5yq5myxx/9u+BquJztCtY6yTgCGWIDM8dPSumt0Bh6CIe8 +hMJkLlprEx0RM7i6izX41o6noOftqpnBeJ/CUJq+DwrTXEHQHlFi6XH9aEFca6mr +nNk8+hYoXK6bnXvxaiwJSVc0sjtugeeT/gJbHs9wEVdCfqyDhr8LiRzJP6wbsZHe +0gPueYYrWHZpb+HVec+Ynzc/6p6wUdDukHMhYDKoZmigYLlsoahHHnDeDWmqiOMA +WA/8CopxlYtOeuOfwdXDTNSUun493LYMpKaE5bzKkF1vNE4azd9WdFfT7iYGWukz +e8CpGvp/549ZJAJ5FPYubgZR+cLkkks0f0qj9pC3Hx3yH/j3uyAMzNQgMEU0+OHP +llocIa83BKTC79/P4HdaWcv15hSDn6kxe19CocjEMnANpePqG53JomqjJndfYesf +JIVJWuc448EhVljLbYzXFhDRJhgN1HDFs6pPNex9sFHfw0Bs6zFwViB9eV0HVd2z +-----END RSA PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs1-plaintext-private-key.pem b/internal/certinfo/testdata/rsa-pkcs1-plaintext-private-key.pem new file mode 100644 index 0000000..833b2e7 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs1-plaintext-private-key.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDZzK3SoBWgwCU/SP4efOJsQM58PVH+wPIIpqx9Dmr6F2Yua727 +MrjcZQBcaz7jCV4lW12X1AhsonNfEe09x6HfW4Y//pgwyabu03nbeQUZ8QuosbLE +hZuP/SKavWnAtk31kV2A6aBuTg6R4aMdx55nhtPYpalWnkCknyNK3+1c/wIDAQAB +AoGAPVQLJiH5tRgl5NoS5FbaEw10lztVGV0PSixUqaojqWRpW2zvMgUoVLcEEQIJ +5ThAEFjX0+basGxV/FE58omCJLOI8DA2FDRtQJNEuM+ACxIF/XwpXBkHwNJxVVNM +8xwSp5e9o7YrqwMkFu2Egqvaa4sg/TQcR7YDPJLj1JQVEwECQQD73elt84kIsWVY +xyM+PEcmJWtP9G319zrEQXG8wvoNywaHuiXp66aeWbbrgSFUA/7/+P3UPDDZwGvR +kEGas66/AkEA3V+mpd04i87RvzWeLJ9yvVhjXLXC7SS8vBwQt5iQpye93l35Qc10 +1F6fu2e0sua+A6u2Q1plBXc5d5aKFxAhwQJAEtbIbQb4SwQp1ngetLsBf2LxIevg +hbNWW/OhNENJPNrgxCoyR8rujY2cVwUznvslibPwGH3gEYmJBwXJQm8KNQJAZWqU +phga97HgVYDy3e6OWkBMBQUgMBRMgsE4x2OVoX6LdXwH3SWLCF/xPaHdHrinBHd+ +II0hCsf8A9VrnfU+AQJBANgOOy8voL1iQ7eS+T8teXQdKRVTpy1fJ53sku72aBZo +hQLNuDtjwt37UStx8NznTBIxbp0Y8zs/JDBSdpQNo5M= +-----END RSA PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs8-crt.pem b/internal/certinfo/testdata/rsa-pkcs8-crt.pem new file mode 100644 index 0000000..d51a102 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-crt.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbTCCA1WgAwIBAgIUBzx2EsVl+ybPvE466xTKQunu37cwDQYJKoZIhvcNAQEL +BQAwXzELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcM +BkJlcmxpbjEUMBIGA1UECgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1wbGUu +Y29tMB4XDTI1MTIxMjIyMDcwOFoXDTM1MTIxMDIyMDcwOFowXzELMAkGA1UEBhMC +REUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcMBkJlcmxpbjEUMBIGA1UE +CgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAtjvz7pjc6iXFHeCNEgQ9Fj/wLArQ2Rmuqhe2 +Lqozt4V8fI8euikVECxXHIF0cDoJmdyY9oU71VKW3qDPS0NcB51rUi0iyfoTPXCy +Sig6tkAewbWc03doUifTLSxSmBz5cjdrZIKK8zkQBtdd3A/B7u/ewRXuRA567vN2 +2Gjc5EkrypJUrCWYeFlfcW1UzcbEMag4X3Z3YEmQXppz3EDsm72XZ+KHA3PytKv4 +rPM4IMtQkLMCIpvQ/NdbQXcwYorVo6dszCohqI2uc6hfQ0sbNEsvGX7/PakArNr8 +JkFif5DWS54tXzfzPz3HMbDFyjAURIguHBa6mBCq/tyTQvBNxNu+N+WERMqJjrKk +MQVVbzZLeEov0cINJipI4IC09CqaHa3LUBsvJQmTRoEZJDvtKDmS6qw+eprJIHgU +R5AJ4Is6CddLHUJnPKLoU3pZdsb7a3ks2d4PiHKuRaVJVs5495d/syfZYejB+C+r +9Uyj48p4VuHknJG5JmGtEc9w5pnWdWiGfPJJCSt5C5cZhjDqIkU/g+LrhYULLQRC +yFQ9oDLWo8zGf8xXYDKNutw/qfjBrCLGApNyqW1MibSQf1JNX3sVljDZaR5k20OW +Yd00wqtubq8rw2KgoKV3KOQa/Dkq6SIFg4krbys+DqnZ2XKtL+Bq1OpAu558A76S +zKjpKT8CAwEAAaMhMB8wHQYDVR0OBBYEFAjuAcgUUDpC4pmbUICqf9TYnsXEMA0G +CSqGSIb3DQEBCwUAA4ICAQAAyrqWKwonL6ZCeuzEjCizXRv0bFSZZBKvggX1z+KS +EDJppjA7vvzC0k9HuWat+qV69xkQ3u+BpnnPa0OSasqI6sQJIMt8Az7EVeqrS074 +qedulOIQzZnbH7NOht/EKso9Gz3iP7G3NKf1JsStc9FuSiWaiqsg83iLCAe1py/t +8KM2G/vCvNPYIZAR8RwaNGgBicaOQsPW9PuNphQ9i1tcF2L0pstImoCuCzY0guRt +Ku0syZweF8RYecnKxqSoWHL++vBWHHJ85O6RKjvSYWcCcaU6SkMlgTY7n7xvztrd +U1GKlBnijObU+lIfNv9cxufy7KE9X5qQmwBbDkNPYQSif1dkCzs/3sWQq0aSvpHX +XI/inejkNtInddchh2prdBLHn1yEilQ4Bow4H05ipsnFhB9W+154JpcYI8VN67xr +xMw4DAG2S1byUIfiBW8hU5AKQE9c56SSUdci6kaFVC4FRvd1Nsd3HU/e3mLdw6J9 +iIXRnj5TBQkX7WipZKc1NBUXSWa1NNPl8oUe1BwaYG9cqOWWNbeprLBiToqPMpts +0ENmMrxPXrl71akGpj5L7A8I+9W9VtQl5BGTy1fmscqVwlHxDh1iWwV1mLFrlSS0 +wlrSh0IajUFG0/M5lvVDH+LGg1z6tDA5KaD3YE4du7JBXFJNMI+kT0cCgNSWmows +IA== +-----END CERTIFICATE----- diff --git a/internal/certinfo/testdata/rsa-pkcs8-csr.pem b/internal/certinfo/testdata/rsa-pkcs8-csr.pem new file mode 100644 index 0000000..bd86ba4 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-csr.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEpDCCAowCAQAwXzELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUx +DzANBgNVBAcMBkJlcmxpbjEUMBIGA1UECgwLZXhhbXBsZSBMdGQxFDASBgNVBAMM +C2V4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtjvz +7pjc6iXFHeCNEgQ9Fj/wLArQ2Rmuqhe2Lqozt4V8fI8euikVECxXHIF0cDoJmdyY +9oU71VKW3qDPS0NcB51rUi0iyfoTPXCySig6tkAewbWc03doUifTLSxSmBz5cjdr +ZIKK8zkQBtdd3A/B7u/ewRXuRA567vN22Gjc5EkrypJUrCWYeFlfcW1UzcbEMag4 +X3Z3YEmQXppz3EDsm72XZ+KHA3PytKv4rPM4IMtQkLMCIpvQ/NdbQXcwYorVo6ds +zCohqI2uc6hfQ0sbNEsvGX7/PakArNr8JkFif5DWS54tXzfzPz3HMbDFyjAURIgu +HBa6mBCq/tyTQvBNxNu+N+WERMqJjrKkMQVVbzZLeEov0cINJipI4IC09CqaHa3L +UBsvJQmTRoEZJDvtKDmS6qw+eprJIHgUR5AJ4Is6CddLHUJnPKLoU3pZdsb7a3ks +2d4PiHKuRaVJVs5495d/syfZYejB+C+r9Uyj48p4VuHknJG5JmGtEc9w5pnWdWiG +fPJJCSt5C5cZhjDqIkU/g+LrhYULLQRCyFQ9oDLWo8zGf8xXYDKNutw/qfjBrCLG +ApNyqW1MibSQf1JNX3sVljDZaR5k20OWYd00wqtubq8rw2KgoKV3KOQa/Dkq6SIF +g4krbys+DqnZ2XKtL+Bq1OpAu558A76SzKjpKT8CAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4ICAQCvhY0/5sJ92H08kWpbOuxDXO9XZalV7wd4wkdb3BNTwcC6tOn8IArv +FjdwjwoS+2EH3sOQUW8xMg00wBOz7F8jDou2PEw2/K1Uq+h5SyGf2Lpp8ck1odLT +8/gUfmtidB3Qf+9nR5wNoz7xmJ2+VMLPwVzXUOpVvGJqk2WhUFAgSDMRwuB+2d5i +DVsZNbaVfvNQfOf+tXpebSAHRqqSUHUmmVKSrPt9nzQJO8/DoPml09d580MUyc9Z +2MHMJUpRnIX16EYHMxVurjJ6kr7enrWWVZl2uIyQ8nX8Fg5x41FPNpE2dXzB7bkT +pBXON5GmFUp3gGBZMVzPXNxY1wsZOro7h67aTDYjloUnxQhF2I9RWtTxZKNckqYm +UgBiE4TAuL9l9YGe/KlsotmdWaJM2Gz+DwwCTo6hISCldnKanvv3n0fdKHfiUfyV +WsYzT3rHfjglTu4Jr8RGlffA1L9/1xMFpMZkgV7vzKLAEzbyyfphu0EoHJOI8tkc +FtMacz49qXfGBxJjUx2Ezt6ybMRphYZcmHKqPOLypj8zHFbYNMvIBSkWQybAdyLf +Nuw6g0VV1VGIxHIfmsB05F7pSuUCwpwZ5nsGYZjeZQNyXQpr5KNbPUBBXa9271R8 +yRSHyLq7S6AReSGUC6GTw3Wgv9mvaeoIZRvrpFkscu28D95tDnX1vg== +-----END CERTIFICATE REQUEST----- diff --git a/internal/certinfo/testdata/rsa-pkcs8-encrypted-broken-private-key.pem b/internal/certinfo/testdata/rsa-pkcs8-encrypted-broken-private-key.pem new file mode 100644 index 0000000..2020549 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-encrypted-broken-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs8-encrypted-private-key.pem b/internal/certinfo/testdata/rsa-pkcs8-encrypted-private-key.pem new file mode 100644 index 0000000..78a6425 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-encrypted-private-key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/internal/certinfo/testdata/rsa-pkcs8-plaintext-private-key.pem b/internal/certinfo/testdata/rsa-pkcs8-plaintext-private-key.pem new file mode 100644 index 0000000..9d39448 --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-plaintext-private-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +OJ2kfHVduBB9rGzDHOjV7gVRdc1GaQ== +-----END PRIVATE KEY----- diff --git a/internal/certinfo/testdata/sample-text.txt b/internal/certinfo/testdata/sample-text.txt new file mode 100644 index 0000000..eec3d7e --- /dev/null +++ b/internal/certinfo/testdata/sample-text.txt @@ -0,0 +1,3 @@ +"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + +"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?" From 4497c6dd6a14d1cfcf839f4c76623f27de33d947 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 10:53:54 +0100 Subject: [PATCH 03/23] ci: add tests for certinfo and common handlers --- .gitignore | 3 +- .golangci.yml | 7 + devenv.nix | 1 + internal/certinfo/certinfo_test.go | 66 +++ internal/certinfo/common_handlers_test.go | 497 ++++++++++++++++++++++ internal/certinfo/main_test.go | 343 +++++++++++++++ 6 files changed, 916 insertions(+), 1 deletion(-) create mode 100644 internal/certinfo/certinfo_test.go create mode 100644 internal/certinfo/common_handlers_test.go create mode 100644 internal/certinfo/main_test.go diff --git a/.gitignore b/.gitignore index 7945ee9..b68eb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ .direnv dist tests -testdata +internal/requests/testdata +internal/certinfo/testdata/*Cert* manpages # ---> Go diff --git a/.golangci.yml b/.golangci.yml index 18996bf..8d56f8a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -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: diff --git a/devenv.nix b/devenv.nix index 35d2263..e953d06 100644 --- a/devenv.nix +++ b/devenv.nix @@ -40,6 +40,7 @@ in { ".gitignore" ".envrc" "internal/certinfo/common_handlers.go" + "internal/certinfo/testdata" "completions" ]; hooks = { diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go new file mode 100644 index 0000000..a53dbb4 --- /dev/null +++ b/internal/certinfo/certinfo_test.go @@ -0,0 +1,66 @@ +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(), + ) + + errNoExist := cc.SetCaPoolFromFile("testdata/not-exist", inputReader) + require.Error(t, errNoExist, "error file not-exist") + assert.Equal(t, + "unable to create CertPool from file", + errNoExist.Error(), + ) + + errWrongCert := cc.SetCaPoolFromFile(RSACaCertKeyFile, inputReader) + require.Error(t, errWrongCert, "error wrong cert") + assert.Equal(t, + "unable to create CertPool from file", + errWrongCert.Error(), + ) + + // TODO: complete, compare struct data with input + errRSACACert := cc.SetCaPoolFromFile(RSACaCertFile, inputReader) + require.NoError(t, errRSACACert, "error generated RSACaCertFile") + }) +} diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go new file mode 100644 index 0000000..557bbad --- /dev/null +++ b/internal/certinfo/common_handlers_test.go @@ -0,0 +1,497 @@ +package certinfo + +import ( + "crypto/x509" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" +) + +func TestCertinfo_GetRootCertsFromFile(t *testing.T) { + t.Run("FileReadErrors", func(t *testing.T) { + t.Parallel() + + _, errEmptyString := GetRootCertsFromFile( + emptyString, + inputReader, + ) + require.Error(t, errEmptyString) + assert.Equal(t, + "empty string provided as caBundlePath", + errEmptyString.Error(), + ) + + _, errNoRead := GetRootCertsFromFile( + unreadableFile, + mockErrReader, + ) + require.Error(t, errNoRead) + assert.Equal(t, + "failed to read CA bundle file: unable to read file testdata/unreadable-file.txt", + errNoRead.Error(), + ) + + _, errWrongFile := GetRootCertsFromFile( + RSACaCertKeyFile, + inputReader, + ) + require.Error(t, errWrongFile) + assert.Equal(t, + "unable to create CertPool from file", + errWrongFile.Error(), + ) + }) + + t.Run("CertImportValidation", func(t *testing.T) { + pool, errCaFile := GetRootCertsFromFile( + RSACaCertFile, + inputReader, + ) + require.NoError(t, errCaFile) + + if diff := cmp.Diff(RSACaCertPool, pool); diff != "" { + t.Errorf( + "RSACACertPool vs imported Cert Pool mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +func TestCertinfo_GetRootCertsFromString(t *testing.T) { + t.Run("ReadErrors", func(t *testing.T) { + t.Parallel() + + _, errEmptyString := GetRootCertsFromString(emptyString) + require.Error(t, errEmptyString) + assert.Equal(t, + "empty string provided as caBundleString", + errEmptyString.Error(), + ) + + _, errWrongString := GetRootCertsFromString("wrong string") + require.Error(t, errWrongString) + assert.Equal(t, + "no valid certs in caBundle config string", + errWrongString.Error()) + }) + + t.Run("CertImportValidation", func(t *testing.T) { + pool, errCaString := GetRootCertsFromString(RSACaCertPEMString) + require.NoError(t, errCaString) + + if diff := cmp.Diff(RSACaCertPool, pool); diff != "" { + t.Errorf( + "RSACACertPool vs imported Cert Pool mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +func TestCertinfo_GetCertsFromBundle(t *testing.T) { + t.Run("FileReadErrors", func(t *testing.T) { + t.Parallel() + + _, errEmptyString := GetCertsFromBundle( + emptyString, + inputReader, + ) + require.Error(t, errEmptyString) + assert.Equal(t, + "empty string provided as caBundlePath", + errEmptyString.Error(), + ) + + _, errNoRead := GetCertsFromBundle( + unreadableFile, + mockErrReader, + ) + require.Error(t, errNoRead) + assert.Equal(t, + "error reading certificate file: unable to read file testdata/unreadable-file.txt", + errNoRead.Error(), + ) + + _, errWrongFile := GetCertsFromBundle( + RSACaCertKeyFile, + inputReader, + ) + require.Error(t, errWrongFile) + assert.Equal(t, + "no valid certificates found in file "+RSACaCertKeyFile, + errWrongFile.Error(), + ) + }) + + t.Run("CertImportValidation", func(t *testing.T) { + gotCerts, errCaString := GetCertsFromBundle( + RSACaCertFile, + inputReader, + ) + require.NoError(t, errCaString) + + wantCerts := []*x509.Certificate{RSACaCertParent} + + if diff := cmp.Diff(wantCerts, gotCerts); diff != "" { + t.Errorf( + "GetCertsFromBundle certs mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +func TestCertinfo_GetKeyFromFile(t *testing.T) { + t.Run("FileReadErrors", func(t *testing.T) { + t.Parallel() + + _, errEmptyString := GetKeyFromFile( + emptyString, + inputReader, + ) + require.Error(t, errEmptyString) + assert.Equal(t, + "empty string provided as keyFilePath", + errEmptyString.Error(), + ) + + _, errNoRead := GetKeyFromFile( + unreadableFile, + mockErrReader, + ) + require.Error(t, errNoRead) + assert.Equal(t, + "unable to read file testdata/unreadable-file.txt", + errNoRead.Error(), + ) + + _, errWrongFile := GetKeyFromFile( + RSACaCertFile, + inputReader, + ) + require.Error(t, errWrongFile) + assert.Equal(t, + "unsupported key format or invalid password", + errWrongFile.Error(), + ) + }) + + t.Run("Plain RSA key import", func(t *testing.T) { + got, err := GetKeyFromFile( + RSACaCertKeyFile, + inputReader, + ) + require.NoError(t, err) + + if diff := cmp.Diff(RSACaCertKey, got); diff != "" { + t.Errorf( + "GetKeyFromFile key mismatch (-want +got):\n%s", + diff, + ) + } + }) + + t.Run("No PEM encoded text import", func(t *testing.T) { + _, err := GetKeyFromFile( + sampleTextFile, + inputReader, + ) + require.Error(t, err) + assert.EqualError(t, err, "failed to decode PEM") + }) + + t.Run("Plain RSA PKCS1 key import", func(t *testing.T) { + _, err := GetKeyFromFile( + RSASamplePKCS1PlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted RSA PKCS1 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + RSASamplePKCS1EncryptedPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted RSA PKCS1 key import with wrong password", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, "wrong password") + + _, err := GetKeyFromFile( + RSASamplePKCS1EncryptedPrivateKey, + inputReader, + ) + require.Error(t, err) + assert.EqualError(t, + err, + "PEM block decryption failed: x509: decryption password incorrect", + ) + }) + + t.Run("Encrypted broken RSA PKCS1 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + RSASamplePKCS1EncBrokenPrivateKey, + inputReader, + ) + require.Error(t, err) + require.Equal(t, + "unsupported key format or invalid password", + err.Error()) + }) + + t.Run("Plain RSA/4096 PKCS8 key import", func(t *testing.T) { + _, err := GetKeyFromFile( + RSASamplePKCS8PlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted RSA/4096 PKCS8 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + RSASamplePKCS8EncryptedPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted broken RSA/4096 PKCS8 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + RSASamplePKCS8EncBrokenPrivateKey, + inputReader, + ) + require.Error(t, err) + assert.Equal(t, + "PKCS8 decryption failed: pkcs8: incorrect password", + err.Error()) + }) + + t.Run("Plain ECDSA key import", func(t *testing.T) { + _, err := GetKeyFromFile( + ECDSASamplePlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted ECDSA key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + ECDSASampleEncryptedPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted broken ECDSA key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + ECDSASampleEncBrokenPrivateKey, + inputReader, + ) + require.Error(t, err) + assert.Equal(t, + "unsupported key format or invalid password", + err.Error()) + }) + + t.Run("Plain ED25519 key import", func(t *testing.T) { + _, err := GetKeyFromFile( + ED25519SamplePlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted ED25519 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + ED25519SampleEncryptedPrivateKey, + inputReader, + ) + require.NoError(t, err) + }) + + t.Run("Encrypted broken ED25519 key import", func(t *testing.T) { + t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) + + _, err := GetKeyFromFile( + ED25519SampleEncBrokenPrivateKey, + inputReader, + ) + require.Error(t, err) + assert.Equal(t, + "PKCS8 decryption failed: pkcs8: incorrect password", + err.Error()) + }) +} + +func TestCertinfo_IsPrivateKeyEncrypted(t *testing.T) { + t.Run("No PEM encoded file", func(t *testing.T) { + sampleText, err := inputReader.ReadFile(sampleTextFile) + require.NoError(t, err) + + _, err = IsPrivateKeyEncrypted(sampleText) + + require.Error(t, err) + assert.EqualError(t, err, "failed to decode PEM") + }) +} + +func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { + t.Run("pwEnvKey not set error", func(t *testing.T) { + _, err := getPassphraseIfNeeded( + true, + emptyString, + inputReader, + ) + require.Error(t, err) + assert.EqualError(t, + err, + "error reading passphrase: inappropriate ioctl for device", + ) + }) + + t.Run("pw read error", func(t *testing.T) { + _, err := getPassphraseIfNeeded( + true, + emptyString, + mockErrReader, + ) + require.Error(t, err) + assert.EqualError(t, + err, + "error reading passphrase: unable to read password", + ) + }) + + t.Run("pw read success", func(t *testing.T) { + pw, err := getPassphraseIfNeeded( + true, + privateKeyPwEnvVar, + mockInputReader, + ) + require.NoError(t, err) + assert.Equal(t, + []byte(samplePrivateKeyPassword), + pw, + ) + }) +} + +func TestCertinfo_certMatchPrivateKey(t *testing.T) { + t.Run("RSA PKCS8 match True", func(t *testing.T) { + match, err := certMatchPrivateKey( + RSACaCertParent, + RSACaCertKey, + ) + require.NoError(t, err) + assert.True(t, match, "matchTrue") + }) + + t.Run("RSA PKCS1 match True", func(t *testing.T) { + certs, err := GetCertsFromBundle( + RSASamplePKCS1Certificate, + inputReader, + ) + require.NoError(t, err) + + key, err := GetKeyFromFile( + RSASamplePKCS1PlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + + match, err := certMatchPrivateKey( + certs[0], + key, + ) + require.NoError(t, err) + assert.True(t, match) + }) + + t.Run("ECDSA match True", func(t *testing.T) { + certs, err := GetCertsFromBundle( + ECDSASampleCertificate, + inputReader, + ) + require.NoError(t, err) + + key, err := GetKeyFromFile( + ECDSASamplePlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + + match, err := certMatchPrivateKey( + certs[0], + key, + ) + require.NoError(t, err) + assert.True(t, match) + }) + + t.Run("ED25519 match True", func(t *testing.T) { + certs, err := GetCertsFromBundle( + ED25519SampleCertificate, + inputReader, + ) + require.NoError(t, err) + + key, err := GetKeyFromFile( + ED25519SamplePlaintextPrivateKey, + inputReader, + ) + require.NoError(t, err) + + match, err := certMatchPrivateKey( + certs[0], + key, + ) + require.NoError(t, err) + assert.True(t, match) + }) + + t.Run("matchFalse", func(t *testing.T) { + match, err := certMatchPrivateKey( + RSACaCertParent, + RSASampleCertKey, + ) + require.NoError(t, err) + assert.False(t, match) + }) + + t.Run("Cert nil False match", func(t *testing.T) { + match, err := certMatchPrivateKey( + nil, + RSASampleCertKey, + ) + require.NoError(t, err) + assert.False(t, match) + }) + + t.Run("Key nil False match", func(t *testing.T) { + match, err := certMatchPrivateKey( + RSACaCertParent, + nil, + ) + require.NoError(t, err) + assert.False(t, match) + }) +} diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go new file mode 100644 index 0000000..89c957a --- /dev/null +++ b/internal/certinfo/main_test.go @@ -0,0 +1,343 @@ +package certinfo + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net" + "os" + "testing" + "time" +) + +type ( + certificateTemplate struct { + cn string + isCA bool + dnsNames []string + ipAddresses []net.IP + key *rsa.PrivateKey + caKey *rsa.PrivateKey + parent *x509.Certificate + } + + MockErrReader struct { + readError error + } + + MockInputReader struct { + readError error + } +) + +var ( + mockErrReader MockErrReader + mockInputReader MockInputReader + + testdataDir = "testdata" + unreadableFile = testdataDir + "/unreadable-file.txt" + sampleTextFile = testdataDir + "/sample-text.txt" + tempDir string + + samplePrivateKeyPassword = "testpassword" + + RSASamplePKCS1PlaintextPrivateKey = testdataDir + "/rsa-pkcs1-plaintext-private-key.pem" + RSASamplePKCS1EncryptedPrivateKey = testdataDir + "/rsa-pkcs1-encrypted-private-key.pem" + RSASamplePKCS1EncBrokenPrivateKey = testdataDir + "/rsa-pkcs1-encrypted-broken-private-key.pem" + RSASamplePKCS1Certificate = testdataDir + "/rsa-pkcs1-crt.pem" + + RSASamplePKCS8PlaintextPrivateKey = testdataDir + "/rsa-pkcs8-plaintext-private-key.pem" + RSASamplePKCS8EncryptedPrivateKey = testdataDir + "/rsa-pkcs8-encrypted-private-key.pem" + RSASamplePKCS8EncBrokenPrivateKey = testdataDir + "/rsa-pkcs8-encrypted-broken-private-key.pem" + RSASamplePKCS8Certificate = testdataDir + "/rsa-pkcs8-crt.pem" + + ECDSASamplePlaintextPrivateKey = testdataDir + "/ecdsa-plaintext-private-key.pem" + ECDSASampleEncryptedPrivateKey = testdataDir + "/ecdsa-encrypted-private-key.pem" + ECDSASampleEncBrokenPrivateKey = testdataDir + "/ecdsa-encrypted-broken-private-key.pem" + ECDSASampleCertificate = testdataDir + "/ecdsa-crt.pem" + + ED25519SamplePlaintextPrivateKey = testdataDir + "/ed25519-plaintext-private-key.pem" + ED25519SampleEncryptedPrivateKey = testdataDir + "/ed25519-encrypted-private-key.pem" + ED25519SampleEncBrokenPrivateKey = testdataDir + "/ed25519-encrypted-broken-private-key.pem" + ED25519SampleCertificate = testdataDir + "/ed25519-crt.pem" + + systemCertPool *x509.CertPool + caCertPool *x509.CertPool + RSACaCertPool *x509.CertPool + + RSACaCertKey *rsa.PrivateKey + RSACaCertKeyPEM []byte + RSACaCertKeyFile string + RSACaCertPEM []byte + RSACaCertParent *x509.Certificate + RSACaCertPEMString string + RSACaCertFile string + + RSASampleCertKey *rsa.PrivateKey + RSASampleCertKeyPEM []byte + RSASampleCertKeyFile string + RSASampleCertPEM []byte + RSASampleCertParent *x509.Certificate + RSASampleCertPEMString string + RSASampleCertFile string + RSASampleCertBundleFile string +) + +func TestMain(m *testing.M) { + fmt.Printf("Certinfo TestMain - check test data dir: %s\n", testdataDir) + + if errDataDir := os.Mkdir(testdataDir, os.ModePerm); errDataDir != nil { + fmt.Println(errDataDir) + } + + systemCertPool, _ = x509.SystemCertPool() + caCertPool = x509.NewCertPool() + + generateRSACaData() + caCertPool.AppendCertsFromPEM(RSACaCertPEM) + + generateRSACertificateData() + + m.Run() + + // Cleanup + defer func() { + filesToDel := []string{ + RSACaCertKeyFile, + RSACaCertFile, + RSASampleCertFile, + RSASampleCertKeyFile, + } + for _, fileToDel := range filesToDel { + err := os.Remove(fileToDel) + if err != nil { + fmt.Printf( + "unable to remove file %s: %s", + fileToDel, + err.Error(), + ) + } + } + }() +} + +func (mir MockInputReader) ReadPassword(fd int) ([]byte, error) { + return []byte(samplePrivateKeyPassword), nil +} + +func (mir MockInputReader) ReadFile(name string) ([]byte, error) { + mir.readError = fmt.Errorf("unable to read file %s", name) + return nil, mir.readError +} + +func (mr MockErrReader) ReadFile(name string) ([]byte, error) { + mr.readError = fmt.Errorf("unable to read file %s", name) + return nil, mr.readError +} + +func (mr MockErrReader) ReadPassword(fd int) ([]byte, error) { + mr.readError = errors.New("unable to read password") + return nil, mr.readError +} + +func generateRSACertificateData() { + var err error + + // RSA Certificate + RSASampleCertKey, _ = RSAGenerateKey(2048) + RSASampleCertKeyPEM = RSAPrivateKeyToPEM(RSASampleCertKey) + + RSASampleCertKeyFile, err = createTmpFileWithContent( + testdataDir, + "RSASampleCertKey", + RSASampleCertKeyPEM, + ) + if err != nil { + fmt.Println(err) + } + + rsaSampleCertTpl := certificateTemplate{ + cn: "RSA Testing Sample Certificate", + isCA: false, + key: RSASampleCertKey, + caKey: RSACaCertKey, + parent: RSACaCertParent, + dnsNames: []string{"example.com", "example.net", "example.de"}, + ipAddresses: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, + } + + RSASampleCertPEM, RSASampleCertParent, _ = GenerateCertificate( + rsaSampleCertTpl, + ) + RSASampleCertPEMString = string(RSASampleCertPEM) + + RSASampleCertFile, err = createTmpFileWithContent( + testdataDir, + "RSASampleCert", + []byte(RSASampleCertPEMString), + ) + if err != nil { + fmt.Print(err) + } +} + +func generateRSACaData() { + var err error + + // RSA CA + RSACaCertKey, _ = RSAGenerateKey(2048) + RSACaCertKeyPEM = RSAPrivateKeyToPEM(RSACaCertKey) + + RSACaCertKeyFile, err = createTmpFileWithContent( + testdataDir, + "RSACaCertKey", + RSACaCertKeyPEM, + ) + if err != nil { + fmt.Println(err) + } + + rsaCaCertTpl := certificateTemplate{ + cn: "RSA Testing CA", + isCA: true, + key: RSACaCertKey, + } + RSACaCertPEM, RSACaCertParent, _ = GenerateCertificate( + rsaCaCertTpl, + ) + RSACaCertPEMString = string(RSACaCertPEM) + + RSACaCertPool = x509.NewCertPool() + RSACaCertPool.AppendCertsFromPEM(RSACaCertPEM) + + RSACaCertFile, err = createTmpFileWithContent( + testdataDir, + "RSACaCert", + []byte(RSACaCertPEMString), + ) + if err != nil { + fmt.Print(err) + } +} + +// GenerateDemoCert takes as input a demoCertTemplate struct, creates a x509 Certificate +// and returns a PEM encoded version of the certificate, a pointer to the certificate and +// an error. +// The pointer can be used as parent in the creation of a new certificate linked to a self signed +// CA. +// +// Reference: https://shaneutt.com/blog/golang-ca-and-signed-cert-go/ +func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, error) { + // Create a random serial number + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return nil, nil, fmt.Errorf("failed to generate serial number: %w", err) + } + + // Define template for non CA certificate + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: tpl.cn, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(1 * 24 * time.Hour), + IsCA: false, + DNSNames: tpl.dnsNames, + IPAddresses: tpl.ipAddresses, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + x509.ExtKeyUsageServerAuth, + }, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + certParent := tpl.parent + signingKey := tpl.caKey + + // in case of CA cert we udpate the template with the proper fields + // use the CA cert key for signing + // and do not reference any previuous parent Certificate + if tpl.isCA { + certParent = &template + signingKey = tpl.key + template = x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: tpl.cn, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(1 * 24 * time.Hour), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + x509.ExtKeyUsageServerAuth, + }, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + } + + derBytes, err := x509.CreateCertificate(rand.Reader, + &template, certParent, &tpl.key.PublicKey, signingKey) + if err != nil { + return nil, nil, fmt.Errorf("failed to create certificate: %w", err) + } + + // Encode the certificate to PEM + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + + // parse the DER encoded x509.Certificate + certificate, err := x509.ParseCertificate(derBytes) + if err != nil { + return nil, nil, err + } + + return certPEM, certificate, nil +} + +func createTmpFileWithContent(tempDir string, filePattern string, fileContent []byte) (string, error) { + f, err := os.CreateTemp(tempDir, filePattern) + if err != nil { + return emptyString, err + } + + defer func() { + err = errors.Join(err, f.Close()) + }() + + err = os.WriteFile(f.Name(), fileContent, 0644) + if err != nil { + return emptyString, err + } + + return f.Name(), nil +} + +func RSAGenerateKey(bits int) (*rsa.PrivateKey, error) { + priv, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, fmt.Errorf("failed to generate private key: %w", err) + } + + return priv, nil +} + +func RSAPrivateKeyToPEM(key *rsa.PrivateKey) []byte { + keyDER := x509.MarshalPKCS1PrivateKey(key) + keyBlock := pem.Block{ + Type: "RSA PRIVATE KEY", + Headers: nil, + Bytes: keyDER, + } + keyPEM := pem.EncodeToMemory(&keyBlock) + + return keyPEM +} From 9277434317eb1871da2797ecad95b2351fabb78e Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 19:08:38 +0100 Subject: [PATCH 04/23] refactor: methods of structs implementing Reader interface declare only the receiver's type --- internal/certinfo/certinfo.go | 12 ++++-------- internal/certinfo/main_test.go | 34 ++++++++++++++++++---------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index eb52853..425c171 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -42,9 +42,7 @@ type ( ReadPassword(fd int) ([]byte, error) } - InputReader struct { - readError error - } + InputReader struct{} ) var ( @@ -53,18 +51,16 @@ var ( inputReader InputReader ) -func (ir InputReader) ReadFile(name string) ([]byte, error) { - ir.readError = nil - +func (InputReader) ReadFile(name string) ([]byte, error) { file, err := os.ReadFile(name) if err != nil { - return nil, ir.readError + return nil, err } return file, nil } -func (ir InputReader) ReadPassword(fd int) ([]byte, error) { +func (InputReader) ReadPassword(fd int) ([]byte, error) { return term.ReadPassword(fd) } diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index 89c957a..ac03657 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -26,13 +26,9 @@ type ( parent *x509.Certificate } - MockErrReader struct { - readError error - } + MockErrReader struct{} - MockInputReader struct { - readError error - } + MockInputReader struct{} ) var ( @@ -126,23 +122,29 @@ func TestMain(m *testing.M) { }() } -func (mir MockInputReader) ReadPassword(fd int) ([]byte, error) { +func (MockInputReader) ReadPassword(_ int) ([]byte, error) { return []byte(samplePrivateKeyPassword), nil } -func (mir MockInputReader) ReadFile(name string) ([]byte, error) { - mir.readError = fmt.Errorf("unable to read file %s", name) - return nil, mir.readError +func (MockInputReader) ReadFile(name string) ([]byte, error) { + return nil, fmt.Errorf("unable to read file %s", name) } -func (mr MockErrReader) ReadFile(name string) ([]byte, error) { - mr.readError = fmt.Errorf("unable to read file %s", name) - return nil, mr.readError +func (MockErrReader) ReadFile(name string) ([]byte, error) { + return nil, fmt.Errorf("unable to read file %s", name) } -func (mr MockErrReader) ReadPassword(fd int) ([]byte, error) { - mr.readError = errors.New("unable to read password") - return nil, mr.readError +func (MockErrReader) ReadPassword(fd int) ([]byte, error) { + // WARN: when used a replacement of term.ReadPassword() + // this function will trigger a returned error of type: + // "error reading passphrase: inappropriate ioctl for device" + // instead of + // "error reading passphrase: unable to read password". + // The function still serves the purpose of injecting an error + // but this show the incomplete mocking of the original function + return func(_ int) ([]byte, error) { + return []byte{}, errors.New("mockErrReader: unable to read password") + }(fd) } func generateRSACertificateData() { From a568733bd9d5c5bc4c04d6de18a91adea0224c02 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 19:19:48 +0100 Subject: [PATCH 05/23] ci: update error checks and draft ParsePrivateKey test --- internal/certinfo/certinfo_test.go | 5 ++- internal/certinfo/common_handlers.go | 7 ++++- internal/certinfo/common_handlers_test.go | 37 ++++++++++++++++++++++- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/internal/certinfo/certinfo_test.go b/internal/certinfo/certinfo_test.go index a53dbb4..45de323 100644 --- a/internal/certinfo/certinfo_test.go +++ b/internal/certinfo/certinfo_test.go @@ -43,13 +43,15 @@ func TestCertinfo_SetCaPoolFromFile(t *testing.T) { 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, - "unable to create CertPool from file", + "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) @@ -57,6 +59,7 @@ func TestCertinfo_SetCaPoolFromFile(t *testing.T) { assert.Equal(t, "unable to create CertPool from file", errWrongCert.Error(), + "check wrong cert", ) // TODO: complete, compare struct data with input diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index c33edfe..6d44457 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -253,7 +253,12 @@ func GetKeyFromFile(keyFilePath string, inputReader Reader) (crypto.PrivateKey, return nil, err } - key, err := ParsePrivateKey(keyPEM, privateKeyPwEnvVar, inputReader) + // TODO: get pwEnvKey from function's arguments + key, err := ParsePrivateKey( + keyPEM, + privateKeyPwEnvVar, + inputReader, + ) if err != nil { return nil, err } diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 557bbad..2f3ce1f 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -342,6 +342,41 @@ func TestCertinfo_GetKeyFromFile(t *testing.T) { }) } +func TestCertinfo_ParsePrivateKey(t *testing.T) { + // ParsePrivateKey calls getPassphraseIfNeeded + // if the key in encrypted. It then evaluates the returned error. + // We want to trigger that error in this test. + // Skip passing password via ENV, this way getPassphraseIfNeeded + // will read from stdin. + // Need to use and encrypted key's PEM + // t.Run("stdin pw read error", func(t *testing.T) { + // key, err := GetKeyFromFile( + // RSASamplePKCS1EncryptedPrivateKey, + // inputReader, + // ) + // require.NoError(t, err) + // + // var i any = key + // + // rsaKey, ok := i.(*rsa.PrivateKey) + // + // require.True(t, ok, "the key must be of type *rsa.PrivateKey") + // + // rsaKeyPEM := RSAPrivateKeyToPEM(rsaKey) + // + // _, err = ParsePrivateKey( + // rsaKeyPEM, + // privateKeyPwEnvVar, + // mockErrReader, + // ) + // require.Error(t, err) + // assert.EqualError(t, + // err, + // "error reading passphrase: inappropriate ioctl for device", + // ) + // }) +} + func TestCertinfo_IsPrivateKeyEncrypted(t *testing.T) { t.Run("No PEM encoded file", func(t *testing.T) { sampleText, err := inputReader.ReadFile(sampleTextFile) @@ -377,7 +412,7 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { require.Error(t, err) assert.EqualError(t, err, - "error reading passphrase: unable to read password", + "error reading passphrase: mockErrReader: unable to read password", ) }) From 14f51b447fc6492e1cb55cd78c942d02347e3524 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 22:40:12 +0100 Subject: [PATCH 06/23] docs: redact PEM blocks in testdata README --- internal/certinfo/testdata/README.md | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/internal/certinfo/testdata/README.md b/internal/certinfo/testdata/README.md index 1aa2acf..f713ff3 100644 --- a/internal/certinfo/testdata/README.md +++ b/internal/certinfo/testdata/README.md @@ -35,8 +35,8 @@ Verifying - Enter pass phrase for rsa-pkcs1-encrypted-private-key.pem: Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,314A1EF1E544F10E9741F8A9C57384C8 -8CpkWf5vuUxv15p6c7LJPgNO2gc1M3K+1ncRmJOblOGrpjv/Z5P1kE5wjEMIl/8Q -3BcapXsV+yuAnA+eBglHKBIrUf3Em6KxjjlcHP95qKSitvj7iOXtGBTJtkkswgR5 +(REDACTED PEM Block) +(REDACTED PEM Block) ``` Decrypt the key: @@ -65,8 +65,8 @@ Verifying - Enter PEM pass phrase: > head -n 3 rsa-pkcs8-encrypted-private-key.pem -----BEGIN ENCRYPTED PRIVATE KEY----- -MIIJtTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQ188U9zxSGVXQrj4+ -8z3z1QICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDutok8Bf7Wu5Pc6 +(REDACTED PEM Block) +(REDACTED PEM Block) ``` Decrypt the key: @@ -78,8 +78,7 @@ writing RSA key > head -n 3 rsa-pkcs8-plaintext-private-key.pem -----BEGIN PRIVATE KEY----- -MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC2O/PumNzqJcUd -4I0SBD0WP/AsCtDZGa6qF7YuqjO3hXx8jx66KRUQLFccgXRwOgmZ3Jj2hTvVUpbe +(REDACTED PEM Block) ``` ## Create sample RSA certificates @@ -110,7 +109,7 @@ Create the plaintext key: > head -n 2 ecdsa-plaintext-private-key.pem -----BEGIN EC PRIVATE KEY----- -MHcCAQEEILVvywgqKFoKYCDNLNehJSbvwxCdtDr1UT+QID0hxqa+oAoGCCqGSM49 +(REDACTED PEM Block) ``` Encrypt the key: @@ -123,8 +122,8 @@ Encrypt the key: Proc-Type: 4,ENCRYPTED DEK-Info: AES-256-CBC,B1D5B0AFFB8F76B80B16373F8D81F9C3 -1+K+hCOmodqpbb5s+GlWnW9J5VlnaR4gHyD/WKmIy5xo8iu/0OTHX4FjOc0TpMqi -UqHGMOYI078StHO7kexCwUC26QaU61RVx1P6AQX21ErSfJaSpO/48fkI+/3mgAK+ +(REDACTED PEM Block) +(REDACTED PEM Block) ``` ## Create sample ECDSA certificate @@ -142,7 +141,7 @@ Create the plaintext key: > head -n2 ed25519-plaintext-private-key.pem -----BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIEGYgM/XLll7BGmu+g7BdEOD21o+B7w/lZ6YbMOAiJ2s +(REDACTED PEM Block) ``` Encrypt the key: @@ -152,13 +151,11 @@ Encrypt the key: > head -n 3 ed25519-encrypted-private-key.pem -----BEGIN ENCRYPTED PRIVATE KEY----- -MIGjMF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBAbN/Oyvx9FWCW4Cq/Y -Ea20AgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQaUVczU8lT61K28bk +(REDACTED PEM Block) +(REDACTED PEM Block) ``` ## Create sample ED25519 certificate ```shell > openssl req -new -x509 -key ed25519-plaintext-private-key.pem -days 3650 -out ed25519-crt.pem -subj "/CN=example.com/O=Example Org" ``` - - From a896b2505391c271fa3ee9bd4bc03874cb48f4c2 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 22:45:49 +0100 Subject: [PATCH 07/23] refactor: GetKeyFromFile takes as argument the ENV var that could hold the key's password --- internal/certinfo/certinfo.go | 6 +++++- internal/certinfo/common_handlers.go | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/certinfo/certinfo.go b/internal/certinfo/certinfo.go index 425c171..07e4c23 100644 --- a/internal/certinfo/certinfo.go +++ b/internal/certinfo/certinfo.go @@ -110,7 +110,11 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) er func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, fileReader Reader) error { if filePath != emptyString { - keyFromFile, err := GetKeyFromFile(filePath, fileReader) + keyFromFile, err := GetKeyFromFile( + filePath, + privateKeyPwEnvVar, + fileReader, + ) if err != nil { return err } diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index 6d44457..fffc445 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -243,7 +243,7 @@ func ParsePrivateKey(keyPEM []byte, pwEnvKey string, pwReader Reader) (crypto.Pr return nil, errors.New("unsupported key format or invalid password") } -func GetKeyFromFile(keyFilePath string, inputReader Reader) (crypto.PrivateKey, error) { +func GetKeyFromFile(keyFilePath string, keyPwEnvVar string, inputReader Reader) (crypto.PrivateKey, error) { if keyFilePath == emptyString { return nil, errors.New("empty string provided as keyFilePath") } @@ -253,10 +253,9 @@ func GetKeyFromFile(keyFilePath string, inputReader Reader) (crypto.PrivateKey, return nil, err } - // TODO: get pwEnvKey from function's arguments key, err := ParsePrivateKey( keyPEM, - privateKeyPwEnvVar, + keyPwEnvVar, inputReader, ) if err != nil { From d2d6677b472f5d1de5e25486efdd22d0cc80b45e Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sat, 13 Dec 2025 22:47:02 +0100 Subject: [PATCH 08/23] ci: use tables for most tests related to GetKeyFromFile and certMatchPrivateKey --- internal/certinfo/common_handlers_test.go | 537 ++++++++++------------ 1 file changed, 247 insertions(+), 290 deletions(-) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 2f3ce1f..f4b7b62 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -1,6 +1,7 @@ package certinfo import ( + "crypto" "crypto/x509" "testing" @@ -144,22 +145,155 @@ func TestCertinfo_GetCertsFromBundle(t *testing.T) { }) } -func TestCertinfo_GetKeyFromFile(t *testing.T) { +func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { + tests := []struct { + desc string + expectError bool + keyFile string + expectMsg string + needEnv bool + keyPw string + }{ + { + desc: "wrong file", + expectError: true, + keyFile: RSACaCertFile, + expectMsg: "unsupported key format or invalid password", + }, + { + desc: "emptyString", + expectError: true, + keyFile: emptyString, + expectMsg: "empty string provided as keyFilePath", + }, + { + desc: "No PEM encoded file", + expectError: true, + keyFile: sampleTextFile, + expectMsg: "failed to decode PEM", + }, + { + desc: "Plain RSA PKCS1 key import", + expectError: false, + keyFile: RSASamplePKCS1PlaintextPrivateKey, + }, + { + desc: "Encrypted RSA PKCS1 key import", + expectError: false, + keyFile: RSASamplePKCS1EncryptedPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Encrypted RSA PKCS1 key import with wrong password", + expectError: true, + expectMsg: "PEM block decryption failed: x509: decryption password incorrect", + keyFile: RSASamplePKCS1EncryptedPrivateKey, + needEnv: true, + keyPw: "wrong pass", + }, + { + desc: "Encrypted broken RSA PKCS1 key import", + expectError: true, + expectMsg: "unsupported key format or invalid password", + keyFile: RSASamplePKCS1EncBrokenPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Plain RSA/4096 PKCS8 key import", + expectError: false, + keyFile: RSASamplePKCS8PlaintextPrivateKey, + }, + { + desc: "Encrypted RSA/4096 PkCS8 key import", + expectError: false, + keyFile: RSASamplePKCS8EncryptedPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Encrypted broken RSA/4096 PKCS8 key import", + expectError: true, + expectMsg: "PKCS8 decryption failed: pkcs8: incorrect password", + keyFile: RSASamplePKCS8EncBrokenPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Plain ECSA key import", + expectError: false, + keyFile: ECDSASamplePlaintextPrivateKey, + }, + { + desc: "Encrypted ECDSA key import", + expectError: false, + keyFile: ECDSASampleEncryptedPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Encrypted broken ECSA key import", + expectError: true, + keyFile: ECDSASampleEncBrokenPrivateKey, + expectMsg: "unsupported key format or invalid password", + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Plain ED25519 key import", + expectError: false, + keyFile: ED25519SamplePlaintextPrivateKey, + }, + { + desc: "Encrypted ED25519 key import", + expectError: false, + keyFile: ED25519SampleEncryptedPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Encrypted broken ED25519 key import", + expectError: true, + keyFile: ED25519SampleEncBrokenPrivateKey, + expectMsg: "PKCS8 decryption failed: pkcs8: incorrect password", + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + if tt.needEnv { + t.Setenv(privateKeyPwEnvVar, tt.keyPw) + } + + _, err := GetKeyFromFile( + tt.keyFile, + privateKeyPwEnvVar, + inputReader, + ) + + if tt.expectError { + require.Error(t, err) + assert.Equal(t, + tt.expectMsg, + err.Error(), + ) + } + + if !tt.expectError { + require.NoError(t, err) + } + }) + } + t.Run("FileReadErrors", func(t *testing.T) { t.Parallel() - _, errEmptyString := GetKeyFromFile( - emptyString, - inputReader, - ) - require.Error(t, errEmptyString) - assert.Equal(t, - "empty string provided as keyFilePath", - errEmptyString.Error(), - ) - _, errNoRead := GetKeyFromFile( unreadableFile, + privateKeyPwEnvVar, mockErrReader, ) require.Error(t, errNoRead) @@ -167,21 +301,14 @@ func TestCertinfo_GetKeyFromFile(t *testing.T) { "unable to read file testdata/unreadable-file.txt", errNoRead.Error(), ) - - _, errWrongFile := GetKeyFromFile( - RSACaCertFile, - inputReader, - ) - require.Error(t, errWrongFile) - assert.Equal(t, - "unsupported key format or invalid password", - errWrongFile.Error(), - ) }) +} +func TestCertinfo_GetKeyFromFile(t *testing.T) { t.Run("Plain RSA key import", func(t *testing.T) { got, err := GetKeyFromFile( RSACaCertKeyFile, + privateKeyPwEnvVar, inputReader, ) require.NoError(t, err) @@ -193,153 +320,6 @@ func TestCertinfo_GetKeyFromFile(t *testing.T) { ) } }) - - t.Run("No PEM encoded text import", func(t *testing.T) { - _, err := GetKeyFromFile( - sampleTextFile, - inputReader, - ) - require.Error(t, err) - assert.EqualError(t, err, "failed to decode PEM") - }) - - t.Run("Plain RSA PKCS1 key import", func(t *testing.T) { - _, err := GetKeyFromFile( - RSASamplePKCS1PlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted RSA PKCS1 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - RSASamplePKCS1EncryptedPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted RSA PKCS1 key import with wrong password", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, "wrong password") - - _, err := GetKeyFromFile( - RSASamplePKCS1EncryptedPrivateKey, - inputReader, - ) - require.Error(t, err) - assert.EqualError(t, - err, - "PEM block decryption failed: x509: decryption password incorrect", - ) - }) - - t.Run("Encrypted broken RSA PKCS1 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - RSASamplePKCS1EncBrokenPrivateKey, - inputReader, - ) - require.Error(t, err) - require.Equal(t, - "unsupported key format or invalid password", - err.Error()) - }) - - t.Run("Plain RSA/4096 PKCS8 key import", func(t *testing.T) { - _, err := GetKeyFromFile( - RSASamplePKCS8PlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted RSA/4096 PKCS8 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - RSASamplePKCS8EncryptedPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted broken RSA/4096 PKCS8 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - RSASamplePKCS8EncBrokenPrivateKey, - inputReader, - ) - require.Error(t, err) - assert.Equal(t, - "PKCS8 decryption failed: pkcs8: incorrect password", - err.Error()) - }) - - t.Run("Plain ECDSA key import", func(t *testing.T) { - _, err := GetKeyFromFile( - ECDSASamplePlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted ECDSA key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - ECDSASampleEncryptedPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted broken ECDSA key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - ECDSASampleEncBrokenPrivateKey, - inputReader, - ) - require.Error(t, err) - assert.Equal(t, - "unsupported key format or invalid password", - err.Error()) - }) - - t.Run("Plain ED25519 key import", func(t *testing.T) { - _, err := GetKeyFromFile( - ED25519SamplePlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted ED25519 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - ED25519SampleEncryptedPrivateKey, - inputReader, - ) - require.NoError(t, err) - }) - - t.Run("Encrypted broken ED25519 key import", func(t *testing.T) { - t.Setenv(privateKeyPwEnvVar, samplePrivateKeyPassword) - - _, err := GetKeyFromFile( - ED25519SampleEncBrokenPrivateKey, - inputReader, - ) - require.Error(t, err) - assert.Equal(t, - "PKCS8 decryption failed: pkcs8: incorrect password", - err.Error()) - }) } func TestCertinfo_ParsePrivateKey(t *testing.T) { @@ -348,33 +328,23 @@ func TestCertinfo_ParsePrivateKey(t *testing.T) { // We want to trigger that error in this test. // Skip passing password via ENV, this way getPassphraseIfNeeded // will read from stdin. + // The mockErrReader will inject an error at this point. // Need to use and encrypted key's PEM - // t.Run("stdin pw read error", func(t *testing.T) { - // key, err := GetKeyFromFile( - // RSASamplePKCS1EncryptedPrivateKey, - // inputReader, - // ) - // require.NoError(t, err) - // - // var i any = key - // - // rsaKey, ok := i.(*rsa.PrivateKey) - // - // require.True(t, ok, "the key must be of type *rsa.PrivateKey") - // - // rsaKeyPEM := RSAPrivateKeyToPEM(rsaKey) - // - // _, err = ParsePrivateKey( - // rsaKeyPEM, - // privateKeyPwEnvVar, - // mockErrReader, - // ) - // require.Error(t, err) - // assert.EqualError(t, - // err, - // "error reading passphrase: inappropriate ioctl for device", - // ) - // }) + t.Run("stdin pw read error", func(t *testing.T) { + keyPEM, err := inputReader.ReadFile(RSASamplePKCS1EncryptedPrivateKey) + require.NoError(t, err) + + _, err = ParsePrivateKey( + keyPEM, + "notSet", + mockErrReader, + ) + require.Error(t, err) + assert.EqualError(t, + err, + "error reading passphrase: mockErrReader: unable to read password", + ) + }) } func TestCertinfo_IsPrivateKeyEncrypted(t *testing.T) { @@ -430,103 +400,90 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { }) } -func TestCertinfo_certMatchPrivateKey(t *testing.T) { - t.Run("RSA PKCS8 match True", func(t *testing.T) { - match, err := certMatchPrivateKey( - RSACaCertParent, - RSACaCertKey, - ) - require.NoError(t, err) - assert.True(t, match, "matchTrue") - }) - - t.Run("RSA PKCS1 match True", func(t *testing.T) { - certs, err := GetCertsFromBundle( - RSASamplePKCS1Certificate, - inputReader, - ) - require.NoError(t, err) - - key, err := GetKeyFromFile( - RSASamplePKCS1PlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - - match, err := certMatchPrivateKey( - certs[0], - key, - ) - require.NoError(t, err) - assert.True(t, match) - }) - - t.Run("ECDSA match True", func(t *testing.T) { - certs, err := GetCertsFromBundle( - ECDSASampleCertificate, - inputReader, - ) - require.NoError(t, err) - - key, err := GetKeyFromFile( - ECDSASamplePlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - - match, err := certMatchPrivateKey( - certs[0], - key, - ) - require.NoError(t, err) - assert.True(t, match) - }) - - t.Run("ED25519 match True", func(t *testing.T) { - certs, err := GetCertsFromBundle( - ED25519SampleCertificate, - inputReader, - ) - require.NoError(t, err) - - key, err := GetKeyFromFile( - ED25519SamplePlaintextPrivateKey, - inputReader, - ) - require.NoError(t, err) - - match, err := certMatchPrivateKey( - certs[0], - key, - ) - require.NoError(t, err) - assert.True(t, match) - }) +func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { + matchFalseTests := []struct { + desc string + cert *x509.Certificate + key crypto.PrivateKey + }{ + { + desc: "key cert mismatch", + cert: RSACaCertParent, + key: RSASampleCertKey, + }, + { + desc: "cert nil", + cert: nil, + key: RSASampleCertKey, + }, + { + desc: "key nil", + cert: RSACaCertParent, + key: nil, + }, + } + + for _, tt := range matchFalseTests { + t.Run(tt.desc, func(t *testing.T) { + match, err := certMatchPrivateKey( + tt.cert, + tt.key, + ) + require.NoError(t, err) + assert.False(t, match) + }) + } +} - t.Run("matchFalse", func(t *testing.T) { - match, err := certMatchPrivateKey( - RSACaCertParent, - RSASampleCertKey, - ) - require.NoError(t, err) - assert.False(t, match) - }) +func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { + matchTruetests := []struct { + desc string + certFile string + keyFile string + }{ + { + desc: "RSA PKCS1", + certFile: RSASamplePKCS1Certificate, + keyFile: RSASamplePKCS1PlaintextPrivateKey, + }, + { + desc: "RSA PKCS8", + certFile: RSASamplePKCS8Certificate, + keyFile: RSASamplePKCS8PlaintextPrivateKey, + }, + { + desc: "ECSA", + certFile: ECDSASampleCertificate, + keyFile: ECDSASamplePlaintextPrivateKey, + }, + { + desc: "ED25519", + certFile: ED25519SampleCertificate, + keyFile: ED25519SamplePlaintextPrivateKey, + }, + } + + for _, tt := range matchTruetests { + t.Run(tt.desc+" match True", func(t *testing.T) { + certs, err := GetCertsFromBundle( + tt.certFile, + inputReader, + ) + require.NoError(t, err) - t.Run("Cert nil False match", func(t *testing.T) { - match, err := certMatchPrivateKey( - nil, - RSASampleCertKey, - ) - require.NoError(t, err) - assert.False(t, match) - }) + key, err := GetKeyFromFile( + tt.keyFile, + privateKeyPwEnvVar, + inputReader, + ) + require.NoError(t, err) - t.Run("Key nil False match", func(t *testing.T) { - match, err := certMatchPrivateKey( - RSACaCertParent, - nil, - ) - require.NoError(t, err) - assert.False(t, match) - }) + match, err := certMatchPrivateKey( + certs[0], + key, + ) + require.NoError(t, err) + assert.True(t, match) + }) + } } From 0afe9085fc07a00ce10ab96de8cd24396dc11cee Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 00:05:19 +0100 Subject: [PATCH 09/23] fix: useless error assertion --- internal/requests/requests_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index ba96917..e90b6b9 100644 --- a/internal/requests/requests_test.go +++ b/internal/requests/requests_test.go @@ -23,10 +23,6 @@ func TestNewRequestsMetaConfig(t *testing.T) { t.Parallel() rmc, err := NewRequestsMetaConfig() - if err != nil { - require.Error(t, err, "error when calling NewRequestsMetaConfig()") - } - require.NoError(t, err) var i any = rmc From 669b04cdcf7f11750d34f682d8d4d4ea27123a5b Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 00:05:59 +0100 Subject: [PATCH 10/23] fix: typos and obsolete warning --- internal/certinfo/common_handlers.go | 2 +- internal/certinfo/common_handlers_test.go | 2 +- internal/certinfo/main_test.go | 9 +-------- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index fffc445..6cba758 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -98,7 +98,7 @@ func GetRootCertsFromString(caBundleString string) (*x509.CertPool, error) { func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certificate, error) { if certBundlePath == emptyString { - return nil, errors.New("empty string provided as caBundlePath") + return nil, errors.New("empty string provided as certBundlePath") } certPEM, err := fileReader.ReadFile(certBundlePath) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index f4b7b62..2e5a7e2 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -102,7 +102,7 @@ func TestCertinfo_GetCertsFromBundle(t *testing.T) { ) require.Error(t, errEmptyString) assert.Equal(t, - "empty string provided as caBundlePath", + "empty string provided as certBundlePath", errEmptyString.Error(), ) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index ac03657..e8410b9 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -135,13 +135,6 @@ func (MockErrReader) ReadFile(name string) ([]byte, error) { } func (MockErrReader) ReadPassword(fd int) ([]byte, error) { - // WARN: when used a replacement of term.ReadPassword() - // this function will trigger a returned error of type: - // "error reading passphrase: inappropriate ioctl for device" - // instead of - // "error reading passphrase: unable to read password". - // The function still serves the purpose of injecting an error - // but this show the incomplete mocking of the original function return func(_ int) ([]byte, error) { return []byte{}, errors.New("mockErrReader: unable to read password") }(fd) @@ -264,7 +257,7 @@ func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, er certParent := tpl.parent signingKey := tpl.caKey - // in case of CA cert we udpate the template with the proper fields + // in case of CA cert we update the template with the proper fields // use the CA cert key for signing // and do not reference any previuous parent Certificate if tpl.isCA { From 9752a2dbfa59223b879d63b42b3f8500d0164ff4 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 00:06:52 +0100 Subject: [PATCH 11/23] refactor: createTmpFileWithContent use named returns to catch file close errors from deferred function --- internal/certinfo/main_test.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index e8410b9..567f723 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -99,8 +99,6 @@ func TestMain(m *testing.M) { generateRSACertificateData() - m.Run() - // Cleanup defer func() { filesToDel := []string{ @@ -120,6 +118,8 @@ func TestMain(m *testing.M) { } } }() + + m.Run() } func (MockInputReader) ReadPassword(_ int) ([]byte, error) { @@ -298,18 +298,23 @@ func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, er return certPEM, certificate, nil } -func createTmpFileWithContent(tempDir string, filePattern string, fileContent []byte) (string, error) { +func createTmpFileWithContent( + tempDir string, + filePattern string, + fileContent []byte, +) (filePath string, err error) { f, err := os.CreateTemp(tempDir, filePattern) if err != nil { return emptyString, err } defer func() { - err = errors.Join(err, f.Close()) + if closeErr := f.Close(); closeErr != nil { + err = errors.Join(err, closeErr) + } }() - err = os.WriteFile(f.Name(), fileContent, 0644) - if err != nil { + if err = os.WriteFile(f.Name(), fileContent, 0644); err != nil { return emptyString, err } From 2e54372ec52177a6177ecedf61cd64ad6cb453db Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 00:36:10 +0100 Subject: [PATCH 12/23] refactor: add nil checks to functions taking the Reader interface as argument --- internal/certinfo/common_handlers.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index 6cba758..0511e62 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -70,6 +70,10 @@ func GetRootCertsFromFile(caBundlePath string, fileReader Reader) (*x509.CertPoo return nil, errors.New("empty string provided as caBundlePath") } + if fileReader == nil { + return nil, errors.New("nil Reader provided") + } + certsFromFile, err := fileReader.ReadFile(caBundlePath) if err != nil { return nil, fmt.Errorf("failed to read CA bundle file: %w", err) @@ -101,6 +105,10 @@ func GetCertsFromBundle(certBundlePath string, fileReader Reader) ([]*x509.Certi return nil, errors.New("empty string provided as certBundlePath") } + if fileReader == nil { + return nil, errors.New("nil Reader provided") + } + certPEM, err := fileReader.ReadFile(certBundlePath) if err != nil { return nil, fmt.Errorf("error reading certificate file: %w", err) @@ -162,6 +170,10 @@ func getPassphraseIfNeeded(isEncrypted bool, pwEnvKey string, pwReader Reader) ( return nil, nil } + if pwReader == nil { + return nil, errors.New("nil Reader provided") + } + pkeyEnvPw := os.Getenv(pwEnvKey) if pkeyEnvPw != "" { return []byte(pkeyEnvPw), nil @@ -243,11 +255,19 @@ func ParsePrivateKey(keyPEM []byte, pwEnvKey string, pwReader Reader) (crypto.Pr return nil, errors.New("unsupported key format or invalid password") } -func GetKeyFromFile(keyFilePath string, keyPwEnvVar string, inputReader Reader) (crypto.PrivateKey, error) { +func GetKeyFromFile( + keyFilePath string, + keyPwEnvVar string, + inputReader Reader, +) (crypto.PrivateKey, error) { if keyFilePath == emptyString { return nil, errors.New("empty string provided as keyFilePath") } + if inputReader == nil { + return nil, errors.New("nil Reader provided") + } + keyPEM, err := inputReader.ReadFile(keyFilePath) if err != nil { return nil, err From eda1e0c1ebe9319a601ecb73feb7476f78d6835d Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 00:52:16 +0100 Subject: [PATCH 13/23] refactor: TestMain for better handling of cleanup and testdatadir creation --- internal/certinfo/main_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index 567f723..cf03d75 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -87,19 +87,11 @@ var ( func TestMain(m *testing.M) { fmt.Printf("Certinfo TestMain - check test data dir: %s\n", testdataDir) - if errDataDir := os.Mkdir(testdataDir, os.ModePerm); errDataDir != nil { - fmt.Println(errDataDir) + if errDataDir := os.MkdirAll(testdataDir, 0o755); errDataDir != nil { + panic(errDataDir) } - systemCertPool, _ = x509.SystemCertPool() - caCertPool = x509.NewCertPool() - - generateRSACaData() - caCertPool.AppendCertsFromPEM(RSACaCertPEM) - - generateRSACertificateData() - - // Cleanup + // Cleanup (register early so panics in setup still clean up what was created) defer func() { filesToDel := []string{ RSACaCertKeyFile, @@ -119,6 +111,14 @@ func TestMain(m *testing.M) { } }() + systemCertPool, _ = x509.SystemCertPool() + caCertPool = x509.NewCertPool() + + generateRSACaData() + caCertPool.AppendCertsFromPEM(RSACaCertPEM) + + generateRSACertificateData() + m.Run() } @@ -259,7 +259,7 @@ func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, er // in case of CA cert we update the template with the proper fields // use the CA cert key for signing - // and do not reference any previuous parent Certificate + // and do not reference any previous parent Certificate if tpl.isCA { certParent = &template signingKey = tpl.key From 7d7c831d38d1a43b88d57d901209650dc5c57011 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 01:20:07 +0100 Subject: [PATCH 14/23] refactor: createTmpFileWithContent adopt stricter permissions --- internal/certinfo/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index cf03d75..3223a8f 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -314,7 +314,7 @@ func createTmpFileWithContent( } }() - if err = os.WriteFile(f.Name(), fileContent, 0644); err != nil { + if err = os.WriteFile(f.Name(), fileContent, 0o600); err != nil { return emptyString, err } From 4e8a92999e92760a960285e1ea6bcceb49fdb7b9 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 01:21:06 +0100 Subject: [PATCH 15/23] refactor: GenerateCertificate create self signed CA certs with themselves as parent --- internal/certinfo/main_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index 3223a8f..9069f9e 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -261,7 +261,6 @@ func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, er // use the CA cert key for signing // and do not reference any previous parent Certificate if tpl.isCA { - certParent = &template signingKey = tpl.key template = x509.Certificate{ SerialNumber: serialNumber, @@ -278,6 +277,7 @@ func GenerateCertificate(tpl certificateTemplate) ([]byte, *x509.Certificate, er KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } + certParent = &template } derBytes, err := x509.CreateCertificate(rand.Reader, From a62f1c0f31edbfbf3a3f53497016f64f947f8382 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 01:49:33 +0100 Subject: [PATCH 16/23] ci: test for nil Reader --- internal/certinfo/common_handlers_test.go | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 2e5a7e2..93f61ff 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -59,6 +59,15 @@ func TestCertinfo_GetRootCertsFromFile(t *testing.T) { ) } }) + + t.Run("nil Reader error", func(t *testing.T) { + _, err := GetRootCertsFromFile( + RSACaCertFile, + nil, + ) + require.Error(t, err) + require.EqualError(t, err, "nil Reader provided") + }) } func TestCertinfo_GetRootCertsFromString(t *testing.T) { @@ -143,6 +152,15 @@ func TestCertinfo_GetCertsFromBundle(t *testing.T) { ) } }) + + t.Run("nil Reader error", func(t *testing.T) { + _, err := GetCertsFromBundle( + RSACaCertFile, + nil, + ) + require.Error(t, err) + require.EqualError(t, err, "nil Reader provided") + }) } func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { @@ -302,6 +320,21 @@ func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { errNoRead.Error(), ) }) + + t.Run("nil Reader error", func(t *testing.T) { + t.Parallel() + + _, errNoRead := GetKeyFromFile( + RSASamplePKCS1PlaintextPrivateKey, + privateKeyPwEnvVar, + nil, + ) + require.Error(t, errNoRead) + assert.Equal(t, + "nil Reader provided", + errNoRead.Error(), + ) + }) } func TestCertinfo_GetKeyFromFile(t *testing.T) { @@ -386,6 +419,19 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { ) }) + t.Run("nil Reader error", func(t *testing.T) { + _, err := getPassphraseIfNeeded( + true, + privateKeyPwEnvVar, + nil, + ) + require.Error(t, err) + assert.EqualError(t, + err, + "nil Reader provided", + ) + }) + t.Run("pw read success", func(t *testing.T) { pw, err := getPassphraseIfNeeded( true, From e1335738a690c88932beb65d70e26693e67d2978 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 02:01:19 +0100 Subject: [PATCH 17/23] fix: typos in tests --- internal/certinfo/common_handlers_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 93f61ff..de5b786 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -239,7 +239,7 @@ func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { keyPw: samplePrivateKeyPassword, }, { - desc: "Plain ECSA key import", + desc: "Plain ECDSA key import", expectError: false, keyFile: ECDSASamplePlaintextPrivateKey, }, @@ -251,7 +251,7 @@ func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { keyPw: samplePrivateKeyPassword, }, { - desc: "Encrypted broken ECSA key import", + desc: "Encrypted broken ECDSA key import", expectError: true, keyFile: ECDSASampleEncBrokenPrivateKey, expectMsg: "unsupported key format or invalid password", @@ -400,9 +400,9 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { inputReader, ) require.Error(t, err) - assert.EqualError(t, + require.ErrorContains(t, err, - "error reading passphrase: inappropriate ioctl for device", + "error reading passphrase:", ) }) @@ -482,7 +482,7 @@ func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { } func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { - matchTruetests := []struct { + matchTrueTests := []struct { desc string certFile string keyFile string @@ -498,7 +498,7 @@ func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { keyFile: RSASamplePKCS8PlaintextPrivateKey, }, { - desc: "ECSA", + desc: "ECDSA", certFile: ECDSASampleCertificate, keyFile: ECDSASamplePlaintextPrivateKey, }, @@ -509,7 +509,7 @@ func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { }, } - for _, tt := range matchTruetests { + for _, tt := range matchTrueTests { t.Run(tt.desc+" match True", func(t *testing.T) { certs, err := GetCertsFromBundle( tt.certFile, From 31fe26b10a540dea8166fd4dd9dd15c7624a7c2c Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 15:52:28 +0100 Subject: [PATCH 18/23] ci: add more tests for GetCertsFromBundle and certMatchPrivateKey --- internal/certinfo/common_handlers_test.go | 122 +++++++++++------- internal/certinfo/main_test.go | 1 + .../testdata/rsa-pkcs8-broken-crt.pem | 32 +++++ 3 files changed, 110 insertions(+), 45 deletions(-) create mode 100644 internal/certinfo/testdata/rsa-pkcs8-broken-crt.pem diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index de5b786..03873cd 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -102,39 +102,59 @@ func TestCertinfo_GetRootCertsFromString(t *testing.T) { } func TestCertinfo_GetCertsFromBundle(t *testing.T) { - t.Run("FileReadErrors", func(t *testing.T) { - t.Parallel() - - _, errEmptyString := GetCertsFromBundle( - emptyString, - inputReader, - ) - require.Error(t, errEmptyString) - assert.Equal(t, - "empty string provided as certBundlePath", - errEmptyString.Error(), - ) + readErrorTests := []struct { + desc string + certPath string + reader Reader + expectedMsg string + }{ + { + desc: "emptyString", + certPath: emptyString, + reader: inputReader, + expectedMsg: "empty string provided as certBundlePath", + }, + { + desc: "unreadableFile", + certPath: unreadableFile, + reader: mockErrReader, + expectedMsg: "error reading certificate file: unable to read file testdata/unreadable-file.txt", + }, + { + desc: "wrong file", + certPath: RSACaCertKeyFile, + reader: inputReader, + expectedMsg: "no valid certificates found in file " + RSACaCertKeyFile, + }, + { + desc: "nil Reader", + certPath: RSACaCertFile, + reader: nil, + expectedMsg: "nil Reader provided", + }, + { + desc: "broken cert file", + certPath: RSASamplePKCS8BrokenCertificate, + reader: inputReader, + expectedMsg: "error parsing certificate: x509: inner and outer signature algorithm identifiers don't match", + }, + } - _, errNoRead := GetCertsFromBundle( - unreadableFile, - mockErrReader, - ) - require.Error(t, errNoRead) - assert.Equal(t, - "error reading certificate file: unable to read file testdata/unreadable-file.txt", - errNoRead.Error(), - ) + for _, tt := range readErrorTests { + t.Run("Read error "+tt.desc, func(t *testing.T) { + t.Parallel() - _, errWrongFile := GetCertsFromBundle( - RSACaCertKeyFile, - inputReader, - ) - require.Error(t, errWrongFile) - assert.Equal(t, - "no valid certificates found in file "+RSACaCertKeyFile, - errWrongFile.Error(), - ) - }) + _, err := GetCertsFromBundle( + tt.certPath, + tt.reader, + ) + require.Error(t, err) + assert.Equal(t, + tt.expectedMsg, + err.Error(), + ) + }) + } t.Run("CertImportValidation", func(t *testing.T) { gotCerts, errCaString := GetCertsFromBundle( @@ -152,15 +172,6 @@ func TestCertinfo_GetCertsFromBundle(t *testing.T) { ) } }) - - t.Run("nil Reader error", func(t *testing.T) { - _, err := GetCertsFromBundle( - RSACaCertFile, - nil, - ) - require.Error(t, err) - require.EqualError(t, err, "nil Reader provided") - }) } func TestCertinfo_GetKeyFromFile_inputReaderErrors(t *testing.T) { @@ -447,11 +458,25 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { } func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { + uncompleteCert := x509.Certificate{ + IsCA: false, + } + matchFalseTests := []struct { - desc string - cert *x509.Certificate - key crypto.PrivateKey + desc string + cert *x509.Certificate + key crypto.PrivateKey + expectErr bool + expectMsg string }{ + { + desc: "uncomplete cert", + cert: &uncompleteCert, + key: RSASampleCertKey, + expectErr: true, + expectMsg: "unsupported public key type in certificate", + }, + { desc: "key cert mismatch", cert: RSACaCertParent, @@ -475,8 +500,15 @@ func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { tt.cert, tt.key, ) - require.NoError(t, err) - assert.False(t, match) + if !tt.expectErr { + require.NoError(t, err) + assert.False(t, match) + } + + if tt.expectErr { + require.Error(t, err) + require.EqualError(t, err, tt.expectMsg) + } }) } } diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go index 9069f9e..85df457 100644 --- a/internal/certinfo/main_test.go +++ b/internal/certinfo/main_test.go @@ -51,6 +51,7 @@ var ( RSASamplePKCS8EncryptedPrivateKey = testdataDir + "/rsa-pkcs8-encrypted-private-key.pem" RSASamplePKCS8EncBrokenPrivateKey = testdataDir + "/rsa-pkcs8-encrypted-broken-private-key.pem" RSASamplePKCS8Certificate = testdataDir + "/rsa-pkcs8-crt.pem" + RSASamplePKCS8BrokenCertificate = testdataDir + "/rsa-pkcs8-broken-crt.pem" ECDSASamplePlaintextPrivateKey = testdataDir + "/ecdsa-plaintext-private-key.pem" ECDSASampleEncryptedPrivateKey = testdataDir + "/ecdsa-encrypted-private-key.pem" diff --git a/internal/certinfo/testdata/rsa-pkcs8-broken-crt.pem b/internal/certinfo/testdata/rsa-pkcs8-broken-crt.pem new file mode 100644 index 0000000..998d31d --- /dev/null +++ b/internal/certinfo/testdata/rsa-pkcs8-broken-crt.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFbTCCA1WgAwIBAgIUBzx2EsVl+ybPvE466xTKQunu37cwDQYJKoZIhvcAAAAA +BQAwXzELMAkGA1UEBhMCREUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNBBBBB +BkJlcmxpbjEUMBIGA1UECgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1CCCCC +Y29tMB4XDTI1MTIxMjIyMDcwOFoXDTM1MTIxMDIyMDcwOFowXzELMAkGA1UEBhMC +REUxEzARBgNVBAgMClNvbWUtU3RhdGUxDzANBgNVBAcMBkJlcmxpbjEUMBIGA1UE +CgwLZXhhbXBsZSBMdGQxFDASBgNVBAMMC2V4YW1wbGUuY29tMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAtjvz7pjc6iXFHeCNEgQ9Fj/wLArQ2Rmuqhe2 +Lqozt4V8fI8euikVECxXHIF0cDoJmdyY9oU71VKW3qDPS0NcB51rUi0iyfoTPXCy +Sig6tkAewbWc03doUifTLSxSmBz5cjdrZIKK8zkQBtdd3A/B7u/ewRXuRA567vN2 +2Gjc5EkrypJUrCWYeFlfcW1UzcbEMag4X3Z3YEmQXppz3EDsm72XZ+KHA3PytKv4 +rPM4IMtQkLMCIpvQ/NdbQXcwYorVo6dszCohqI2uc6hfQ0sbNEsvGX7/PakArNr8 +JkFif5DWS54tXzfzPz3HMbDFyjAURIguHBa6mBCq/tyTQvBNxNu+N+WERMqJjrKk +MQVVbzZLeEov0cINJipI4IC09CqaHa3LUBsvJQmTRoEZJDvtKDmS6qw+eprJIHgU +R5AJ4Is6CddLHUJnPKLoU3pZdsb7a3ks2d4PiHKuRaVJVs5495d/syfZYejB+C+r +9Uyj48p4VuHknJG5JmGtEc9w5pnWdWiGfPJJCSt5C5cZhjDqIkU/g+LrhYULLQRC +yFQ9oDLWo8zGf8xXYDKNutw/qfjBrCLGApNyqW1MibSQf1JNX3sVljDZaR5k20OW +Yd00wqtubq8rw2KgoKV3KOQa/Dkq6SIFg4krbys+DqnZ2XKtL+Bq1OpAu558A76S +zKjpKT8CAwEAAaMhMB8wHQYDVR0OBBYEFAjuAcgUUDpC4pmbUICqf9TYnsXEMA0G +CSqGSIb3DQEBCwUAA4ICAQAAyrqWKwonL6ZCeuzEjCizXRv0bFSZZBKvggX1z+KS +EDJppjA7vvzC0k9HuWat+qV69xkQ3u+BpnnPa0OSasqI6sQJIMt8Az7EVeqrS074 +qedulOIQzZnbH7NOht/EKso9Gz3iP7G3NKf1JsStc9FuSiWaiqsg83iLCAe1py/t +8KM2G/vCvNPYIZAR8RwaNGgBicaOQsPW9PuNphQ9i1tcF2L0pstImoCuCzY0guRt +Ku0syZweF8RYecnKxqSoWHL++vBWHHJ85O6RKjvSYWcCcaU6SkMlgTY7n7xvztrd +U1GKlBnijObU+lIfNv9cxufy7KE9X5qQmwBbDkNPYQSif1dkCzs/3sWQq0aSvpHX +XI/inejkNtInddchh2prdBLHn1yEilQ4Bow4H05ipsnFhB9W+154JpcYI8VN67xr +xMw4DAG2S1byUIfiBW8hU5AKQE9c56SSUdci6kaFVC4FRvd1Nsd3HU/e3mLdw6J9 +iIXRnj5TBQkX7WipZKc1NBUXSWa1NNPl8oUe1BwaYG9cqOWWNbeprLBiToqPMpts +0ENmMrxPXrl71akGpj5L7A8I+9W9VtQl5BGTy1fmscqVwlHxDh1iWwV1mLFrlSS0 +wlrSh0IajUFG0/M5lvVDH+LGg1z6tDA5KaD3YE4du7JBXFJNMI+kT0cCgNSWmows +IA== +-----END CERTIFICATE----- From b2eaa11194bccc49ad5c7c8774d63c78ed5a179a Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 15:58:52 +0100 Subject: [PATCH 19/23] fix: incompleteCert typo --- internal/certinfo/common_handlers_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 03873cd..497d783 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -458,7 +458,7 @@ func TestCertinfo_getPassphraseIfNeeded(t *testing.T) { } func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { - uncompleteCert := x509.Certificate{ + incompleteCert := x509.Certificate{ IsCA: false, } @@ -471,7 +471,7 @@ func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { }{ { desc: "uncomplete cert", - cert: &uncompleteCert, + cert: &incompleteCert, key: RSASampleCertKey, expectErr: true, expectMsg: "unsupported public key type in certificate", From b0228119f6a470f67491ffc5b18cd5330ee6ee84 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 16:20:14 +0100 Subject: [PATCH 20/23] refactor: PrintCertInfo to use a Writer --- internal/certinfo/common_handlers.go | 23 ++++++++++++----------- internal/requests/requests.go | 4 ++-- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index 0511e62..3f4b2e5 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -9,28 +9,29 @@ import ( "encoding/pem" "errors" "fmt" + "io" "os" "time" "github.com/youmark/pkcs8" ) -func PrintCertInfo(cert *x509.Certificate, depth int) { +func PrintCertInfo(cert *x509.Certificate, depth int, w io.Writer) { prefix := "" for range depth { prefix += " " } - fmt.Printf("%sSubject: %s\n", prefix, cert.Subject) - fmt.Printf("%sIssuer: %s\n", prefix, cert.Issuer) - fmt.Printf("%sValid From: %s\n", prefix, cert.NotBefore.Format(time.RFC1123)) - fmt.Printf("%sValid To: %s\n", prefix, cert.NotAfter.Format(time.RFC1123)) - fmt.Printf("%sDNS Names: %v\n", prefix, cert.DNSNames) - fmt.Printf("%sIs CA: %v\n", prefix, cert.IsCA) - fmt.Printf("%sSerial Number: %s\n", prefix, cert.SerialNumber) - fmt.Printf("%sPublic Key Algorithm: %s\n", prefix, cert.PublicKeyAlgorithm) - fmt.Printf("%sSignature Algorithm: %s\n", prefix, cert.SignatureAlgorithm) - fmt.Println() + fmt.Fprintf(w, "%sSubject: %s\n", prefix, cert.Subject) + fmt.Fprintf(w, "%sIssuer: %s\n", prefix, cert.Issuer) + fmt.Fprintf(w, "%sValid From: %s\n", prefix, cert.NotBefore.Format(time.RFC1123)) + fmt.Fprintf(w, "%sValid To: %s\n", prefix, cert.NotAfter.Format(time.RFC1123)) + fmt.Fprintf(w, "%sDNS Names: %v\n", prefix, cert.DNSNames) + fmt.Fprintf(w, "%sIs CA: %v\n", prefix, cert.IsCA) + fmt.Fprintf(w, "%sSerial Number: %s\n", prefix, cert.SerialNumber) + fmt.Fprintf(w, "%sPublic Key Algorithm: %s\n", prefix, cert.PublicKeyAlgorithm) + fmt.Fprintf(w, "%sSignature Algorithm: %s\n", prefix, cert.SignatureAlgorithm) + fmt.Fprintln(w) } // Check if the PublicKey of a Certificate matches the PrivateKey. diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 966324b..e1f3992 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -251,7 +251,7 @@ func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { for i, cert := range resp.TLS.PeerCertificates { fmt.Fprintf(w, "Certificate %d:\n", i) - certinfo.PrintCertInfo(cert, 1) + certinfo.PrintCertInfo(cert, 1, os.Stdout) } for i, chain := range resp.TLS.VerifiedChains { @@ -259,7 +259,7 @@ func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { for j, cert := range chain { fmt.Fprintf(w, " Cert %d:\n", j) - certinfo.PrintCertInfo(cert, 2) + certinfo.PrintCertInfo(cert, 2, os.Stdout) } } } else { From 0248027a07e01324f69cea15accac6ce812ab0b3 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 16:20:29 +0100 Subject: [PATCH 21/23] ci: add test for PrintCertInfo --- internal/certinfo/common_handlers_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 497d783..42fc842 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -1,6 +1,7 @@ package certinfo import ( + "bytes" "crypto" "crypto/x509" "testing" @@ -565,3 +566,22 @@ func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { }) } } + +func TestCertinfo_PrintCertInfo(t *testing.T) { + t.Run("RSA Sample Certtificate", func(t *testing.T) { + buffer := bytes.Buffer{} + PrintCertInfo(RSACaCertParent, 1, &buffer) + + got := buffer.String() + + require.Contains(t, got, "Subject") + require.Contains(t, got, "Issuer") + require.Contains(t, got, "Valid From:") + require.Contains(t, got, "Valid To:") + require.Contains(t, got, "DNS Names:") + require.Contains(t, got, "Is CA:") + require.Contains(t, got, "Serial Number:") + require.Contains(t, got, "Public Key Algorithm:") + require.Contains(t, got, "Signature Algorithm:") + }) +} From 4f7c9045647cd3ae639837b6f84e1ac66c359305 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 16:28:45 +0100 Subject: [PATCH 22/23] fix: typo --- internal/certinfo/common_handlers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go index 42fc842..bf4b22e 100644 --- a/internal/certinfo/common_handlers_test.go +++ b/internal/certinfo/common_handlers_test.go @@ -568,7 +568,7 @@ func TestCertinfo_certMatchPrivateKey_matchTrue(t *testing.T) { } func TestCertinfo_PrintCertInfo(t *testing.T) { - t.Run("RSA Sample Certtificate", func(t *testing.T) { + t.Run("RSA Sample Certificate", func(t *testing.T) { buffer := bytes.Buffer{} PrintCertInfo(RSACaCertParent, 1, &buffer) From c48df9b68a6c3a6fe24c479feb95f03506cbaa00 Mon Sep 17 00:00:00 2001 From: Zeno Belli Date: Sun, 14 Dec 2025 16:43:18 +0100 Subject: [PATCH 23/23] fix: PrintResponseDebug reference Writer from input args --- internal/requests/requests.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/requests/requests.go b/internal/requests/requests.go index e1f3992..1c6f152 100644 --- a/internal/requests/requests.go +++ b/internal/requests/requests.go @@ -251,7 +251,7 @@ func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { for i, cert := range resp.TLS.PeerCertificates { fmt.Fprintf(w, "Certificate %d:\n", i) - certinfo.PrintCertInfo(cert, 1, os.Stdout) + certinfo.PrintCertInfo(cert, 1, w) } for i, chain := range resp.TLS.VerifiedChains { @@ -259,7 +259,7 @@ func (r *RequestConfig) PrintResponseDebug(w io.Writer, resp *http.Response) { for j, cert := range chain { fmt.Fprintf(w, " Cert %d:\n", j) - certinfo.PrintCertInfo(cert, 2, os.Stdout) + certinfo.PrintCertInfo(cert, 2, w) } } } else {