From 823a7eac03ca3202108456550daf4bb5e5f15b6f Mon Sep 17 00:00:00 2001 From: Guilherme Rezende Date: Mon, 18 May 2015 16:38:21 -0300 Subject: [PATCH] Added tls option block including: ciphers, protocols and cache options Signed-off-by: Guilherme Rezende --- config/setup/tls.go | 90 +++++++++++++++++++++++++++++++++++++++- config/setup/tls_test.go | 78 ++++++++++++++++++++++++++++++++++ server/config.go | 11 +++-- server/server.go | 14 ++++++- 4 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 config/setup/tls_test.go diff --git a/config/setup/tls.go b/config/setup/tls.go index f084e6c6..bd923c34 100644 --- a/config/setup/tls.go +++ b/config/setup/tls.go @@ -1,10 +1,38 @@ package setup import ( - "github.com/mholt/caddy/middleware" + "crypto/tls" "log" + "strconv" + "strings" + + "github.com/mholt/caddy/middleware" ) +// Map of supported protocols +// SSLv3 will be not supported in next release +var supportedProtocols = map[string]uint16{ + "ssl3": tls.VersionSSL30, + "tls1.0": tls.VersionTLS10, + "tls1.1": tls.VersionTLS11, + "tls1.2": tls.VersionTLS12, +} + +// Map of supported ciphers +// For security reasons caddy will not support RC4 ciphers +var supportedCiphers = map[string]uint16{ + "ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + "ECDHE-ECDSA-AES128-GCM-SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + "ECDHE-RSA-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "ECDHE-RSA-AES256-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "ECDHE-ECDSA-AES256-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "ECDHE-ECDSA-AES128-CBC-SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "RSA-AES128-CBC-SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA, + "RSA-AES256-CBC-SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA, + "ECDHE-RSA-3DES-EDE-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, +} + func TLS(c *Controller) (middleware.Middleware, error) { c.TLS.Enabled = true if c.Port == "http" { @@ -12,7 +40,7 @@ func TLS(c *Controller) (middleware.Middleware, error) { log.Printf("Warning: TLS was disabled on host http://%s."+ " Make sure you are specifying https://%s in your config (if you haven't already)."+ " If you meant to serve tls on port 80,"+ - " specify port 80 in your config (http://%s:80).", c.Host, c.Host, c.Host) + " specify port 80 in your config (https://%s:80).", c.Host, c.Host, c.Host) } for c.Next() { @@ -25,6 +53,64 @@ func TLS(c *Controller) (middleware.Middleware, error) { return nil, c.ArgErr() } c.TLS.Key = c.Val() + + // Optional block + for c.NextBlock() { + switch c.Val() { + case "protocols": + args := c.RemainingArgs() + if len(args) != 2 { + return nil, c.ArgErr() + } + value, ok := supportedProtocols[strings.ToLower(args[0])] + if !ok { + return nil, c.ArgErr() + } + c.TLS.ProtocolMinVersion = value + value, ok = supportedProtocols[strings.ToLower(args[1])] + if !ok { + return nil, c.ArgErr() + } + c.TLS.ProtocolMaxVersion = value + case "ciphers": + for c.NextArg() { + value, ok := supportedCiphers[strings.ToUpper(c.Val())] + if !ok { + return nil, c.ArgErr() + } + c.TLS.Ciphers = append(c.TLS.Ciphers, value) + } + case "cache": + if !c.NextArg() { + return nil, c.ArgErr() + } + c.TLS.CacheSize, _ = strconv.Atoi(c.Val()) + default: + return nil, c.ArgErr() + } + } + } + + // If no Ciphers provided, use all caddy supportedCiphers + if len(c.TLS.Ciphers) == 0 { + for _, v := range supportedCiphers { + c.TLS.Ciphers = append(c.TLS.Ciphers, v) + } + } + + // If no ProtocolMin provided, set default MinVersion to TLSv1.1 for security reasons + if c.TLS.ProtocolMinVersion == 0 { + c.TLS.ProtocolMinVersion = tls.VersionTLS11 + } + + //If no ProtocolMax provided, use crypto/tls default MaxVersion(tls1.2) + if c.TLS.ProtocolMaxVersion == 0 { + c.TLS.ProtocolMaxVersion = tls.VersionTLS12 + } + + //If no cachesize provided, set default to 64 + if c.TLS.CacheSize == 0 { + c.TLS.CacheSize = 64 } return nil, nil diff --git a/config/setup/tls_test.go b/config/setup/tls_test.go new file mode 100644 index 00000000..11e98388 --- /dev/null +++ b/config/setup/tls_test.go @@ -0,0 +1,78 @@ +package setup + +import ( + "crypto/tls" + "testing" +) + +func TestTLSParseNoOptional(t *testing.T) { + c := newTestController(`tls cert.crt cert.key`) + + _, err := TLS(c) + if err != nil { + t.Errorf("Expected no errors, got: %v", err) + } + + if len(c.TLS.Ciphers) != len(supportedCiphers) { + t.Errorf("Expected %v Ciphers, got %v", len(supportedCiphers), len(c.TLS.Ciphers)) + } + + if c.TLS.ProtocolMinVersion != tls.VersionTLS11 { + t.Errorf("Expected 'tls1.1' ProtocolMinVersion , got %v", c.TLS.ProtocolMinVersion) + } + + if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 { + t.Errorf("Expected ProtocolMaxVersion 0, got %v", c.TLS.ProtocolMaxVersion) + } + + if c.TLS.CacheSize != 64 { + t.Errorf("Expected CacheSize 64, got %v", c.TLS.CacheSize) + } +} + +func TestTLSParseIncompleteParams(t *testing.T) { + c := newTestController(`tls`) + + _, err := TLS(c) + if err == nil { + t.Errorf("Expected errors, but no error returned") + } + + c = newTestController(`tls cert.key`) + + _, err = TLS(c) + if err == nil { + t.Errorf("Expected errors, but no error returned") + } + +} + +func TestTLSParseWithOptionalParams(t *testing.T) { + params := `tls cert.crt cert.key { + protocols ssl3 tls1.2 + ciphers RSA-3DES-EDE-CBC-SHA RSA-AES256-CBC-SHA ECDHE-RSA-AES128-GCM-SHA256 + cache 128 + }` + c := newTestController(params) + + _, err := TLS(c) + if err != nil { + t.Errorf("Expected no errors, got: %v", err) + } + + if c.TLS.ProtocolMinVersion != tls.VersionSSL30 { + t.Errorf("Expected 'ssl3' ProtocolMinVersion, got %#v", c.TLS.ProtocolMinVersion) + } + + if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 { + t.Errorf("Expected 'tls1.2' ProtocolMaxVersion, got %v", c.TLS.ProtocolMaxVersion) + } + + if len(c.TLS.Ciphers) != 3 { + t.Errorf("Expected 3 Ciphers, got %v", len(c.TLS.Ciphers)) + } + + if c.TLS.CacheSize != 128 { + t.Errorf("Expected CacheSize 128, got %v", c.TLS.CacheSize) + } +} diff --git a/server/config.go b/server/config.go index b037b613..502ba423 100644 --- a/server/config.go +++ b/server/config.go @@ -55,8 +55,13 @@ func (c Config) Address() string { // TLSConfig describes how TLS should be configured and used, // if at all. A certificate and key are both required. +// Ciphers, Protocols and CacheSize are optional type TLSConfig struct { - Enabled bool - Certificate string - Key string + Enabled bool + Certificate string + Key string + Ciphers []uint16 + ProtocolMinVersion uint16 + ProtocolMaxVersion uint16 + CacheSize int } diff --git a/server/server.go b/server/server.go index 2416f21d..e139e760 100644 --- a/server/server.go +++ b/server/server.go @@ -132,8 +132,18 @@ func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error { } config.BuildNameToCertificate() - // Add a session cache LRU algorithm with default capacity (64) - config.ClientSessionCache = tls.NewLRUClientSessionCache(0) + // Here we change some crypto/tls defaults based on caddyfile + // If no config provided, we set defaults focused in security + + // Add a session cache LRU algorithm + config.ClientSessionCache = tls.NewLRUClientSessionCache(tlsConfigs[0].CacheSize) + + config.MinVersion = tlsConfigs[0].ProtocolMinVersion + config.MaxVersion = tlsConfigs[0].ProtocolMaxVersion + config.CipherSuites = tlsConfigs[0].Ciphers + + // Server ciphers have priority over client ciphers + config.PreferServerCipherSuites = true conn, err := net.Listen("tcp", addr) if err != nil {