From 0ff1b55fa96a85316d96d3e3a2ed92eccde35eda Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 20 Nov 2015 16:00:25 -0700 Subject: [PATCH] Require cipher suites to be specified in the configuration. * Configuration requires a cipher suite * Removed default cipher suites * Added help to assist with TLS configuration and list available cipher suites. --- gnatsd.go | 6 +++ server/configs/tls.conf | 4 ++ server/configs/tls_ciphers.conf | 33 ++++++++++++ server/opts.go | 94 +++++++++++++++++++++++++++++---- server/opts_test.go | 37 ++++++++++++- server/usage.go | 1 + test/configs/srv_a_tls.conf | 4 ++ test/configs/srv_b_tls.conf | 4 ++ test/configs/tls.conf | 4 ++ test/configs/tlsverify.conf | 5 ++ 10 files changed, 181 insertions(+), 11 deletions(-) create mode 100644 server/configs/tls_ciphers.conf diff --git a/gnatsd.go b/gnatsd.go index 5cbee58f..862f81d7 100644 --- a/gnatsd.go +++ b/gnatsd.go @@ -19,6 +19,7 @@ func main() { var showVersion bool var debugAndTrace bool var configFile string + var showTlsHelp bool // Parse flags flag.IntVar(&opts.Port, "port", 0, "Port to listen on.") @@ -52,6 +53,7 @@ func main() { flag.BoolVar(&showVersion, "v", false, "Print version information.") flag.IntVar(&opts.ProfPort, "profile", 0, "Profiling HTTP port") flag.StringVar(&opts.RoutesStr, "routes", "", "Routes to actively solicit a connection.") + flag.BoolVar(&showTlsHelp, "help_tls", false, "TLS help.") // Not public per se, will be replaced with dynamic system, but can be used to lower memory footprint when // lots of connections present. @@ -66,6 +68,10 @@ func main() { server.PrintServerAndExit() } + if showTlsHelp { + server.PrintTlsHelpAndDie() + } + // One flag can set multiple options. if debugAndTrace { opts.Trace, opts.Debug = true, true diff --git a/server/configs/tls.conf b/server/configs/tls.conf index 54fe8788..7368b01b 100644 --- a/server/configs/tls.conf +++ b/server/configs/tls.conf @@ -7,6 +7,10 @@ net: apcera.me # net interface tls { cert_file: "./configs/certs/server.pem" key_file: "./configs/certs/key.pem" + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } authorization { diff --git a/server/configs/tls_ciphers.conf b/server/configs/tls_ciphers.conf new file mode 100644 index 00000000..3e24016b --- /dev/null +++ b/server/configs/tls_ciphers.conf @@ -0,0 +1,33 @@ + +# Simple TLS config file + +port: 4443 +net: apcera.me # net interface + +tls { + cert_file: "./configs/certs/server.pem" + key_file: "./configs/certs/key.pem" + cipher_suites: [ + "TLS_RSA_WITH_RC4_128_SHA", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" +# "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", +# "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384" + ] +} + +authorization { + user: derek + password: buckley + timeout: 1 +} diff --git a/server/opts.go b/server/opts.go index 5ee3bce0..388454a8 100644 --- a/server/opts.go +++ b/server/opts.go @@ -14,6 +14,7 @@ import ( "time" "github.com/nats-io/gnatsd/conf" + "os" ) // Options block for gnatsd server. @@ -66,6 +67,7 @@ type tlsConfig struct { keyFile string caFile string verify bool + ciphers []uint16 } // ProcessConfigFile processes a configuration file. @@ -199,6 +201,67 @@ func parseAuthorization(am map[string]interface{}) authorization { return auth } +// keep one place where we hold all of the available ciphers +var cipherMap = map [string]uint16 { + "TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA, + "TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + "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_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + "TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + "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, + // go 1.5 "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + // go 1.5 "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, +} + +// For Usage... +func PrintTlsHelpAndDie() { + + var tlsUsage = ` +TLS configuration is specified in the tls section of a configuration file: + +e.g. + + tls { + cert_file: "./certs/server-cert.pem" + key_file: "./certs/server-key.pem" + ca_file: "./certs/ca.pem" + verify: true + + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] + } + +Available cipher suites include: +` + + fmt.Printf("%s\n", tlsUsage) + for k, _ := range cipherMap { + fmt.Printf(" %s\n", k); + } + fmt.Printf("\n") + + os.Exit(0) +} + +func parseCipher(cipherName string) (uint16, error) { + + cipher, exists := cipherMap[cipherName]; + if !exists { + return 0, fmt.Errorf("Unrecognized cipher %s", cipherName); + } + + return cipher, nil; +} + // Helper function to parse TLS configs. func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) { tc := tlsConfig{} @@ -228,11 +291,29 @@ func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) { return nil, fmt.Errorf("error parsing tls config, expected 'verify' to be a boolean") } tc.verify = verify - + case "cipher_suites": + ra := mv.([]interface{}) + if len(ra) == 0 { + return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' cannot be empty.") + } + tc.ciphers = make([]uint16, 0, len(ra)) + for _, r := range ra { + cipher, err := parseCipher(r.(string)) + if err != nil { + return nil, err + } + tc.ciphers = append(tc.ciphers, cipher) + } default: return nil, fmt.Errorf("error parsing tls config, unknown field [%q]", mk) } } + + // specifing a cipher suite is required. + if (tc.ciphers == nil) { + return nil, fmt.Errorf("error parsing tls config, 'cipher_suites' not present.") + } + // Now load in cert and private key cert, err := tls.LoadX509KeyPair(tc.certFile, tc.keyFile) if err != nil { @@ -242,21 +323,16 @@ func parseTLS(tlsm map[string]interface{}) (*tls.Config, error) { if err != nil { return nil, fmt.Errorf("error parsing certificate: %v", err) } + // Create TLSConfig // We will determine the cipher suites that we prefer. config := tls.Config{ Certificates: []tls.Certificate{cert}, PreferServerCipherSuites: true, MinVersion: tls.VersionTLS12, - CipherSuites: []uint16{ - // The SHA384 versions are only in Go1.5 - // tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - // tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_RSA_WITH_RC4_128_SHA, - }, + CipherSuites: tc.ciphers, } + // Require client certificates as needed if tc.verify == true { config.ClientAuth = tls.RequireAnyClientCert diff --git a/server/opts_test.go b/server/opts_test.go index 6bb2dbf5..d40b0bc4 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -100,13 +100,12 @@ func TestTLSConfigFile(t *testing.T) { golden, opts) } // Now check TLSConfig a bit more closely - // CipherSuites + // The default CipherSuites we recommend ciphers := []uint16{ // tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_RSA_WITH_RC4_128_SHA, } if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) { t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites) @@ -125,6 +124,40 @@ func TestTLSConfigFile(t *testing.T) { if err := cert.VerifyHostname("localhost"); err != nil { t.Fatalf("Could not verify hostname in certificate: %v\n", err) } + + // Now test adding cipher suites. + opts, err = ProcessConfigFile("./configs/tls_ciphers.conf") + if err != nil { + t.Fatalf("Received an error reading config file: %v\n", err) + } + tlsConfig = opts.TLSConfig + if tlsConfig == nil { + t.Fatal("Expected opts.TLSConfig to be non-nil") + } + + // CipherSuites listed in the config - test all of them. + ciphers = []uint16{ + tls.TLS_RSA_WITH_RC4_128_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + //tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + //tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } + + if !reflect.DeepEqual(tlsConfig.CipherSuites, ciphers) { + t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CipherSuites) + } + } func TestMergeOverrides(t *testing.T) { diff --git a/server/usage.go b/server/usage.go index 97f9be56..6f7f4b7c 100644 --- a/server/usage.go +++ b/server/usage.go @@ -34,6 +34,7 @@ Cluster Options: Common Options: -h, --help Show this message -v, --version Show version + --help_tls TLS help. ` // Usage will print out the flag options for the server. diff --git a/test/configs/srv_a_tls.conf b/test/configs/srv_a_tls.conf index f56aa956..c4b78fc4 100644 --- a/test/configs/srv_a_tls.conf +++ b/test/configs/srv_a_tls.conf @@ -17,6 +17,10 @@ cluster { # Optional certificate authority verifying connected routes # Required when we have self-signed CA, etc. ca_file: "./configs/certs/ca.pem" + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } # Routes are actively solicited and connected to from this server. diff --git a/test/configs/srv_b_tls.conf b/test/configs/srv_b_tls.conf index 6dc268fc..f07b2041 100644 --- a/test/configs/srv_b_tls.conf +++ b/test/configs/srv_b_tls.conf @@ -17,6 +17,10 @@ cluster { # Optional certificate authority verifying connected routes # Required when we have self-signed CA, etc. ca_file: "./configs/certs/ca.pem" + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } # Routes are actively solicited and connected to from this server. diff --git a/test/configs/tls.conf b/test/configs/tls.conf index ac6a9aa2..e20e669c 100644 --- a/test/configs/tls.conf +++ b/test/configs/tls.conf @@ -9,6 +9,10 @@ tls { cert_file: "./configs/certs/server-cert.pem" # Server private key key_file: "./configs/certs/server-key.pem" + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] } authorization { diff --git a/test/configs/tlsverify.conf b/test/configs/tlsverify.conf index 531b477e..c59ed554 100644 --- a/test/configs/tlsverify.conf +++ b/test/configs/tlsverify.conf @@ -13,4 +13,9 @@ tls { ca_file: "./configs/certs/ca.pem" # Require a client certificate verify: true + + cipher_suites: [ + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" + ] }