From 7427ea33c74ca47034f2231d33826f3964b92408 Mon Sep 17 00:00:00 2001 From: Rajiv Kushwaha Date: Thu, 1 Jun 2023 13:34:59 +0530 Subject: [PATCH] feat(common.tls): Add support for passphrase-protected private key (#13262) --- docs/TLS.md | 4 +++ go.mod | 2 +- plugins/common/tls/config.go | 52 +++++++++++++++++++++++++---- plugins/common/tls/config_test.go | 50 +++++++++++++++++++++++++++ testutil/pki/clientenckey.pkcs8.pem | 30 +++++++++++++++++ testutil/pki/clientkey.pkcs8.pem | 28 ++++++++++++++++ testutil/pki/tls-certs.sh | 7 ++-- testutil/tls.go | 10 +++++- 8 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 testutil/pki/clientenckey.pkcs8.pem create mode 100644 testutil/pki/clientkey.pkcs8.pem diff --git a/docs/TLS.md b/docs/TLS.md index 7938779ba..47bd4e35c 100644 --- a/docs/TLS.md +++ b/docs/TLS.md @@ -22,6 +22,8 @@ For client TLS support we have the following options: ## contain intermediate certificates. # tls_cert = "/etc/telegraf/cert.pem" # tls_key = "/etc/telegraf/key.pem" +# passphrase for encrypted private key, if it is in PKCS#8 format. Encrypted PKCS#1 private keys are not supported. +# tls_key_pwd = "changeme" ## Skip TLS verification. # insecure_skip_verify = false ## Send the specified TLS server name via SNI. @@ -47,6 +49,8 @@ The server TLS configuration provides support for TLS mutual authentication: ## Add service certificate and key. # tls_cert = "/etc/telegraf/cert.pem" # tls_key = "/etc/telegraf/key.pem" +# passphrase for encrypted private key, if it is in PKCS#8 format. Encrypted PKCS#1 private keys are not supported. +# tls_key_pwd = "changeme" ``` #### Advanced Configuration diff --git a/go.mod b/go.mod index e762e5d39..4051847c0 100644 --- a/go.mod +++ b/go.mod @@ -426,7 +426,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg/stringprep v1.0.3 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect diff --git a/plugins/common/tls/config.go b/plugins/common/tls/config.go index 8301b9824..2b65ccb72 100644 --- a/plugins/common/tls/config.go +++ b/plugins/common/tls/config.go @@ -1,13 +1,17 @@ package tls import ( + "crypto/rsa" "crypto/tls" "crypto/x509" + "encoding/pem" + "errors" "fmt" "os" "strings" "github.com/influxdata/telegraf/internal/choice" + "github.com/youmark/pkcs8" ) const TLSMinVersionDefault = tls.VersionTLS12 @@ -108,7 +112,7 @@ func (c *ClientConfig) TLSConfig() (*tls.Config, error) { } if c.TLSCert != "" && c.TLSKey != "" { - err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey) + err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd) if err != nil { return nil, err } @@ -153,7 +157,7 @@ func (c *ServerConfig) TLSConfig() (*tls.Config, error) { } if c.TLSCert != "" && c.TLSKey != "" { - err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey) + err := loadCertificate(tlsConfig, c.TLSCert, c.TLSKey, c.TLSKeyPwd) if err != nil { return nil, err } @@ -206,23 +210,57 @@ func (c *ServerConfig) TLSConfig() (*tls.Config, error) { func makeCertPool(certFiles []string) (*x509.CertPool, error) { pool := x509.NewCertPool() for _, certFile := range certFiles { - pem, err := os.ReadFile(certFile) + cert, err := os.ReadFile(certFile) if err != nil { return nil, fmt.Errorf("could not read certificate %q: %w", certFile, err) } - if !pool.AppendCertsFromPEM(pem) { + if !pool.AppendCertsFromPEM(cert) { return nil, fmt.Errorf("could not parse any PEM certificates %q: %w", certFile, err) } } return pool, nil } -func loadCertificate(config *tls.Config, certFile, keyFile string) error { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) +func loadCertificate(config *tls.Config, certFile, keyFile, privateKeyPassphrase string) error { + certBytes, err := os.ReadFile(certFile) if err != nil { - return fmt.Errorf("could not load keypair %s:%s: %w", certFile, keyFile, err) + return fmt.Errorf("could not load certificate %q: %w", certFile, err) } + keyBytes, err := os.ReadFile(keyFile) + if err != nil { + return fmt.Errorf("could not load private key %q: %w", keyFile, err) + } + + keyPEMBlock, _ := pem.Decode(keyBytes) + if keyPEMBlock == nil { + return errors.New("failed to decode private key: no PEM data found") + } + + var cert tls.Certificate + if keyPEMBlock.Type == "ENCRYPTED PRIVATE KEY" { + if privateKeyPassphrase == "" { + return errors.New("missing password for PKCS#8 encrypted private key") + } + var decryptedKey *rsa.PrivateKey + decryptedKey, err = pkcs8.ParsePKCS8PrivateKeyRSA(keyPEMBlock.Bytes, []byte(privateKeyPassphrase)) + if err != nil { + return fmt.Errorf("failed to parse encrypted PKCS#8 private key: %w", err) + } + cert, err = tls.X509KeyPair(certBytes, pem.EncodeToMemory(&pem.Block{Type: keyPEMBlock.Type, Bytes: x509.MarshalPKCS1PrivateKey(decryptedKey)})) + if err != nil { + return fmt.Errorf("failed to load cert/key pair: %w", err) + } + } else if keyPEMBlock.Headers["Proc-Type"] == "4,ENCRYPTED" { + // The key is an encrypted private key with the DEK-Info header. + // This is currently unsupported because of the deprecation of x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock. + return fmt.Errorf("password-protected keys in pkcs#1 format are not supported") + } else { + cert, err = tls.X509KeyPair(certBytes, keyBytes) + if err != nil { + return fmt.Errorf("failed to load cert/key pair: %w", err) + } + } config.Certificates = []tls.Certificate{cert} return nil } diff --git a/plugins/common/tls/config_test.go b/plugins/common/tls/config_test.go index 8a50834e7..03bbb9b82 100644 --- a/plugins/common/tls/config_test.go +++ b/plugins/common/tls/config_test.go @@ -44,6 +44,56 @@ func TestClientConfig(t *testing.T) { TLSKeyPwd: "", }, }, + { + name: "success with unencrypted pkcs#8 key", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientPKCS8KeyPath(), + }, + }, + { + name: "encrypted pkcs#8 key but missing password", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientEncPKCS8KeyPath(), + }, + expNil: true, + expErr: true, + }, + { + name: "encrypted pkcs#8 key and incorrect password", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientEncPKCS8KeyPath(), + TLSKeyPwd: "incorrect", + }, + expNil: true, + expErr: true, + }, + { + name: "success with encrypted pkcs#8 key and password set", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientEncPKCS8KeyPath(), + TLSKeyPwd: "changeme", + }, + }, + { + name: "error with encrypted pkcs#1 key and password set", + client: tls.ClientConfig{ + TLSCA: pki.CACertPath(), + TLSCert: pki.ClientCertPath(), + TLSKey: pki.ClientEncKeyPath(), + TLSKeyPwd: "changeme", + }, + expNil: true, + expErr: true, + }, + { name: "invalid ca", client: tls.ClientConfig{ diff --git a/testutil/pki/clientenckey.pkcs8.pem b/testutil/pki/clientenckey.pkcs8.pem new file mode 100644 index 000000000..1a59c8be3 --- /dev/null +++ b/testutil/pki/clientenckey.pkcs8.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI0QKPLrW1mlACAggA +MBQGCCqGSIb3DQMHBAhz7px4LL4+gASCBMi4KiB3IZOQfDiRdfnQHHpdS8cOk7fc +0fEd6EnDeb7wczkUg0NDerrsfvV6JVzvmFIYB2Eqmg1mW20sP3U6tXKke7zn1BnF +3SFpMT5i4/YoiULPmkDK07N+36VqfBCqOscVGm32DXBBqNHLi1xYoTUQA5BCxdka +s8NAArHmSHOZGOODa46LKmepBQBoI+vvoYrSlWX2Zq/KHjrhtWZy25bJP78xhvoU +GDvzbmglXaqfWLGYBZRYw9ixkSNu2QkyKCJZ1QYAX2pbdloMmYfSQ9RRCKIKQqqY +1C7AswWcqWQDlqoNTpmn51Ss6h6M8oLzUYdER11+KsEVViWeOADCeEb0V5mbVJqC +F1ltVgI+JNY8DgStrshZzEMLYMtjaa2u64Xd/XIhShbNBZ03rRdg0Ev56NSPDM70 +w16inaCMUumEIR5nxAswfxO6+ukVxlRLwn/+KtSyajpCjeg/D258ryLM91702CAF +8D5VaJ80q1ZVoMWDgJnB3Oy2aE8EtIWPXQIfsLk0Fy62EjhO99EhE+rkuFzma9Px +ICFN3HIKxsTPLbOnduqFZOUp8rRTaju6KGsXOXgHReBAl/14rEJ+YN+dasMhhEi7 +VNwdjodkOeNpM13G3D2sA92LqnWYBbiHS/AJykoyWUSxdcMUNH6PBa1ASWGgMo9o +rB/HBbxbEGgiGYMqAUZ89HB//J3fxAV7+kDF9feGLk2hlpdh9FMAYs9+L/Bjsz5e +caxmP1go8qapvNhDScKAuaR/2JHlptowE9mRw92UaMB4WgNmzkqu3KxBRDM12iXv +2OJEKo6ntHVvOwN0ZAQsqyosPmmXXnif245ubGzZYxkuOA/mHckZ8aFwn+CE1xwi +NHmk3pXnbpwFeh4a/4yOh7vcR25NyS9zhOgXhfIRz4E+GrHzTQbpLgOlpoCm43tG +PDdyc0qsFQ2NcDSZBto2KJzukWYj8u2zoIEvYCDF90t81y6P3CVgPaIXW1J2Qmhi +esnxWhJdDlSS0fDZbfhDpD4zz0tHwd45OdMpg+x8fKJXi/K14fDMNb1LjvdyhmvE +cZRwevapOL9NtoVHoaYTLOCJm1RUmKUcjAoOagq0jMNtM1SbtMsenWoioyj6RItE +G9b3oSqfYjOmp0RBeR5ryEdJPliPuf8xvvoC6AntKlHEmyasSOf/k+t/X1IKYrfZ +Rird+/24P15NjblfBRXYudVCIeepuoJGwm7bad2cD4WB6j9soIAGv7XThamo7tGo +/IAJ1U8tSNXSR+DihXme1ZfEzbxhCkJX0HLDUZ1P2JSpzaVquE8cRd24uP5utNgY +nZsQRlD/O1avpVlja10VtAC44FNrF714E92t15C97JXnqe9iHEeAKmCNX0s7eM0r +qSPaVkVUkPom/QPE3oYGzI73TvgktJ+RXz3Z5t395MZH0ldHQ/imRGw+K8St5Hte ++uNy2JNQWBAmM/eCK6zIQTrJnuVnAJPgk5pTI92sUbMx60BHTlfDwIZLSmsVPbQp +hDIjrFHnKGda4hQ8gRiVFYW11ROcVF5rNLfHIcse7WaQ0vX+6P9C/xJgit6CVoOV +ZdtAB7sNRYy1ttlZv5LTi31tGrvJi1Waeu+ZL4Tr3Tm0YH3RrAtbkRf9XXxCTd3D +0yg= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/testutil/pki/clientkey.pkcs8.pem b/testutil/pki/clientkey.pkcs8.pem new file mode 100644 index 000000000..9eb720c11 --- /dev/null +++ b/testutil/pki/clientkey.pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDAU4sKV0pZNr8G +fxGAg5C3+gibh3Wn8FKZmdZ4ND1CGSxATDvC2+wyE5h5x+DCtmFGxrMnBF4CPoyw +HiOgW+vODQQ8rKXc9qR2e1yUl3gLu43IOr5zD5uUgU+TrjKh+mBzAwjuHQUSm+7c +L/LuRMqbCRP89fXtGWb5aiLs2rrlD4ttOGDivkrlJw0CweybbF5k5Fjzpd56xoT/ +WjlGSXuP+7ovw5ui1jSrvNDAzEmq1q+vMDInD5OHiizk2zHKHrhwU99iZE53cpZN +3/Vn2lZvxDcArJZuwVhWr94PVeNdleuF65PGMl1mJAYJj8U4ZTmmDEMEzfluru8+ +byKdsAezAgMBAAECggEABe2NR5BRNla9pJGeFHv5pfIK+eA+/CIoKZvYRHQ3vyXP +e1veK69jpFd+R6KlhtrgPLI7K2SzERakd3k2V+b9aah1DPm1Uw43G3/J5rqUdKgf +QqCs5hcBEJ67uS2RWfTBrEnXqJ80XNJ5lkfUEQwFwqmQ+CcMoXY6+k0PLoW/ZPf7 +7BgiN9tgCFpmxpbIvAzopfuoupMdmsetHME1VobqdR9N9cwWiNAvmfied/sLuBoW +4H9fef0HJDZqvatPdhdmYWsypzVr5Z8UTiefdxwsvss0IHbEWwH7uCXWf/HTzSdb +qUyMlVP9jtLif8yd+or2pCiBnLOs1Sy6UjUhgveUQQKBgQD4bfzufOgGOj/oPyQv +FJryfpxJDZgTYxSMhu6+OjeG/1Yri9T9+Wm8pk74DKWbLpzWafHTpdQGHmwN1bze +h+0L3vPzPFDVa3ff0F9YmQmdabSvezBLaAvooWY+S6cH+y2QzT03epAbduam5jxF +ObmPLAXJoo3fB7Bjmkq1rwPHaQKBgQDGL+P2M3Lsfv+WMOpfYp4iBzFp2WZ7xWj8 +eKH2xZMZDyf0ZNAoclSXLXLcLR2YSc+FzlYyvHrGiV0dBToQKoM66WFvI4guJHEX +KFjd2wb7j/PI9SaEl2T+5kM0k3uwbKccSSEx+3dPQ9dPlwSzTSAcI/SB2TdKc/oM +B5H1SMSuuwKBgHQX4hUI5dzTJ74+k4g01tvvctVotuKIcLfi7nqS9hPuCrS62cP9 +4/22sjyUnGdSdxRz2gCQA+8AFHq6oVJUrt/XRqUqUJQLbq92zre2fJnFJhzDMqRq +cPY7Rm5iKCJArOB4KN1eNy2+eLcR7KRk/2P6rJG3lxjcnP9OwQ76RIfRAoGAbSfz +ZqbvpVJNc2t9qwumDRfpH2OeDo2sRWIr0r7/Rc4TM4hTHfPao2lk2d//bOE6a3g6 +AbfN54vAvTsjv8Oqg8gG4i+v0bpAj4CpcYgxUFz46LYdgOnhtoMcgNi6R3GQmQOu +RNk73WyAKlYDQL458UXcZag9y8QU9Is22OI7cgECgYAjlK7hACJ9UkNlrJSHDt1X +6WcxKPib/1gpDOgujHPKzyuyzjhR+cvsUs6DUr3diHY66wYi3UgMFvNQ17OKsmuj +ZxK0BQMDw0v6ZZr7EnD0MK/jndShPtxXbJCc/mendstW1atsGcocaHHxkyQy7PmG +CrJm+I0R+zTdec261fjsjQ== +-----END PRIVATE KEY----- diff --git a/testutil/pki/tls-certs.sh b/testutil/pki/tls-certs.sh index 450b66e80..67cd0cfa6 100644 --- a/testutil/pki/tls-certs.sh +++ b/testutil/pki/tls-certs.sh @@ -77,8 +77,11 @@ openssl ca -config ./openssl.conf -in ./certs/servercsr.pem -out ./certs/serverc openssl genrsa -out ./private/clientkey.pem 2048 && openssl req -new -key ./private/clientkey.pem -out ./certs/clientcsr.pem -outform PEM -subj "/CN=$(cat /proc/sys/kernel/hostname)/O=client/" && openssl ca -config ./openssl.conf -in ./certs/clientcsr.pem -out ./certs/clientcert.pem -notext -batch -extensions client_ca_extensions && -cp ./private/clientkey.pem ./private/clientkeyenc.pem && -ssh-keygen -p -f ./private/clientkeyenc.pem -m PEM -N 'changeme' +cp ./private/clientkey.pem ./private/clientenckey.pem && +ssh-keygen -p -f ./private/clientenckey.pem -m PEM -N 'changeme' && +# Generate a pkcs#8 encrypted private key using pkcs#5 v2.0 algorithm +openssl pkcs8 -topk8 -v2 des3 -in ./private/clientkey.pem -out ./private/clientenckey.pkcs8.pem -passout pass:changeme && +openssl pkcs8 -topk8 -in clientenckey.pem -passin pass:changeme -nocrypt -out clientkey.pkcs8.pem && # Combine crt and key to create pem formatted keyfile cat ./certs/clientcert.pem ./private/clientkey.pem > ./private/client.pem && diff --git a/testutil/tls.go b/testutil/tls.go index a3cf66980..13ce22069 100644 --- a/testutil/tls.go +++ b/testutil/tls.go @@ -77,7 +77,15 @@ func (p *pki) ClientCertAndKeyPath() string { } func (p *pki) ClientEncKeyPath() string { - return path.Join(p.keyPath, "clientkeyenc.pem") + return path.Join(p.keyPath, "clientenckey.pem") +} + +func (p *pki) ClientPKCS8KeyPath() string { + return path.Join(p.keyPath, "clientkey.pkcs8.pem") +} + +func (p *pki) ClientEncPKCS8KeyPath() string { + return path.Join(p.keyPath, "clientenckey.pkcs8.pem") } func (p *pki) ClientCertAndEncKeyPath() string {