Commit d6df6155 authored by Matthew Holt's avatar Matthew Holt

tls: Mainstream compatibility improvements, better security rating

parent ce6e30c0
...@@ -6,7 +6,6 @@ import ( ...@@ -6,7 +6,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/mholt/caddy/app"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
) )
...@@ -40,7 +39,6 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -40,7 +39,6 @@ func TLS(c *Controller) (middleware.Middleware, error) {
value, ok := supportedProtocols[strings.ToLower(args[0])] value, ok := supportedProtocols[strings.ToLower(args[0])]
if !ok { if !ok {
return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val()) return nil, c.Errf("Wrong protocol name or protocol not supported '%s'", c.Val())
} }
c.TLS.ProtocolMinVersion = value c.TLS.ProtocolMinVersion = value
value, ok = supportedProtocols[strings.ToLower(args[1])] value, ok = supportedProtocols[strings.ToLower(args[1])]
...@@ -50,13 +48,10 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -50,13 +48,10 @@ func TLS(c *Controller) (middleware.Middleware, error) {
c.TLS.ProtocolMaxVersion = value c.TLS.ProtocolMaxVersion = value
case "ciphers": case "ciphers":
for c.NextArg() { for c.NextArg() {
value, ok := supportedCiphers[strings.ToUpper(c.Val())] value, ok := supportedCiphersMap[strings.ToUpper(c.Val())]
if !ok { if !ok {
return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val()) return nil, c.Errf("Wrong cipher name or cipher not supported '%s'", c.Val())
} }
if _, ok := http2CipherSuites[value]; app.Http2 && !ok {
return nil, c.Errf("Cipher suite %s is not allowed for HTTP/2", c.Val())
}
c.TLS.Ciphers = append(c.TLS.Ciphers, value) c.TLS.Ciphers = append(c.TLS.Ciphers, value)
} }
case "cache": case "cache":
...@@ -65,7 +60,7 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -65,7 +60,7 @@ func TLS(c *Controller) (middleware.Middleware, error) {
} }
size, err := strconv.Atoi(c.Val()) size, err := strconv.Atoi(c.Val())
if err != nil { if err != nil {
return nil, c.Errf("Cache parameter must be an number '%s': %v", c.Val(), err) return nil, c.Errf("Cache parameter must be a number '%s': %v", c.Val(), err)
} }
c.TLS.CacheSize = size c.TLS.CacheSize = size
default: default:
...@@ -76,19 +71,16 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -76,19 +71,16 @@ func TLS(c *Controller) (middleware.Middleware, error) {
// If no ciphers provided, use all that Caddy supports for the protocol // If no ciphers provided, use all that Caddy supports for the protocol
if len(c.TLS.Ciphers) == 0 { if len(c.TLS.Ciphers) == 0 {
for _, v := range supportedCiphers { c.TLS.Ciphers = supportedCiphers
if _, ok := http2CipherSuites[v]; !app.Http2 || ok {
c.TLS.Ciphers = append(c.TLS.Ciphers, v)
}
}
} }
// If no ProtocolMin provided, set default MinVersion to TLSv1.1 for security reasons // Not a cipher suite, but still important for mitigating protocol downgrade attacks
c.TLS.Ciphers = append(c.TLS.Ciphers, tls.TLS_FALLBACK_SCSV)
// Set default protocol min and max versions - must balance compatibility and security
if c.TLS.ProtocolMinVersion == 0 { if c.TLS.ProtocolMinVersion == 0 {
c.TLS.ProtocolMinVersion = tls.VersionTLS11 c.TLS.ProtocolMinVersion = tls.VersionTLS10
} }
//If no ProtocolMax provided, use crypto/tls default MaxVersion(tls1.2)
if c.TLS.ProtocolMaxVersion == 0 { if c.TLS.ProtocolMaxVersion == 0 {
c.TLS.ProtocolMaxVersion = tls.VersionTLS12 c.TLS.ProtocolMaxVersion = tls.VersionTLS12
} }
...@@ -98,6 +90,9 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -98,6 +90,9 @@ func TLS(c *Controller) (middleware.Middleware, error) {
c.TLS.CacheSize = 64 c.TLS.CacheSize = 64
} }
// Prefer server cipher suites
c.TLS.PreferServerCipherSuites = true
return nil, nil return nil, nil
} }
...@@ -111,11 +106,17 @@ var supportedProtocols = map[string]uint16{ ...@@ -111,11 +106,17 @@ var supportedProtocols = map[string]uint16{
"tls1.2": tls.VersionTLS12, "tls1.2": tls.VersionTLS12,
} }
// Map of supported ciphers. // Map of supported ciphers, used only for parsing config.
// //
// Note that, at time of writing, HTTP/2 blacklists 276 cipher suites, // Note that, at time of writing, HTTP/2 blacklists 276 cipher suites,
// including all but two of the suites below (the two GCM suites). // including all but two of the suites below (the two GCM suites).
var supportedCiphers = map[string]uint16{ // See https://http2.github.io/http2-spec/#BadCipherSuites
//
// TLS_FALLBACK_SCSV is not in this list because we manually ensure
// it is always added (even though it is not technically a cipher suite).
//
// This map, like any map, is NOT ORDERED. Do not range over this map.
var supportedCiphersMap = map[string]uint16{
"ECDHE-RSA-AES128-GCM-SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "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-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-AES128-CBC-SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
...@@ -128,9 +129,21 @@ var supportedCiphers = map[string]uint16{ ...@@ -128,9 +129,21 @@ var supportedCiphers = map[string]uint16{
"RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, "RSA-3DES-EDE-CBC-SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
} }
// Set of cipher suites not blacklisted by HTTP/2 spec. // List of supported cipher suites in descending order of preference.
// See https://http2.github.io/http2-spec/#BadCipherSuites // Ordering is very important! Getting the wrong order will break
var http2CipherSuites = map[uint16]struct{}{ // mainstream clients, especially with HTTP/2.
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: struct{}{}, //
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: struct{}{}, // Note that TLS_FALLBACK_SCSV is not in this list since it is always
// added manually.
var supportedCiphers = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
} }
...@@ -3,8 +3,6 @@ package setup ...@@ -3,8 +3,6 @@ package setup
import ( import (
"crypto/tls" "crypto/tls"
"testing" "testing"
"github.com/mholt/caddy/app"
) )
func TestTLSParseBasic(t *testing.T) { func TestTLSParseBasic(t *testing.T) {
...@@ -12,9 +10,10 @@ func TestTLSParseBasic(t *testing.T) { ...@@ -12,9 +10,10 @@ func TestTLSParseBasic(t *testing.T) {
_, err := TLS(c) _, err := TLS(c)
if err != nil { if err != nil {
t.Error("Expected no errors, but had an error") t.Errorf("Expected no errors, got: %v", err)
} }
// Basic checks
if c.TLS.Certificate != "cert.pem" { if c.TLS.Certificate != "cert.pem" {
t.Errorf("Expected certificate arg to be 'cert.pem', was '%s'", c.TLS.Certificate) t.Errorf("Expected certificate arg to be 'cert.pem', was '%s'", c.TLS.Certificate)
} }
...@@ -24,30 +23,48 @@ func TestTLSParseBasic(t *testing.T) { ...@@ -24,30 +23,48 @@ func TestTLSParseBasic(t *testing.T) {
if !c.TLS.Enabled { if !c.TLS.Enabled {
t.Error("Expected TLS Enabled=true, but was false") t.Error("Expected TLS Enabled=true, but was false")
} }
}
func TestTLSParseNoOptional(t *testing.T) { // Security defaults
c := newTestController(`tls cert.crt cert.key`) if c.TLS.ProtocolMinVersion != tls.VersionTLS10 {
t.Errorf("Expected 'tls1.0 (0x0301)' as ProtocolMinVersion, got %#v", c.TLS.ProtocolMinVersion)
_, err := TLS(c) }
if err != nil { if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 {
t.Errorf("Expected no errors, got: %v", err) t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMaxVersion, got %v", c.TLS.ProtocolMaxVersion)
}
if c.TLS.CacheSize != 64 {
t.Errorf("Expected CacheSize 64, got %v", c.TLS.CacheSize)
} }
if len(c.TLS.Ciphers) != len(supportedCiphers) { // Cipher checks
t.Errorf("Expected %v Ciphers, got %v", len(supportedCiphers), len(c.TLS.Ciphers)) expectedCiphers := []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_FALLBACK_SCSV,
} }
if c.TLS.ProtocolMinVersion != tls.VersionTLS11 { // Ensure count is correct (plus one for TLS_FALLBACK_SCSV)
t.Errorf("Expected 'tls1.1 (0x0302)' as ProtocolMinVersion, got %#v", c.TLS.ProtocolMinVersion) if len(c.TLS.Ciphers) != len(supportedCiphers)+1 {
t.Errorf("Expected %v Ciphers (including TLS_FALLBACK_SCSV), got %v",
len(supportedCiphers)+1, len(c.TLS.Ciphers))
} }
if c.TLS.ProtocolMaxVersion != tls.VersionTLS12 { // Ensure ordering is correct
t.Errorf("Expected 'tls1.2 (0x0303)' as ProtocolMaxVersion, got %v", c.TLS.ProtocolMaxVersion) for i, actual := range c.TLS.Ciphers {
if actual != expectedCiphers[i] {
t.Errorf("Expected cipher in position %d to be %0x, got %0x", i, expectedCiphers[i], actual)
}
} }
if c.TLS.CacheSize != 64 { if !c.TLS.PreferServerCipherSuites {
t.Errorf("Expected CacheSize 64, got %v", c.TLS.CacheSize) t.Error("Expected PreferServerCipherSuites = true, but was false")
} }
} }
...@@ -88,8 +105,8 @@ func TestTLSParseWithOptionalParams(t *testing.T) { ...@@ -88,8 +105,8 @@ func TestTLSParseWithOptionalParams(t *testing.T) {
t.Errorf("Expected 'tls1.2 (0x0302)' as ProtocolMaxVersion, got %#v", c.TLS.ProtocolMaxVersion) t.Errorf("Expected 'tls1.2 (0x0302)' as ProtocolMaxVersion, got %#v", c.TLS.ProtocolMaxVersion)
} }
if len(c.TLS.Ciphers) != 3 { if len(c.TLS.Ciphers)-1 != 3 {
t.Errorf("Expected 3 Ciphers, got %v", len(c.TLS.Ciphers)) t.Errorf("Expected 3 Ciphers (not including TLS_FALLBACK_SCSV), got %v", len(c.TLS.Ciphers))
} }
if c.TLS.CacheSize != 128 { if c.TLS.CacheSize != 128 {
...@@ -127,43 +144,3 @@ func TestTLSParseWithWrongOptionalParams(t *testing.T) { ...@@ -127,43 +144,3 @@ func TestTLSParseWithWrongOptionalParams(t *testing.T) {
t.Errorf("Expected errors, but no error returned") t.Errorf("Expected errors, but no error returned")
} }
} }
func TestTLSParseWithHTTP2Requirements(t *testing.T) {
params := `tls cert.crt cert.key`
c := newTestController(params)
// With HTTP2, cipher suites should be limited
app.Http2 = true
_, err := TLS(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if len(c.TLS.Ciphers) != len(http2CipherSuites) {
t.Errorf("With HTTP/2 on, expected %d supported ciphers, got %d",
len(http2CipherSuites), len(c.TLS.Ciphers))
}
params = `tls cert.crt cert.key {
ciphers RSA-AES128-CBC-SHA
}`
c = newTestController(params)
// Should not be able to specify a blacklisted cipher suite with HTTP2 on
_, err = TLS(c)
if err == nil {
t.Error("Expected an error because cipher suite is invalid for HTTP/2")
}
params = `tls cert.crt cert.key`
c = newTestController(params)
// Without HTTP2, cipher suites should not be as restricted
app.Http2 = false
_, err = TLS(c)
if err != nil {
t.Errorf("Expected no errors, got: %v", err)
}
if len(c.TLS.Ciphers) != len(supportedCiphers) {
t.Errorf("With HTTP/2 off, expected %d supported ciphers, got %d",
len(supportedCiphers), len(c.TLS.Ciphers))
}
}
...@@ -55,13 +55,14 @@ func (c Config) Address() string { ...@@ -55,13 +55,14 @@ func (c Config) Address() string {
// TLSConfig describes how TLS should be configured and used, // TLSConfig describes how TLS should be configured and used,
// if at all. A certificate and key are both required. // if at all. A certificate and key are both required.
// Ciphers, Protocols and CacheSize are optional // The rest is optional.
type TLSConfig struct { type TLSConfig struct {
Enabled bool Enabled bool
Certificate string Certificate string
Key string Key string
Ciphers []uint16 Ciphers []uint16
ProtocolMinVersion uint16 ProtocolMinVersion uint16
ProtocolMaxVersion uint16 ProtocolMaxVersion uint16
CacheSize int CacheSize int
PreferServerCipherSuites bool
} }
...@@ -137,7 +137,7 @@ func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error { ...@@ -137,7 +137,7 @@ func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
config.MinVersion = tlsConfigs[0].ProtocolMinVersion config.MinVersion = tlsConfigs[0].ProtocolMinVersion
config.MaxVersion = tlsConfigs[0].ProtocolMaxVersion config.MaxVersion = tlsConfigs[0].ProtocolMaxVersion
config.CipherSuites = tlsConfigs[0].Ciphers config.CipherSuites = tlsConfigs[0].Ciphers
config.PreferServerCipherSuites = true config.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites
conn, err := net.Listen("tcp", addr) conn, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment