Add capability to limit TLS versions and cipher suites (#6246)
This commit is contained in:
committed by
Daniel Nelson
parent
fbfaf767f1
commit
149d221191
34
internal/tls/common.go
Normal file
34
internal/tls/common.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package tls
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
var tlsVersionMap = map[string]uint16{
|
||||
"TLS10": tls.VersionTLS10,
|
||||
"TLS11": tls.VersionTLS11,
|
||||
"TLS12": tls.VersionTLS12,
|
||||
}
|
||||
|
||||
var tlsCipherMap = map[string]uint16{
|
||||
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
}
|
||||
12
internal/tls/common_go112.go
Normal file
12
internal/tls/common_go112.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build go1.12
|
||||
|
||||
package tls
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
func init() {
|
||||
tlsVersionMap["TLS13"] = tls.VersionTLS13
|
||||
tlsCipherMap["TLS_AES_128_GCM_SHA256"] = tls.TLS_AES_128_GCM_SHA256
|
||||
tlsCipherMap["TLS_AES_256_GCM_SHA384"] = tls.TLS_AES_256_GCM_SHA384
|
||||
tlsCipherMap["TLS_CHACHA20_POLY1305_SHA256"] = tls.TLS_CHACHA20_POLY1305_SHA256
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ClientConfig represents the standard client TLS config.
|
||||
@@ -25,6 +26,9 @@ type ServerConfig struct {
|
||||
TLSCert string `toml:"tls_cert"`
|
||||
TLSKey string `toml:"tls_key"`
|
||||
TLSAllowedCACerts []string `toml:"tls_allowed_cacerts"`
|
||||
TLSCipherSuites []string `toml:"tls_cipher_suites"`
|
||||
TLSMinVersion string `toml:"tls_min_version"`
|
||||
TLSMaxVersion string `toml:"tls_max_version"`
|
||||
}
|
||||
|
||||
// TLSConfig returns a tls.Config, may be nil without error if TLS is not
|
||||
@@ -97,6 +101,38 @@ func (c *ServerConfig) TLSConfig() (*tls.Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.TLSCipherSuites) != 0 {
|
||||
cipherSuites, err := ParseCiphers(c.TLSCipherSuites)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"could not parse server cipher suites %s: %v", strings.Join(c.TLSCipherSuites, ","), err)
|
||||
}
|
||||
tlsConfig.CipherSuites = cipherSuites
|
||||
}
|
||||
|
||||
if c.TLSMaxVersion != "" {
|
||||
version, err := ParseTLSVersion(c.TLSMaxVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"could not parse tls max version %q: %v", c.TLSMaxVersion, err)
|
||||
}
|
||||
tlsConfig.MaxVersion = version
|
||||
}
|
||||
|
||||
if c.TLSMinVersion != "" {
|
||||
version, err := ParseTLSVersion(c.TLSMinVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"could not parse tls min version %q: %v", c.TLSMinVersion, err)
|
||||
}
|
||||
tlsConfig.MinVersion = version
|
||||
}
|
||||
|
||||
if tlsConfig.MinVersion != 0 && tlsConfig.MaxVersion != 0 && tlsConfig.MinVersion > tlsConfig.MaxVersion {
|
||||
return nil, fmt.Errorf(
|
||||
"tls min version %q can't be greater then tls max version %q", tlsConfig.MinVersion, tlsConfig.MaxVersion)
|
||||
}
|
||||
|
||||
return tlsConfig, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,47 @@ func TestServerConfig(t *testing.T) {
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
TLSMinVersion: pki.TLSMinVersion(),
|
||||
TLSMaxVersion: pki.TLSMaxVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing tls cipher suites is okay",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing tls max version is okay",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
TLSMaxVersion: pki.TLSMaxVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing tls min version is okay",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
TLSMinVersion: pki.TLSMinVersion(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing tls min/max versions is okay",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -172,6 +213,56 @@ func TestServerConfig(t *testing.T) {
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid cipher suites",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CACertPath()},
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "TLS Max Version less then TLS Min version",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CACertPath()},
|
||||
TLSMinVersion: pki.TLSMaxVersion(),
|
||||
TLSMaxVersion: pki.TLSMinVersion(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tls min version",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CipherSuite()},
|
||||
TLSMinVersion: pki.ServerKeyPath(),
|
||||
TLSMaxVersion: pki.TLSMaxVersion(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid tls max version",
|
||||
server: tls.ServerConfig{
|
||||
TLSCert: pki.ServerCertPath(),
|
||||
TLSKey: pki.ServerKeyPath(),
|
||||
TLSAllowedCACerts: []string{pki.CACertPath()},
|
||||
TLSCipherSuites: []string{pki.CACertPath()},
|
||||
TLSMinVersion: pki.TLSMinVersion(),
|
||||
TLSMaxVersion: pki.ServerCertPath(),
|
||||
},
|
||||
expNil: true,
|
||||
expErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
30
internal/tls/utils.go
Normal file
30
internal/tls/utils.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ParseCiphers returns a `[]uint16` by received `[]string` key that represents ciphers from crypto/tls.
|
||||
// If some of ciphers in received list doesn't exists ParseCiphers returns nil with error
|
||||
func ParseCiphers(ciphers []string) ([]uint16, error) {
|
||||
suites := []uint16{}
|
||||
|
||||
for _, cipher := range ciphers {
|
||||
if v, ok := tlsCipherMap[cipher]; ok {
|
||||
suites = append(suites, v)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unsupported cipher %q", cipher)
|
||||
}
|
||||
}
|
||||
|
||||
return suites, nil
|
||||
}
|
||||
|
||||
// ParseTLSVersion returns a `uint16` by received version string key that represents tls version from crypto/tls.
|
||||
// If version isn't supportes ParseTLSVersion returns 0 with error
|
||||
func ParseTLSVersion(version string) (uint16, error) {
|
||||
if v, ok := tlsVersionMap[version]; ok {
|
||||
return v, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unsupported version %q", version)
|
||||
}
|
||||
Reference in New Issue
Block a user