feat(common.tls): Add support for passphrase-protected private key (#13262)
This commit is contained in:
parent
6c49584355
commit
7427ea33c7
|
|
@ -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
|
||||
|
|
|
|||
2
go.mod
2
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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-----
|
||||
|
|
@ -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-----
|
||||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue