diff --git a/libbeat/common/transport/tlscommon/ca_test.key b/libbeat/common/transport/tlscommon/ca_test.key new file mode 100644 index 00000000000..6b658ead57c --- /dev/null +++ b/libbeat/common/transport/tlscommon/ca_test.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/libbeat/common/transport/tlscommon/ca_test.pem b/libbeat/common/transport/tlscommon/ca_test.pem new file mode 100644 index 00000000000..dcc8b984b78 --- /dev/null +++ b/libbeat/common/transport/tlscommon/ca_test.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFTDCCAzSgAwIBAgIRAOAMlgVxz4G+Zj/EtBTvpg4wDQYJKoZIhvcNAQENBQAw +LzELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB2VsYXN0aWMxDjAMBgNVBAsTBWJlYXRz +MB4XDTE3MDUxODIwMzI1MVoXDTI3MDUxODIwMzI1MVowLzELMAkGA1UEBhMCVVMx +EDAOBgNVBAoTB2VsYXN0aWMxDjAMBgNVBAsTBWJlYXRzMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAv8IiJDAIDl+roQOWe+oSq46Nyuu9R+Iis0V1i6M7 +zA6QijbxCSZ64cCFYQfKheRYQSZRstHPHSUM1gSvUih/sqZqsiNMYDbb9j7geMDv +ls4c7rsHx7xImD7nCrEVWkiapGIhkW6SOtVo18Zmw89FUuDFhoRmMHcQ+7AtM4uU +NPkSqKcXvzG093SU0oNdIBdw5PzoQlvBh5DL0iRYC6y22cwJyjWTUEB5vTjOTDxi +FzsovRtjpdjzSZACXyW68b99icLzmxzLvsZ7w8tFJ8uOPQAVxwg6SmMUorURv48s +BjfVfN487OjH3d+51ozNJjP1MmKoN2BoE8pWq0jdhOWhDQH+pRiRjfMuL+yvcIJ2 +pxdOv0F3KBkng7qEgEUA8cqaFnawDA7O3a20SeDFWSQtN6LsFjT7EDMzNkML1pJj +bGK24QFCIOOvCJtaccuREN1OfbN1yhTz3VErbJttwO6j2KueasPHXU3qLu2FKOls +XbPy1XMuLYZgv8Zprcbs4KhQ3/A7/RO1cakxWlRwta63mUIM2xLIMIgRSR+DSZ5d +JaDNO6i49eIGQXRxDb9dxA2hoCcoTv7PJKyOpNb5vyxMXJGY7H5j1jEEcqEeuI5u +vuUwugQGtsl1eFLXIeQLerOHEQoS6wMv0fHBtZOVCHu8CCrnt/ag7kn39nkwNofL +ovECAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMC +BggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDgQHBAUxMjM0NTAPBgNV +HREECDAGhwR/AAABMA0GCSqGSIb3DQEBDQUAA4ICAQBjeGIfFqXuwHiClMytJNZL +cRyjeZ6PJIAQtqh8Vi+XD2JiDTkwJ/g4R0FbgqE/icGkm/hsJ6BEwp8ep5eXevjS +Hb8tVbM5Uc31yyIKcJMgnfS8O0eIXi5PxgFWPcUXxrsjwHyQREqj96HImmzOm99O +MJhifWT3YP8OEMyl1KpioPaXafhc4ATEiRVZizHM9z+phyINBNghH3OaN91ZnsKJ +El7mvOLjRi7fuSxBWJntKVAZAwXK+nH+z/Ay4AZFA9HgFHo3PGpKUaLOYCIsGxAq +GP4V/WsOtEJ9rP5TR92pOvcj49T47FmwSYaRtoXHDVuoun0fdwT4DxWJdksqdWzG +ieRls2IrZIvR2FT/A/XdQG3kZ79WA/K3OAGDgxv0PCpw6ssAMvgjR03TjEXpwMmN +SNcrx1H6l8DHFHJN9f7SofO/J0hkA+fRZUFxP5R+P2BPU0hV14H9iSie/bxhSWIW +ieAh0K1SNRbffXeYUvAgrjEvG5x40TktnvjHb20lxc1F1gqB+855kfZdiJeUeizi +syq6OnCEp+RSBdK7J3scm7t6Nt3GRndJMO9hNDprogTqHxQbZ0jficntGd7Lbp+C +CBegkhOzD6cp2rGlyYI+MmvdXFaHbsUJj2tfjHQdo2YjQ1s8r2pw219LTzPvO/Dz +morZ618ezCBBqxHsDF6DCA== +-----END CERTIFICATE----- diff --git a/libbeat/common/transport/tlscommon/config.go b/libbeat/common/transport/tlscommon/config.go new file mode 100644 index 00000000000..9e89f2eea3a --- /dev/null +++ b/libbeat/common/transport/tlscommon/config.go @@ -0,0 +1,86 @@ +package tlscommon + +import ( + "crypto/tls" + + "github.com/joeshaw/multierror" +) + +// Config defines the user configurable options in the yaml file. +type Config struct { + Enabled *bool `config:"enabled"` + VerificationMode TLSVerificationMode `config:"verification_mode"` // one of 'none', 'full' + Versions []TLSVersion `config:"supported_protocols"` + CipherSuites []tlsCipherSuite `config:"cipher_suites"` + CAs []string `config:"certificate_authorities"` + Certificate CertificateConfig `config:",inline"` + CurveTypes []tlsCurveType `config:"curve_types"` + Renegotiation tlsRenegotiationSupport `config:"renegotiation"` +} + +// LoadTLSConfig will load a certificate from config with all TLS based keys +// defined. If Certificate and CertificateKey are configured, client authentication +// will be configured. If no CAs are configured, the host CA will be used by go +// built-in TLS support. +func LoadTLSConfig(config *Config) (*TLSConfig, error) { + if !config.IsEnabled() { + return nil, nil + } + + fail := multierror.Errors{} + logFail := func(es ...error) { + for _, e := range es { + if e != nil { + fail = append(fail, e) + } + } + } + + var cipherSuites []uint16 + for _, suite := range config.CipherSuites { + cipherSuites = append(cipherSuites, uint16(suite)) + } + + var curves []tls.CurveID + for _, id := range config.CurveTypes { + curves = append(curves, tls.CurveID(id)) + } + + cert, err := LoadCertificate(&config.Certificate) + logFail(err) + + cas, errs := LoadCertificateAuthorities(config.CAs) + logFail(errs...) + + // fail, if any error occurred when loading certificate files + if err = fail.Err(); err != nil { + return nil, err + } + + var certs []tls.Certificate + if cert != nil { + certs = []tls.Certificate{*cert} + } + + // return config if no error occurred + return &TLSConfig{ + Versions: config.Versions, + Verification: config.VerificationMode, + Certificates: certs, + RootCAs: cas, + CipherSuites: cipherSuites, + CurvePreferences: curves, + Renegotiation: tls.RenegotiationSupport(config.Renegotiation), + }, nil +} + +// Validate valies the TLSConfig struct making sure certificate sure we have both a certificate and +// a key. +func (c *Config) Validate() error { + return c.Certificate.Validate() +} + +// IsEnabled returns true if the `enable` field is set to true in the yaml. +func (c *Config) IsEnabled() bool { + return c != nil && (c.Enabled == nil || *c.Enabled) +} diff --git a/libbeat/common/transport/tlscommon/server_config.go b/libbeat/common/transport/tlscommon/server_config.go new file mode 100644 index 00000000000..c6351410921 --- /dev/null +++ b/libbeat/common/transport/tlscommon/server_config.go @@ -0,0 +1,84 @@ +package tlscommon + +import ( + "crypto/tls" + + "github.com/joeshaw/multierror" +) + +// ServerConfig defines the user configurable tls options for any TCP based service. +type ServerConfig struct { + Enabled *bool `config:"enabled"` + VerificationMode TLSVerificationMode `config:"verification_mode"` // one of 'none', 'full' + Versions []TLSVersion `config:"supported_protocols"` + CipherSuites []tlsCipherSuite `config:"cipher_suites"` + CAs []string `config:"certificate_authorities"` + Certificate CertificateConfig `config:",inline"` + CurveTypes []tlsCurveType `config:"curve_types"` + ClientAuth tlsClientAuth `config:"client_authentification"` //`none`, `optional` or `required` +} + +// LoadTLSServerConfig tranforms a ServerConfig into a `tls.Config` to be used directly with golang +// network types. +func LoadTLSServerConfig(config *ServerConfig) (*TLSConfig, error) { + if !config.IsEnabled() { + return nil, nil + } + + fail := multierror.Errors{} + logFail := func(es ...error) { + for _, e := range es { + if e != nil { + fail = append(fail, e) + } + } + } + + var cipherSuites []uint16 + for _, suite := range config.CipherSuites { + cipherSuites = append(cipherSuites, uint16(suite)) + } + + var curves []tls.CurveID + for _, id := range config.CurveTypes { + curves = append(curves, tls.CurveID(id)) + } + + cert, err := LoadCertificate(&config.Certificate) + logFail(err) + + cas, errs := LoadCertificateAuthorities(config.CAs) + logFail(errs...) + + // fail, if any error occurred when loading certificate files + if err = fail.Err(); err != nil { + return nil, err + } + + var certs []tls.Certificate + if cert != nil { + certs = []tls.Certificate{*cert} + } + + // return config if no error occurred + return &TLSConfig{ + Versions: config.Versions, + Verification: config.VerificationMode, + Certificates: certs, + ClientCAs: cas, + CipherSuites: cipherSuites, + CurvePreferences: curves, + ClientAuth: tls.ClientAuthType(config.ClientAuth), + }, nil +} + +// Validate valies the TLSConfig struct making sure certificate sure we have both a certificate and +// a key. +func (c *ServerConfig) Validate() error { + return c.Certificate.Validate() +} + +// IsEnabled returns true if the `enable` field is set to true in the yaml. +func (c *ServerConfig) IsEnabled() bool { + return c != nil && (c.Enabled == nil || *c.Enabled) +} diff --git a/libbeat/common/transport/tlscommon/tls.go b/libbeat/common/transport/tlscommon/tls.go new file mode 100644 index 00000000000..d90c2500b31 --- /dev/null +++ b/libbeat/common/transport/tlscommon/tls.go @@ -0,0 +1,172 @@ +package tlscommon + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "io/ioutil" + + "github.com/elastic/beats/libbeat/logp" +) + +// LoadCertificate will load a certificate from disk and return a tls.Certificate or error +func LoadCertificate(config *CertificateConfig) (*tls.Certificate, error) { + certificate := config.Certificate + key := config.Key + + hasCertificate := certificate != "" + hasKey := key != "" + + switch { + case hasCertificate && !hasKey: + return nil, ErrCertificateNoKey + case !hasCertificate && hasKey: + return nil, ErrKeyNoCertificate + case !hasCertificate && !hasKey: + return nil, nil + } + + certPEM, err := ReadPEMFile(certificate, config.Passphrase) + if err != nil { + logp.Critical("Failed reading certificate file %v: %v", certificate, err) + return nil, fmt.Errorf("%v %v", err, certificate) + } + + keyPEM, err := ReadPEMFile(key, config.Passphrase) + if err != nil { + logp.Critical("Failed reading key file %v: %v", key, err) + return nil, fmt.Errorf("%v %v", err, key) + } + + cert, err := tls.X509KeyPair(certPEM, keyPEM) + if err != nil { + logp.Critical("Failed loading client certificate", err) + return nil, err + } + + logp.Debug("tls", "loading certificate: %v and key %v", certificate, key) + return &cert, nil +} + +// ReadPEMFile reads a PEM format file on disk and decrypt it with the privided password and +// return the raw content. +func ReadPEMFile(path, passphrase string) ([]byte, error) { + pass := []byte(passphrase) + var blocks []*pem.Block + + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + for len(content) > 0 { + var block *pem.Block + + block, content = pem.Decode(content) + if block == nil { + if len(blocks) == 0 { + return nil, errors.New("no pem file") + } + break + } + + if x509.IsEncryptedPEMBlock(block) { + var buffer []byte + var err error + if len(pass) == 0 { + err = errors.New("No passphrase available") + } else { + // Note, decrypting pem might succeed even with wrong password, but + // only noise will be stored in buffer in this case. + buffer, err = x509.DecryptPEMBlock(block, pass) + } + + if err != nil { + logp.Err("Dropping encrypted pem '%v' block read from %v. %v", + block.Type, path, err) + continue + } + + // DEK-Info contains encryption info. Remove header to mark block as + // unencrypted. + delete(block.Headers, "DEK-Info") + block.Bytes = buffer + } + blocks = append(blocks, block) + } + + if len(blocks) == 0 { + return nil, errors.New("no PEM blocks") + } + + // re-encode available, decrypted blocks + buffer := bytes.NewBuffer(nil) + for _, block := range blocks { + err := pem.Encode(buffer, block) + if err != nil { + return nil, err + } + } + return buffer.Bytes(), nil +} + +// LoadCertificateAuthorities read the slice of CAcert and return a Certpool. +func LoadCertificateAuthorities(CAs []string) (*x509.CertPool, []error) { + errors := []error{} + + if len(CAs) == 0 { + return nil, nil + } + + roots := x509.NewCertPool() + for _, path := range CAs { + pemData, err := ioutil.ReadFile(path) + if err != nil { + logp.Critical("Failed reading CA certificate: %v", err) + errors = append(errors, fmt.Errorf("%v reading %v", err, path)) + continue + } + + if ok := roots.AppendCertsFromPEM(pemData); !ok { + logp.Critical("Failed reading CA certificate: %v", err) + errors = append(errors, fmt.Errorf("%v adding %v", ErrNotACertificate, path)) + continue + } + logp.Debug("tls", "successfully loaded CA certificate: %v", path) + } + + return roots, errors +} + +func extractMinMaxVersion(versions []TLSVersion) (uint16, uint16) { + if len(versions) == 0 { + versions = TLSDefaultVersions + } + + minVersion := uint16(0xffff) + maxVersion := uint16(0) + for _, version := range versions { + v := uint16(version) + if v < minVersion { + minVersion = v + } + if v > maxVersion { + maxVersion = v + } + } + + return minVersion, maxVersion +} + +// ResolveTLSVersion takes the integer representation and return the name. +func ResolveTLSVersion(v uint16) string { + return TLSVersion(v).String() +} + +// ResolveCipherSuite takes the integer representation and return the cipher name. +func ResolveCipherSuite(cipher uint16) string { + return tlsCipherSuite(cipher).String() +} diff --git a/libbeat/common/transport/tlscommon/tls_config.go b/libbeat/common/transport/tlscommon/tls_config.go new file mode 100644 index 00000000000..e4d9bca14b8 --- /dev/null +++ b/libbeat/common/transport/tlscommon/tls_config.go @@ -0,0 +1,77 @@ +package tlscommon + +import ( + "crypto/tls" + "crypto/x509" + + "github.com/elastic/beats/libbeat/logp" +) + +// TLSConfig is the interface used to configure a tcp client or server from a `Config` +type TLSConfig struct { + + // List of allowed SSL/TLS protocol versions. Connections might be dropped + // after handshake succeeded, if TLS version in use is not listed. + Versions []TLSVersion + + // Configure SSL/TLS verification mode used during handshake. By default + // VerifyFull will be used. + Verification TLSVerificationMode + + // List of certificate chains to present to the other side of the + // connection. + Certificates []tls.Certificate + + // Set of root certificate authorities use to verify server certificates. + // If RootCAs is nil, TLS might use the system its root CA set (not supported + // on MS Windows). + RootCAs *x509.CertPool + + // Set of root certificate authorities use to verify client certificates. + // If ClientCAs is nil, TLS might use the system its root CA set (not supported + // on MS Windows). + ClientCAs *x509.CertPool + + // List of supported cipher suites. If nil, a default list provided by the + // implementation will be used. + CipherSuites []uint16 + + // Types of elliptic curves that will be used in an ECDHE handshake. If empty, + // the implementation will choose a default. + CurvePreferences []tls.CurveID + + // Renegotiation controls what types of renegotiation are supported. + // The default, never, is correct for the vast majority of applications. + Renegotiation tls.RenegotiationSupport + + // ClientAuth controls how we want to verify certificate from a client, `none`, `optional` and + // `required`, default to required. Do not affect TCP client. + ClientAuth tls.ClientAuthType +} + +// BuildModuleConfig takes the TLSConfig and tranform it into a `tls.Config`. +func (c *TLSConfig) BuildModuleConfig(host string) *tls.Config { + if c == nil { + // use default TLS settings, if config is empty. + return &tls.Config{ServerName: host} + } + + minVersion, maxVersion := extractMinMaxVersion(c.Versions) + insecure := c.Verification != VerifyFull + if insecure { + logp.Warn("SSL/TLS verifications disabled.") + } + + return &tls.Config{ + ServerName: host, + MinVersion: minVersion, + MaxVersion: maxVersion, + Certificates: c.Certificates, + RootCAs: c.RootCAs, + ClientCAs: c.ClientCAs, + InsecureSkipVerify: insecure, + CipherSuites: c.CipherSuites, + CurvePreferences: c.CurvePreferences, + ClientAuth: c.ClientAuth, + } +} diff --git a/libbeat/outputs/tls_test.go b/libbeat/common/transport/tlscommon/tls_test.go similarity index 60% rename from libbeat/outputs/tls_test.go rename to libbeat/common/transport/tlscommon/tls_test.go index 4c8ce1eae49..d629fcaeced 100644 --- a/libbeat/outputs/tls_test.go +++ b/libbeat/common/transport/tlscommon/tls_test.go @@ -1,21 +1,21 @@ // +build !integration -package outputs +package tlscommon import ( "crypto/tls" + "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/elastic/beats/libbeat/common" - "github.com/elastic/beats/libbeat/outputs/transport" ) // test TLS config loading -func load(yamlStr string) (*TLSConfig, error) { - var cfg TLSConfig +func load(yamlStr string) (*Config, error) { + var cfg Config config, err := common.NewConfigWithYAML([]byte(yamlStr), "") if err != nil { return nil, err @@ -27,7 +27,7 @@ func load(yamlStr string) (*TLSConfig, error) { return &cfg, nil } -func mustLoad(t *testing.T, yamlStr string) *TLSConfig { +func mustLoad(t *testing.T, yamlStr string) *Config { cfg, err := load(yamlStr) if err != nil { t.Fatal(err) @@ -39,7 +39,7 @@ func TestEmptyTlsConfig(t *testing.T) { cfg, err := load("") assert.Nil(t, err) - assert.Equal(t, cfg, &TLSConfig{}) + assert.Equal(t, cfg, &Config{}) } func TestLoadWithEmptyValues(t *testing.T) { @@ -56,7 +56,7 @@ func TestLoadWithEmptyValues(t *testing.T) { `) assert.Nil(t, err) - assert.Equal(t, cfg, &TLSConfig{}) + assert.Equal(t, cfg, &Config{}) } func TestNoLoadNilConfig(t *testing.T) { @@ -67,7 +67,7 @@ func TestNoLoadNilConfig(t *testing.T) { func TestNoLoadDisabledConfig(t *testing.T) { enabled := false - cfg, err := LoadTLSConfig(&TLSConfig{Enabled: &enabled}) + cfg, err := LoadTLSConfig(&Config{Enabled: &enabled}) assert.Nil(t, err) assert.Nil(t, cfg) } @@ -95,10 +95,10 @@ func TestValuesSet(t *testing.T) { assert.Equal(t, "mycert.pem", cfg.Certificate.Certificate) assert.Equal(t, "mycert.key", cfg.Certificate.Key) assert.Len(t, cfg.CAs, 2) - assert.Equal(t, transport.VerifyNone, cfg.VerificationMode) + assert.Equal(t, VerifyNone, cfg.VerificationMode) assert.Len(t, cfg.CipherSuites, 2) assert.Equal(t, - []transport.TLSVersion{transport.TLSVersion11, transport.TLSVersion12}, + []TLSVersion{TLSVersion11, TLSVersion12}, cfg.Versions) assert.Len(t, cfg.CurveTypes, 1) assert.Equal(t, @@ -107,7 +107,7 @@ func TestValuesSet(t *testing.T) { } func TestApplyEmptyConfig(t *testing.T) { - tmp, err := LoadTLSConfig(&TLSConfig{}) + tmp, err := LoadTLSConfig(&Config{}) if err != nil { t.Fatal(err) } @@ -124,9 +124,9 @@ func TestApplyEmptyConfig(t *testing.T) { func TestApplyWithConfig(t *testing.T) { tmp, err := LoadTLSConfig(mustLoad(t, ` - certificate: logstash/ca_test.pem - key: logstash/ca_test.key - certificate_authorities: [logstash/ca_test.pem] + certificate: ca_test.pem + key: ca_test.key + certificate_authorities: [ca_test.pem] verification_mode: none cipher_suites: - "ECDHE-ECDSA-AES-256-CBC-SHA" @@ -148,6 +148,46 @@ func TestApplyWithConfig(t *testing.T) { assert.Len(t, cfg.CurvePreferences, 1) } +func TestApplyWithServerConfig(t *testing.T) { + yamlStr := ` + certificate: ca_test.pem + key: ca_test.key + certificate_authorities: [ca_test.pem] + verification_mode: none + client_authentification: optional + supported_protocols: [TLSv1.1, TLSv1.2] + cipher_suites: + - "ECDHE-ECDSA-AES-256-CBC-SHA" + - "ECDHE-ECDSA-AES-256-GCM-SHA384" + curve_types: [P-384] + ` + var c ServerConfig + config, err := common.NewConfigWithYAML([]byte(yamlStr), "") + if !assert.NoError(t, err) { + return + } + + err = config.Unpack(&c) + if !assert.NoError(t, err) { + return + } + tmp, err := LoadTLSServerConfig(&c) + if !assert.NoError(t, err) { + return + } + + cfg := tmp.BuildModuleConfig("") + assert.NotNil(t, cfg) + assert.Len(t, cfg.Certificates, 1) + assert.NotNil(t, cfg.ClientCAs) + assert.Equal(t, true, cfg.InsecureSkipVerify) + assert.Len(t, cfg.CipherSuites, 2) + assert.Equal(t, int(tls.VersionTLS11), int(cfg.MinVersion)) + assert.Equal(t, int(tls.VersionTLS12), int(cfg.MaxVersion)) + assert.Len(t, cfg.CurvePreferences, 1) + assert.Equal(t, tls.VerifyClientCertIfGiven, cfg.ClientAuth) +} + func TestCertificateFails(t *testing.T) { tests := []struct { title string @@ -180,22 +220,32 @@ func TestCertificateFails(t *testing.T) { } for i, test := range tests { - t.Logf("run test (%v): %v", i, test.title) - - config, err := common.NewConfigWithYAML([]byte(test.yaml), "") - if err != nil { - t.Error(err) - continue - } - - // one must fail: validators on Unpack or transformation to *tls.Config - var tlscfg TLSConfig - if err = config.Unpack(&tlscfg); err != nil { + t.Run(fmt.Sprintf("run test (%v): %v", i, test.title), func(t *testing.T) { + config, err := common.NewConfigWithYAML([]byte(test.yaml), "") + if err != nil { + t.Error(err) + return + } + + // one must fail: validators on Unpack or transformation to *tls.Config + var tlscfg Config + if err = config.Unpack(&tlscfg); err != nil { + t.Log(err) + return + } + _, err = LoadTLSConfig(&tlscfg) t.Log(err) - continue - } - _, err = LoadTLSConfig(&tlscfg) - t.Log(err) - assert.Error(t, err) + assert.Error(t, err) + }) } } + +func TestResolveTLSVersion(t *testing.T) { + v := ResolveTLSVersion(tls.VersionTLS11) + assert.Equal(t, "TLSv1.1", v) +} + +func TestResolveCipherSuite(t *testing.T) { + c := ResolveCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA) + assert.Equal(t, "RSA-AES-128-CBC-SHA", c) +} diff --git a/libbeat/common/transport/tlscommon/types.go b/libbeat/common/transport/tlscommon/types.go new file mode 100644 index 00000000000..7c2be0e9a0e --- /dev/null +++ b/libbeat/common/transport/tlscommon/types.go @@ -0,0 +1,267 @@ +package tlscommon + +import ( + "crypto/tls" + "errors" + "fmt" +) + +var ( + // ErrNotACertificate indicates a PEM file to be loaded not being a valid + // PEM file or certificate. + ErrNotACertificate = errors.New("file is not a certificate") + + // ErrCertificateNoKey indicate a configuration error with missing key file + ErrCertificateNoKey = errors.New("key file not configured") + + // ErrKeyNoCertificate indicate a configuration error with missing certificate file + ErrKeyNoCertificate = errors.New("certificate file not configured") +) + +var tlsCipherSuites = map[string]tlsCipherSuite{ + "ECDHE-ECDSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), + "ECDHE-ECDSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), + "ECDHE-ECDSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), + "ECDHE-ECDSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), + "ECDHE-ECDSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA), + "ECDHE-RSA-3DES-CBC3-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), + "ECDHE-RSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), + "ECDHE-RSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), + "ECDHE-RSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), + "ECDHE-RSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), + "ECDHE-RSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA), + "RSA-3DES-CBC3-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA), + "RSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA), + "RSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_RSA_WITH_AES_128_GCM_SHA256), + "RSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_CBC_SHA), + "RSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_GCM_SHA384), + "RSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_RC4_128_SHA), +} + +var tlsCipherSuitesInverse = make(map[tlsCipherSuite]string, len(tlsCipherSuites)) + +// Init creates a inverse representation of the values mapping. +func init() { + for cipherName, i := range tlsCipherSuites { + tlsCipherSuitesInverse[i] = cipherName + } +} + +var tlsCurveTypes = map[string]tlsCurveType{ + "P-256": tlsCurveType(tls.CurveP256), + "P-384": tlsCurveType(tls.CurveP384), + "P-521": tlsCurveType(tls.CurveP521), +} + +var tlsRenegotiationSupportTypes = map[string]tlsRenegotiationSupport{ + "never": tlsRenegotiationSupport(tls.RenegotiateNever), + "once": tlsRenegotiationSupport(tls.RenegotiateOnceAsClient), + "freely": tlsRenegotiationSupport(tls.RenegotiateFreelyAsClient), +} + +// TLSVersion type for TLS version. +type TLSVersion uint16 + +// Define all the possible TLS version. +const ( + TLSVersionSSL30 TLSVersion = tls.VersionSSL30 + TLSVersion10 TLSVersion = tls.VersionTLS10 + TLSVersion11 TLSVersion = tls.VersionTLS11 + TLSVersion12 TLSVersion = tls.VersionTLS12 +) + +// TLSDefaultVersions list of versions of TLS we should support. +var TLSDefaultVersions = []TLSVersion{ + TLSVersion10, + TLSVersion11, + TLSVersion12, +} + +type tlsClientAuth int + +const ( + tlsClientAuthNone tlsClientAuth = tlsClientAuth(tls.NoClientCert) + tlsClientAuthOptional = tlsClientAuth(tls.VerifyClientCertIfGiven) + tlsClientAuthRequired = tlsClientAuth(tls.RequireAndVerifyClientCert) +) + +var tlsClientAuthTypes = map[string]tlsClientAuth{ + "none": tlsClientAuthNone, + "optional": tlsClientAuthOptional, + "required": tlsClientAuthRequired, +} + +var tlsProtocolVersions = map[string]TLSVersion{ + "SSLv3": TLSVersionSSL30, + "SSLv3.0": TLSVersionSSL30, + "TLSv1": TLSVersion10, + "TLSv1.0": TLSVersion10, + "TLSv1.1": TLSVersion11, + "TLSv1.2": TLSVersion12, +} + +var tlsProtocolVersionsInverse = map[TLSVersion]string{ + TLSVersionSSL30: "SSLv3", + TLSVersion10: "TLSv1.0", + TLSVersion11: "TLSv1.1", + TLSVersion12: "TLSv1.2", +} + +// TLSVerificationMode represents the type of verification to do on the remote host, +// `none` or `full` and we default to `full`, internally this option is transformed into the +// `insecure` field in the `tls.Config` struct. +type TLSVerificationMode uint8 + +// Constants of the supported verification mode. +const ( + VerifyFull TLSVerificationMode = iota + VerifyNone + + // TODO: add VerifyCertificate support. Due to checks being run + // during handshake being limited, verify certificates in + // postVerifyTLSConnection + // VerifyCertificate +) + +func (v TLSVersion) String() string { + if s, ok := tlsProtocolVersionsInverse[v]; ok { + return s + } + return "unknown" +} + +//Unpack transforms the string into a constant. +func (v *TLSVersion) Unpack(s string) error { + version, found := tlsProtocolVersions[s] + if !found { + return fmt.Errorf("invalid tls version '%v'", s) + } + + *v = version + return nil +} + +var tlsVerificationModes = map[string]TLSVerificationMode{ + "": VerifyFull, + "full": VerifyFull, + "none": VerifyNone, + // "certificate": verifyCertificate, +} + +func (m TLSVerificationMode) String() string { + modes := map[TLSVerificationMode]string{ + VerifyFull: "full", + // VerifyCertificate: "certificate", + VerifyNone: "none", + } + + if s, ok := modes[m]; ok { + return s + } + return "unknown" +} + +// Unpack unpacks the string into contants. +func (m *TLSVerificationMode) Unpack(in interface{}) error { + if in == nil { + *m = VerifyFull + return nil + } + + s, ok := in.(string) + if !ok { + return fmt.Errorf("verification mode must be an identifier") + } + + mode, found := tlsVerificationModes[s] + if !found { + return fmt.Errorf("unknown verification mode '%v'", s) + } + + *m = mode + return nil +} + +func (m *tlsClientAuth) Unpack(in interface{}) error { + if in == nil { + *m = tlsClientAuthRequired + return nil + } + + s, ok := in.(string) + if !ok { + return fmt.Errorf("client authentification must be an identifier") + } + + mode, found := tlsClientAuthTypes[s] + if !found { + return fmt.Errorf("unknown client authentification mode'%v'", s) + } + + *m = mode + return nil +} + +type tlsCipherSuite uint16 + +func (cs *tlsCipherSuite) Unpack(s string) error { + suite, found := tlsCipherSuites[s] + if !found { + return fmt.Errorf("invalid tls cipher suite '%v'", s) + } + + *cs = suite + return nil +} + +func (cs tlsCipherSuite) String() string { + if s, found := tlsCipherSuitesInverse[cs]; found { + return s + } + return "unkown" +} + +type tlsCurveType tls.CurveID + +func (ct *tlsCurveType) Unpack(s string) error { + t, found := tlsCurveTypes[s] + if !found { + return fmt.Errorf("invalid tls curve type '%v'", s) + } + + *ct = t + return nil +} + +type tlsRenegotiationSupport tls.RenegotiationSupport + +func (r *tlsRenegotiationSupport) Unpack(s string) error { + t, found := tlsRenegotiationSupportTypes[s] + if !found { + return fmt.Errorf("invalid tls renegotiation type '%v'", s) + } + + *r = t + return nil +} + +// CertificateConfig define a common set of fields for a certificate. +type CertificateConfig struct { + Certificate string `config:"certificate"` + Key string `config:"key"` + Passphrase string `config:"key_passphrase"` +} + +// Validate validates the CertificateConfig +func (c *CertificateConfig) Validate() error { + hasCertificate := c.Certificate != "" + hasKey := c.Key != "" + + switch { + case hasCertificate && !hasKey: + return ErrCertificateNoKey + case !hasCertificate && hasKey: + return ErrKeyNoCertificate + } + return nil +} diff --git a/libbeat/outputs/tls.go b/libbeat/outputs/tls.go index 607aedfe250..5d21ec9036d 100644 --- a/libbeat/outputs/tls.go +++ b/libbeat/outputs/tls.go @@ -1,311 +1,41 @@ package outputs import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io/ioutil" - - "github.com/joeshaw/multierror" - - "github.com/elastic/beats/libbeat/logp" - "github.com/elastic/beats/libbeat/outputs/transport" + "github.com/elastic/beats/libbeat/common/transport/tlscommon" ) +// Managing TLS option with the outputs package is deprecated move your code to use the tlscommon +// package. var ( // ErrNotACertificate indicates a PEM file to be loaded not being a valid // PEM file or certificate. - ErrNotACertificate = errors.New("file is not a certificate") + ErrNotACertificate = tlscommon.ErrNotACertificate // ErrCertificateNoKey indicate a configuration error with missing key file - ErrCertificateNoKey = errors.New("key file not configured") + ErrCertificateNoKey = tlscommon.ErrCertificateNoKey // ErrKeyNoCertificate indicate a configuration error with missing certificate file - ErrKeyNoCertificate = errors.New("certificate file not configured") + ErrKeyNoCertificate = tlscommon.ErrKeyNoCertificate ) // TLSConfig defines config file options for TLS clients. -type TLSConfig struct { - Enabled *bool `config:"enabled"` - VerificationMode transport.TLSVerificationMode `config:"verification_mode"` // one of 'none', 'full' - Versions []transport.TLSVersion `config:"supported_protocols"` - CipherSuites []tlsCipherSuite `config:"cipher_suites"` - CAs []string `config:"certificate_authorities"` - Certificate CertificateConfig `config:",inline"` - CurveTypes []tlsCurveType `config:"curve_types"` - Renegotiation tlsRenegotiationSupport `config:"renegotiation"` -} - -type CertificateConfig struct { - Certificate string `config:"certificate"` - Key string `config:"key"` - Passphrase string `config:"key_passphrase"` -} - -type tlsCipherSuite uint16 - -type tlsCurveType tls.CurveID - -type tlsRenegotiationSupport tls.RenegotiationSupport - -var tlsCipherSuites = map[string]tlsCipherSuite{ - "ECDHE-ECDSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA), - "ECDHE-ECDSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256), - "ECDHE-ECDSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA), - "ECDHE-ECDSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384), - "ECDHE-ECDSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA), - "ECDHE-RSA-3DES-CBC3-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA), - "ECDHE-RSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA), - "ECDHE-RSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), - "ECDHE-RSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA), - "ECDHE-RSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384), - "ECDHE-RSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA), - "RSA-3DES-CBC3-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA), - "RSA-AES-128-CBC-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_AES_128_CBC_SHA), - "RSA-AES-128-GCM-SHA256": tlsCipherSuite(tls.TLS_RSA_WITH_AES_128_GCM_SHA256), - "RSA-AES-256-CBC-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_CBC_SHA), - "RSA-AES-256-GCM-SHA384": tlsCipherSuite(tls.TLS_RSA_WITH_AES_256_GCM_SHA384), - "RSA-RC4-128-SHA": tlsCipherSuite(tls.TLS_RSA_WITH_RC4_128_SHA), -} - -var tlsCurveTypes = map[string]tlsCurveType{ - "P-256": tlsCurveType(tls.CurveP256), - "P-384": tlsCurveType(tls.CurveP384), - "P-521": tlsCurveType(tls.CurveP521), -} - -var tlsRenegotiationSupportTypes = map[string]tlsRenegotiationSupport{ - "never": tlsRenegotiationSupport(tls.RenegotiateNever), - "once": tlsRenegotiationSupport(tls.RenegotiateOnceAsClient), - "freely": tlsRenegotiationSupport(tls.RenegotiateFreelyAsClient), -} - -func (c *TLSConfig) Validate() error { - hasCertificate := c.Certificate.Certificate != "" - hasKey := c.Certificate.Key != "" - - switch { - case hasCertificate && !hasKey: - return ErrCertificateNoKey - case !hasCertificate && hasKey: - return ErrKeyNoCertificate - } +type TLSConfig = tlscommon.Config - return nil -} - -func (c *TLSConfig) IsEnabled() bool { - return c != nil && (c.Enabled == nil || *c.Enabled) -} +// CertificateConfig define a common set of fields for a certificate. +type CertificateConfig = tlscommon.CertificateConfig // LoadTLSConfig will load a certificate from config with all TLS based keys // defined. If Certificate and CertificateKey are configured, client authentication // will be configured. If no CAs are configured, the host CA will be used by go // built-in TLS support. -func LoadTLSConfig(config *TLSConfig) (*transport.TLSConfig, error) { - if !config.IsEnabled() { - return nil, nil - } - - fail := multierror.Errors{} - logFail := func(es ...error) { - for _, e := range es { - if e != nil { - fail = append(fail, e) - } - } - } - - var cipherSuites []uint16 - for _, suite := range config.CipherSuites { - cipherSuites = append(cipherSuites, uint16(suite)) - } - - var curves []tls.CurveID - for _, id := range config.CurveTypes { - curves = append(curves, tls.CurveID(id)) - } - - cert, err := LoadCertificate(&config.Certificate) - logFail(err) - - cas, errs := LoadCertificateAuthorities(config.CAs) - logFail(errs...) - - // fail, if any error occurred when loading certificate files - if err = fail.Err(); err != nil { - return nil, err - } - - var certs []tls.Certificate - if cert != nil { - certs = []tls.Certificate{*cert} - } - - // return config if no error occurred - return &transport.TLSConfig{ - Versions: config.Versions, - Verification: config.VerificationMode, - Certificates: certs, - RootCAs: cas, - CipherSuites: cipherSuites, - CurvePreferences: curves, - Renegotiation: tls.RenegotiationSupport(config.Renegotiation), - }, nil -} - -func LoadCertificate(config *CertificateConfig) (*tls.Certificate, error) { - certificate := config.Certificate - key := config.Key - - hasCertificate := certificate != "" - hasKey := key != "" - - switch { - case hasCertificate && !hasKey: - return nil, ErrCertificateNoKey - case !hasCertificate && hasKey: - return nil, ErrKeyNoCertificate - case !hasCertificate && !hasKey: - return nil, nil - } - - certPEM, err := ReadPEMFile(certificate, config.Passphrase) - if err != nil { - logp.Critical("Failed reading certificate file %v: %v", certificate, err) - return nil, fmt.Errorf("%v %v", err, certificate) - } - - keyPEM, err := ReadPEMFile(key, config.Passphrase) - if err != nil { - logp.Critical("Failed reading key file %v: %v", key, err) - return nil, fmt.Errorf("%v %v", err, key) - } - - cert, err := tls.X509KeyPair(certPEM, keyPEM) - if err != nil { - logp.Critical("Failed loading client certificate", err) - return nil, err - } - - return &cert, nil -} - -func ReadPEMFile(path, passphrase string) ([]byte, error) { - pass := []byte(passphrase) - var blocks []*pem.Block - - content, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - for len(content) > 0 { - var block *pem.Block - - block, content = pem.Decode(content) - if block == nil { - if len(blocks) == 0 { - return nil, errors.New("no pem file") - } - break - } - - if x509.IsEncryptedPEMBlock(block) { - var buffer []byte - var err error - if len(pass) == 0 { - err = errors.New("No passphrase available") - } else { - // Note, decrypting pem might succeed even with wrong password, but - // only noise will be stored in buffer in this case. - buffer, err = x509.DecryptPEMBlock(block, pass) - } - - if err != nil { - logp.Err("Dropping encrypted pem '%v' block read from %v. %v", - block.Type, path, err) - continue - } - - // DEK-Info contains encryption info. Remove header to mark block as - // unencrypted. - delete(block.Headers, "DEK-Info") - block.Bytes = buffer - } - blocks = append(blocks, block) - } - - if len(blocks) == 0 { - return nil, errors.New("no PEM blocks") - } - - // re-encode available, decrypted blocks - buffer := bytes.NewBuffer(nil) - for _, block := range blocks { - err := pem.Encode(buffer, block) - if err != nil { - return nil, err - } - } - return buffer.Bytes(), nil -} - -func LoadCertificateAuthorities(CAs []string) (*x509.CertPool, []error) { - errors := []error{} - - if len(CAs) == 0 { - return nil, nil - } - - roots := x509.NewCertPool() - for _, path := range CAs { - pemData, err := ioutil.ReadFile(path) - if err != nil { - logp.Critical("Failed reading CA certificate: %v", err) - errors = append(errors, fmt.Errorf("%v reading %v", err, path)) - continue - } - - if ok := roots.AppendCertsFromPEM(pemData); !ok { - logp.Critical("Failed reading CA certificate: %v", err) - errors = append(errors, fmt.Errorf("%v adding %v", ErrNotACertificate, path)) - continue - } - } - - return roots, errors -} - -func (cs *tlsCipherSuite) Unpack(s string) error { - suite, found := tlsCipherSuites[s] - if !found { - return fmt.Errorf("invalid tls cipher suite '%v'", s) - } - - *cs = suite - return nil -} - -func (ct *tlsCurveType) Unpack(s string) error { - t, found := tlsCurveTypes[s] - if !found { - return fmt.Errorf("invalid tls curve type '%v'", s) - } +var LoadTLSConfig = tlscommon.LoadTLSConfig - *ct = t - return nil -} +// LoadCertificate will load a certificate from disk and return a tls.Certificate or error +var LoadCertificate = tlscommon.LoadCertificate -func (r *tlsRenegotiationSupport) Unpack(s string) error { - t, found := tlsRenegotiationSupportTypes[s] - if !found { - return fmt.Errorf("invalid tls renegotiation type '%v'", s) - } +// ReadPEMFile reads a PEM format file on disk and decrypt it with the privided password and +// return the raw content. +var ReadPEMFile = tlscommon.ReadPEMFile - *r = t - return nil -} +// LoadCertificateAuthorities read the slice of CAcert and return a Certpool. +var LoadCertificateAuthorities = tlscommon.LoadCertificateAuthorities diff --git a/libbeat/outputs/transport/tls.go b/libbeat/outputs/transport/tls.go index c0eb074f498..dc4976d221f 100644 --- a/libbeat/outputs/transport/tls.go +++ b/libbeat/outputs/transport/tls.go @@ -2,76 +2,36 @@ package transport import ( "crypto/tls" - "crypto/x509" "errors" "fmt" "net" "sync" "time" - "github.com/elastic/beats/libbeat/logp" + "github.com/elastic/beats/libbeat/common/transport/tlscommon" "github.com/elastic/beats/libbeat/testing" ) -type TLSConfig struct { +// TLSConfig is the interface used to configure a tcp client or server from a `Config` +type TLSConfig = tlscommon.TLSConfig - // List of allowed SSL/TLS protocol versions. Connections might be dropped - // after handshake succeeded, if TLS version in use is not listed. - Versions []TLSVersion - - // Configure SSL/TLS verification mode used during handshake. By default - // VerifyFull will be used. - Verification TLSVerificationMode - - // List of certificate chains to present to the other side of the - // connection. - Certificates []tls.Certificate - - // Set of root certificate authorities use to verify server certificates. - // If RootCAs is nil, TLS might use the system its root CA set (not supported - // on MS Windows). - RootCAs *x509.CertPool - - // List of supported cipher suites. If nil, a default list provided by the - // implementation will be used. - CipherSuites []uint16 - - // Types of elliptic curves that will be used in an ECDHE handshake. If empty, - // the implementation will choose a default. - CurvePreferences []tls.CurveID - - // Renegotiation controls what types of renegotiation are supported. - // The default, never, is correct for the vast majority of applications. - Renegotiation tls.RenegotiationSupport -} - -type TLSVersion uint16 +// TLSVersion type for TLS version. +type TLSVersion = tlscommon.TLSVersion +// Define all the possible TLS version. const ( - TLSVersionSSL30 TLSVersion = tls.VersionSSL30 - TLSVersion10 TLSVersion = tls.VersionTLS10 - TLSVersion11 TLSVersion = tls.VersionTLS11 - TLSVersion12 TLSVersion = tls.VersionTLS12 + TLSVersionSSL30 = tlscommon.TLSVersionSSL30 + TLSVersion10 = tlscommon.TLSVersion10 + TLSVersion11 = tlscommon.TLSVersion11 + TLSVersion12 = tlscommon.TLSVersion12 ) -type TLSVerificationMode uint8 - +// Constants of the supported verification mode. const ( - VerifyFull TLSVerificationMode = iota - VerifyNone - - // TODO: add VerifyCertificate support. Due to checks being run - // during handshake being limited, verify certificates in - // postVerifyTLSConnection - // VerifyCertificate + VerifyFull = tlscommon.VerifyFull + VerifyNone = tlscommon.VerifyNone ) -var tlsDefaultVersions = []TLSVersion{ - TLSVersion10, - TLSVersion11, - TLSVersion12, -} - func TLSDialer(forward Dialer, config *TLSConfig, timeout time.Duration) (Dialer, error) { return TestTLSDialer(testing.NullDriver, forward, config, timeout) } @@ -184,7 +144,7 @@ func postVerifyTLSConnection(d testing.Driver, conn *tls.Conn, config *TLSConfig versions := config.Versions if versions == nil { - versions = tlsDefaultVersions + versions = tlscommon.TLSDefaultVersions } versionOK := false for _, version := range versions { @@ -198,115 +158,3 @@ func postVerifyTLSConnection(d testing.Driver, conn *tls.Conn, config *TLSConfig return nil } - -func (c *TLSConfig) BuildModuleConfig(host string) *tls.Config { - if c == nil { - // use default TLS settings, if config is empty. - return &tls.Config{ServerName: host} - } - - versions := c.Versions - if len(versions) == 0 { - versions = tlsDefaultVersions - } - - minVersion := uint16(0xffff) - maxVersion := uint16(0) - for _, version := range versions { - v := uint16(version) - if v < minVersion { - minVersion = v - } - if v > maxVersion { - maxVersion = v - } - } - - insecure := c.Verification != VerifyFull - if insecure { - logp.Warn("SSL/TLS verifications disabled.") - } - - return &tls.Config{ - ServerName: host, - MinVersion: minVersion, - MaxVersion: maxVersion, - Certificates: c.Certificates, - RootCAs: c.RootCAs, - InsecureSkipVerify: insecure, - CipherSuites: c.CipherSuites, - CurvePreferences: c.CurvePreferences, - } -} - -var tlsProtocolVersions = map[string]TLSVersion{ - "SSLv3": TLSVersionSSL30, - "SSLv3.0": TLSVersionSSL30, - "TLSv1": TLSVersion10, - "TLSv1.0": TLSVersion10, - "TLSv1.1": TLSVersion11, - "TLSv1.2": TLSVersion12, -} - -func (v TLSVersion) String() string { - versions := map[TLSVersion]string{ - TLSVersionSSL30: "SSLv3", - TLSVersion10: "TLSv1.0", - TLSVersion11: "TLSv1.1", - TLSVersion12: "TLSv1.2", - } - if s, ok := versions[v]; ok { - return s - } - return "unknown" -} - -func (v *TLSVersion) Unpack(s string) error { - version, found := tlsProtocolVersions[s] - if !found { - return fmt.Errorf("invalid tls version '%v'", s) - } - - *v = version - return nil -} - -var tlsVerificationModes = map[string]TLSVerificationMode{ - "": VerifyFull, - "full": VerifyFull, - "none": VerifyNone, - // "certificate": verifyCertificate, -} - -func (m TLSVerificationMode) String() string { - modes := map[TLSVerificationMode]string{ - VerifyFull: "full", - // VerifyCertificate: "certificate", - VerifyNone: "none", - } - - if s, ok := modes[m]; ok { - return s - } - return "unknown" -} - -func (m *TLSVerificationMode) Unpack(in interface{}) error { - if in == nil { - *m = VerifyFull - return nil - } - - s, ok := in.(string) - if !ok { - return fmt.Errorf("verification mode must be an identifier") - } - - mode, found := tlsVerificationModes[s] - if !found { - return fmt.Errorf("unknown verification mode '%v'", s) - } - - *m = mode - return nil -}