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/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/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.go b/internal/certinfo/certinfo.go index 530dfbf..07e4c23 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,34 @@ type CertinfoConfig struct { TLSInsecure bool } +type ( + Reader interface { + ReadFile(name string) ([]byte, error) + ReadPassword(fd int) ([]byte, error) + } + + InputReader struct{} +) + var ( - // TODO: remove - // certsBundle []*x509.Certificate - // privKey any - // tlsEndpoint string TlsServerName string TlsInsecure bool + inputReader InputReader ) +func (InputReader) ReadFile(name string) ([]byte, error) { + file, err := os.ReadFile(name) + if err != nil { + return nil, err + } + + return file, nil +} + +func (InputReader) ReadPassword(fd int) ([]byte, error) { + return term.ReadPassword(fd) +} + func NewCertinfoConfig() (*CertinfoConfig, error) { defaultCertPool, err := x509.SystemCertPool() if err != nil { @@ -54,9 +77,12 @@ func NewCertinfoConfig() (*CertinfoConfig, error) { return &c, nil } -func (c *CertinfoConfig) SetCaPoolFromFile(filePath string) error { - if filePath != "" { - caCertsPool, err := GetRootCertsFromFile(filePath) +func (c *CertinfoConfig) SetCaPoolFromFile(filePath string, fileReader Reader) error { + if filePath != emptyString { + caCertsPool, err := GetRootCertsFromFile( + filePath, + fileReader, + ) if err != nil { return err } @@ -68,9 +94,9 @@ func (c *CertinfoConfig) SetCaPoolFromFile(filePath string) error { return nil } -func (c *CertinfoConfig) SetCertsFromFile(filePath string) error { - if filePath != "" { - certs, err := GetCertsFromBundle(filePath) +func (c *CertinfoConfig) SetCertsFromFile(filePath string, fileReader Reader) error { + if filePath != emptyString { + certs, err := GetCertsFromBundle(filePath, fileReader) if err != nil { return err } @@ -82,9 +108,13 @@ func (c *CertinfoConfig) SetCertsFromFile(filePath string) error { return nil } -func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string) error { - if filePath != "" { - keyFromFile, err := GetKeyFromFile(filePath) +func (c *CertinfoConfig) SetPrivateKeyFromFile(filePath string, fileReader Reader) error { + if filePath != emptyString { + keyFromFile, err := GetKeyFromFile( + filePath, + privateKeyPwEnvVar, + fileReader, + ) if err != nil { return err } 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/certinfo_test.go b/internal/certinfo/certinfo_test.go new file mode 100644 index 0000000..45de323 --- /dev/null +++ b/internal/certinfo/certinfo_test.go @@ -0,0 +1,69 @@ +package certinfo + +import ( + "fmt" + "testing" + + "github.com/alecthomas/assert/v2" + "github.com/stretchr/testify/require" +) + +func TestNewCertinfoConfig(t *testing.T) { + t.Run("NewCertinfoConfig", func(t *testing.T) { + t.Parallel() + + cc, err := NewCertinfoConfig() + require.NoError(t, err) + + require.NotNil(t, cc.CACertsPool) + }) +} + +type mockReader struct { + readError error +} + +func (mr mockReader) ReadFile(name string) ([]byte, error) { + mr.readError = fmt.Errorf("unable to read file %s", name) + return nil, mr.readError +} + +func TestCertinfo_SetCaPoolFromFile(t *testing.T) { + t.Run("FileReadErrors", func(t *testing.T) { + t.Parallel() + + cc, err := NewCertinfoConfig() + require.NoError(t, err) + + errEmpty := cc.SetCaPoolFromFile(emptyString, inputReader) + require.NoError(t, errEmpty, "error emptyString") + + errNoRead := cc.SetCaPoolFromFile(unreadableFile, mockErrReader) + require.Error(t, errNoRead, "error unreadableFile") + assert.Equal(t, + "failed to read CA bundle file: unable to read file testdata/unreadable-file.txt", + errNoRead.Error(), + "check unreadableFile", + ) + + errNoExist := cc.SetCaPoolFromFile("testdata/not-exist", inputReader) + require.Error(t, errNoExist, "error file not-exist") + assert.Equal(t, + "failed to read CA bundle file: open testdata/not-exist: no such file or directory", + errNoExist.Error(), + "read not-exist file", + ) + + errWrongCert := cc.SetCaPoolFromFile(RSACaCertKeyFile, inputReader) + require.Error(t, errWrongCert, "error wrong cert") + assert.Equal(t, + "unable to create CertPool from file", + errWrongCert.Error(), + "check wrong cert", + ) + + // TODO: complete, compare struct data with input + errRSACACert := cc.SetCaPoolFromFile(RSACaCertFile, inputReader) + require.NoError(t, errRSACACert, "error generated RSACaCertFile") + }) +} diff --git a/internal/certinfo/common_handlers.go b/internal/certinfo/common_handlers.go index b76eda4..3f4b2e5 100644 --- a/internal/certinfo/common_handlers.go +++ b/internal/certinfo/common_handlers.go @@ -9,78 +9,29 @@ import ( "encoding/pem" "errors" "fmt" + "io" "os" "time" "github.com/youmark/pkcs8" - "golang.org/x/term" ) -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() -} - -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 + 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. @@ -115,35 +66,51 @@ 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") + } + + if fileReader == nil { + return nil, errors.New("nil Reader provided") + } - 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 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) } @@ -180,15 +147,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,11 +166,15 @@ 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 } + if pwReader == nil { + return nil, errors.New("nil Reader provided") + } + pkeyEnvPw := os.Getenv(pwEnvKey) if pkeyEnvPw != "" { return []byte(pkeyEnvPw), nil @@ -220,7 +182,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 +208,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 +216,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 +256,29 @@ 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, + 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 } - key, err := ParsePrivateKey(keyPEM, privateKeyPwEnvVar) + key, err := ParsePrivateKey( + keyPEM, + keyPwEnvVar, + inputReader, + ) if err != nil { return nil, err } diff --git a/internal/certinfo/common_handlers_test.go b/internal/certinfo/common_handlers_test.go new file mode 100644 index 0000000..bf4b22e --- /dev/null +++ b/internal/certinfo/common_handlers_test.go @@ -0,0 +1,587 @@ +package certinfo + +import ( + "bytes" + "crypto" + "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, + ) + } + }) + + 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) { + 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) { + 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", + }, + } + + for _, tt := range readErrorTests { + t.Run("Read error "+tt.desc, func(t *testing.T) { + t.Parallel() + + _, 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( + 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_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 ECDSA key import", + expectError: false, + keyFile: ECDSASamplePlaintextPrivateKey, + }, + { + desc: "Encrypted ECDSA key import", + expectError: false, + keyFile: ECDSASampleEncryptedPrivateKey, + needEnv: true, + keyPw: samplePrivateKeyPassword, + }, + { + desc: "Encrypted broken ECDSA 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() + + _, errNoRead := GetKeyFromFile( + unreadableFile, + privateKeyPwEnvVar, + mockErrReader, + ) + require.Error(t, errNoRead) + assert.Equal(t, + "unable to read file testdata/unreadable-file.txt", + 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) { + t.Run("Plain RSA key import", func(t *testing.T) { + got, err := GetKeyFromFile( + RSACaCertKeyFile, + privateKeyPwEnvVar, + inputReader, + ) + require.NoError(t, err) + + if diff := cmp.Diff(RSACaCertKey, got); diff != "" { + t.Errorf( + "GetKeyFromFile key mismatch (-want +got):\n%s", + diff, + ) + } + }) +} + +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. + // 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) { + 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) { + 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) + require.ErrorContains(t, + err, + "error reading passphrase:", + ) + }) + + 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: mockErrReader: unable to read password", + ) + }) + + 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, + privateKeyPwEnvVar, + mockInputReader, + ) + require.NoError(t, err) + assert.Equal(t, + []byte(samplePrivateKeyPassword), + pw, + ) + }) +} + +func TestCertinfo_certMatchPrivateKey_matchFalse(t *testing.T) { + incompleteCert := x509.Certificate{ + IsCA: false, + } + + matchFalseTests := []struct { + desc string + cert *x509.Certificate + key crypto.PrivateKey + expectErr bool + expectMsg string + }{ + { + desc: "uncomplete cert", + cert: &incompleteCert, + key: RSASampleCertKey, + expectErr: true, + expectMsg: "unsupported public key type in certificate", + }, + + { + 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, + ) + if !tt.expectErr { + require.NoError(t, err) + assert.False(t, match) + } + + if tt.expectErr { + require.Error(t, err) + require.EqualError(t, err, tt.expectMsg) + } + }) + } +} + +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: "ECDSA", + 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) + + key, err := GetKeyFromFile( + tt.keyFile, + privateKeyPwEnvVar, + inputReader, + ) + require.NoError(t, err) + + match, err := certMatchPrivateKey( + certs[0], + key, + ) + require.NoError(t, err) + assert.True(t, match) + }) + } +} + +func TestCertinfo_PrintCertInfo(t *testing.T) { + t.Run("RSA Sample Certificate", 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:") + }) +} diff --git a/internal/certinfo/main_test.go b/internal/certinfo/main_test.go new file mode 100644 index 0000000..85df457 --- /dev/null +++ b/internal/certinfo/main_test.go @@ -0,0 +1,344 @@ +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{} + + MockInputReader struct{} +) + +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" + RSASamplePKCS8BrokenCertificate = testdataDir + "/rsa-pkcs8-broken-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.MkdirAll(testdataDir, 0o755); errDataDir != nil { + panic(errDataDir) + } + + // Cleanup (register early so panics in setup still clean up what was created) + 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(), + ) + } + } + }() + + systemCertPool, _ = x509.SystemCertPool() + caCertPool = x509.NewCertPool() + + generateRSACaData() + caCertPool.AppendCertsFromPEM(RSACaCertPEM) + + generateRSACertificateData() + + m.Run() +} + +func (MockInputReader) ReadPassword(_ int) ([]byte, error) { + return []byte(samplePrivateKeyPassword), nil +} + +func (MockInputReader) ReadFile(name string) ([]byte, error) { + return nil, fmt.Errorf("unable to read file %s", name) +} + +func (MockErrReader) ReadFile(name string) ([]byte, error) { + return nil, fmt.Errorf("unable to read file %s", name) +} + +func (MockErrReader) ReadPassword(fd int) ([]byte, error) { + return func(_ int) ([]byte, error) { + return []byte{}, errors.New("mockErrReader: unable to read password") + }(fd) +} + +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 update the template with the proper fields + // use the CA cert key for signing + // and do not reference any previous parent Certificate + if tpl.isCA { + 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, + } + certParent = &template + } + + 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, +) (filePath string, err error) { + f, err := os.CreateTemp(tempDir, filePattern) + if err != nil { + return emptyString, err + } + + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = errors.Join(err, closeErr) + } + }() + + if err = os.WriteFile(f.Name(), fileContent, 0o600); 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 +} diff --git a/internal/certinfo/testdata/README.md b/internal/certinfo/testdata/README.md new file mode 100644 index 0000000..f713ff3 --- /dev/null +++ b/internal/certinfo/testdata/README.md @@ -0,0 +1,161 @@ +# 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 + +(REDACTED PEM Block) +(REDACTED PEM Block) +``` + +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----- +(REDACTED PEM Block) +(REDACTED PEM Block) +``` + +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----- +(REDACTED PEM Block) +``` +## 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----- +(REDACTED PEM Block) +``` + +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 + +(REDACTED PEM Block) +(REDACTED PEM Block) +``` + +## 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----- +(REDACTED PEM Block) +``` + +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----- +(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" +``` 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-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----- 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----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC2O/PumNzqJcUd +4I0SBD0WP/AsCtDZGa6qF7YuqjO3hXx8jx66KRUQLFccgXRwOgmZ3Jj2hTvVUpbe +oM9LQ1wHnWtSLSLJ+hM9cLJKKDq2QB7BtZzTd2hSJ9MtLFKYHPlyN2tkgorzORAG +113cD8Hu797BFe5EDnru83bYaNzkSSvKklSsJZh4WV9xbVTNxsQxqDhfdndgSZBe +mnPcQOybvZdn4ocDc/K0q/is8zggy1CQswIim9D811tBdzBiitWjp2zMKiGoja5z +qF9DSxs0Sy8Zfv89qQCs2vwmQWJ/kNZLni1fN/M/PccxsMXKMBREiC4cFrqYEKr+ +3JNC8E3E27435YREyomOsqQxBVVvNkt4Si/Rwg0mKkjggLT0KpodrctQGy8lCZNG +gRkkO+0oOZLqrD56mskgeBRHkAngizoJ10sdQmc8ouhTell2xvtreSzZ3g+Icq5F +pUlWznj3l3+zJ9lh6MH4L6v1TKPjynhW4eSckbkmYa0Rz3DmmdZ1aIZ88kkJK3kL +lxmGMOoiRT+D4uuFhQstBELIVD2gMtajzMZ/zFdgMo263D+p+MGsIsYCk3KpbUyJ +tJB/Uk1fexWWMNlpHmTbQ5Zh3TTCq25uryvDYqCgpXco5Br8OSrpIgWDiStvKz4O +qdnZcq0v4GrU6kC7nnwDvpLMqOkpPwIDAQABAoICAADFiTdh61GMfmStfjtszCtr +cuNk2lSJAM3Ukt9othZZWNkYLAuiv+WVIntK6wjMnhzKK9WjY8j39A6WRLpvWxTn +LDypV0PerLUuZHre57y80cSFNdhJYaYzZ/gBf4oIWWAL7wb6bIcyHyjAQbP8Vxhh +aKKNqrgcDZY0brTk4MaQrsBJVVVzRU2BJ1/OFDVY2ZzSNwbkdAXy2cskwUfV+IqC +R4FXmrW/iHAOaFunHf45jipGr8Qgv8ZKTSceT5SjPNtYCqRkq0bQ7sOrm1eoGkHi +7ZyghokmzYtt0/J/hWC/5sBkN5H3JS8H3m+N7ZB5GqTdpMBWskyRiX/RxEfcx5+D +bon5o6BpCFtj8Tpm2OVQGavUbMiO5ZKdAB8rOznZ7nKDwXO04tgORRcCifZnbe6f +6ujm0JtSjE2WBGgF2wQgpaTKqe2qJTXLpwb/jSh7FR3Re8TNOwUMaGxpftf00IDq +k5IS0fMFfXN2k7RLHPj1v8Y1f7yoQs8td8bmtuaQeUBxXe32LFka8qyycPlvNSC8 +DMv0MmR9fZ8Iusvdll6sP2KM6bfArU3t1/OY2NYCboWc9IboWtx3ZyASctKfQEOn +rMS1YIz8ogWtOp8cugIJo8ceXrtp+lc8yTrJbNDnKrqHQsZ5kFvv6M3fmH8040FS +plH6iLqf3cXzJB5TqPIdAoIBAQDsQiJelAvYJFcnMlmbKbPHGzJ63NVLeMEY699c +07VTiza/q2sZhmbRstv46kjS7mKlU/ItqLLJul2fY1KMT1h9X6LnBgqn7jVpfNFg +ckyqNRCR3eBQWGg77xyoHrBZc8f9EE5GwOfjJgi2TURNPQK/7zRZqwsKSYcRH/4X +nyDMvofHJAcLmUuLfW6+5lQ2+tHet7Pd97KYvvfzJFXsvQa/pJ14W/calCcbrGdH +5ZamwO8WVVefiuXOGZ4sNq9eBQ245MjkBj74aHOnbkRO2t/qEF4YeOKFDlYssRhO +iNunMiBgCSg2cmpXBm3+3QDbnknMY/q3/3+xwIAalM9tw6LVAoIBAQDFdixtA4y/ +3w+Zomry5BbEiVxSrd7Gt+cTc+lKQvTknt2EhspnFwnClPjxYEPBP0xn7uDDTiiJ +6FpOUeZ5UHT964Z7XiOaIIVGf6oohOLbL507o5V15P6bye9n8X+Qu6HsFyeh4buT +s/bUFXhuUg4KUdJ7XATYphuZeOb7zr91n525BtPnUsUxe7WlTZoNtivL+qm9mT6B +LqnnTPJH6185Be4Rv0DVsDalhSEGznIX2t+qD+3xz/GBIeBk7kX4E9KBr0uNKYvE +MLPymUVdWLgu20ocJBYtmIzGwdihAek8DkiJbog2vStTw5cS5B7O5/VjO1FWCqix +yQ97VpS6qh3DAoIBAF5pCyz6QkVsODucnjwoDQRatPBuAPo41gkscMS6gTJSZl1v +lcvMbpgLvmWY0eMgieEBS7y9NjzwsWr5GMqjBnyLVCC3xFhZE5UE4PKGmmZF8Eop +/PJ+caDJq4zvPo9wehGBi5YwjjmyQDcBZ3LqF4g1gZj3heDlgwjv3Oz5lPTciouk +tQAIkSy7wh6dEjgqQOWcSU9BJWhun8zUz1VcMC8zDA5FydggLgSb/2W2tF4MqT4m +em5x5eVXTWLjs26B3HrvSev9JvrDWrwkjz1nr6TsR33GCNcc5IjVeB4iCIymtlNK +NXyUejQuiJQZIregaGM57L3MZu3UPBzjLITzccUCggEBAKelKhivz9rC7gK5E2X+ +YpogKOMFPUOntScd3O4wpKxJeLbgnY0i0SWDx0tfOIckT6FZ/Tez7tUPEUS3Yh6T +QZCnWzj4du+PNrfhAb1+/P+skCinPOioL8ZijsDIF07xDotcKUjWVqQTQbdUNzgq +sELwruwO9wpdFMebDziwknqxtn31nwkhi8v0RQcAOI/1n3+B5ITVS2zwCk4XVQfc +fK70KuikYM5L2klzGtQliFZafFRtS85mhprlAReP+JWjdrCl/FXv9Prvgoewu2SC +Q3V8tpHnzXCOuM6ym1IvRi63zbvovPsEZnmqcGQYi1ONOhgyr16GlCzFEEDeBE3q +ynMCggEASADabtmeRGKvQBxcc0oFw8iw/oYvZIIuIqj5n3T3ProNvtY1I7acs0Ve +4KGwDm7l7u2E6n94XpmmhWl9rEKYZT2aq/bqC7WqschdxKAoGX0JAV/Uojl1TZgW +OxBru0srw5NOQ7DDMOJnJD/hI4iGz6/hxI1sFQ9e539sTaH2w+T/APDvE07a/xuM +qcOrewxS1RMbV43RUGjokAXh+hfQr06Ndgco9wt8DkGGiWX66BPp8CNmY6LyxTPy +2O1jzoxMrIwVlaEdG+Rp52/e5QSL/CepiqLS+op2fSDXljSmAoR0Mp2om02nZqyf +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?" diff --git a/internal/requests/requests.go b/internal/requests/requests.go index 7cb3972..1c6f152 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 } @@ -248,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, w) } for i, chain := range resp.TLS.VerifiedChains { @@ -256,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, w) } } } else { diff --git a/internal/requests/requests_test.go b/internal/requests/requests_test.go index 95630ad..e90b6b9 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) { @@ -22,11 +23,7 @@ func TestNewRequestsMetaConfig(t *testing.T) { t.Parallel() rmc, err := NewRequestsMetaConfig() - if err != nil { - require.Error(t, err, "error when calling NewRequestsMetaConfig()") - } - - assert.NoError(t, err) + require.NoError(t, err) var i any = rmc @@ -147,8 +144,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