diff --git a/README.md b/README.md index e0242403..d4819dcb 100644 --- a/README.md +++ b/README.md @@ -478,7 +478,7 @@ Note that `_INBOX.*` subscribe permissions must be granted in order to use the r ### TLS As of Release 0.7.0, the server can use modern TLS semantics for client connections, route connections, and the HTTPS monitoring port. -The server requires TLS version 1.2, and sets preferences for modern cipher suites that avoid those known with vunerabilities. The +The server requires TLS version 1.2, and sets preferences for modern cipher suites that avoid those known with vulnerabilities. The server's preferences when building with Go1.5 are as follows. ```go @@ -492,7 +492,17 @@ func defaultCipherSuites() []uint16 { } } ``` - +The curve preferences are also re-ordered to provide the most secure +environment available, and are as follows: +```go +func defaultCurvePreferences() []tls.CurveID { + return []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + } +} +``` Generating self signed certs and intermediary certificate authorities is beyond the scope here, but this document can be helpful in addition to Google Search: https://docs.docker.com/engine/articles/https/. The server **requires** a certificate and private key. Optionally the server can require that clients need to present certificates, and the server can be configured with a CA authority to verify the client certificates. diff --git a/server/ciphersuites_1.4.go b/server/ciphersuites_1.4.go deleted file mode 100644 index 731cf2e7..00000000 --- a/server/ciphersuites_1.4.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 Apcera Inc. All rights reserved. - -// +build go1.4,!go1.5 - -package server - -import ( - "crypto/tls" -) - -// Where we maintain all of the available 1.4 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, -} - -func defaultCipherSuites() []uint16 { - return []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - } -} diff --git a/server/ciphersuites_1.5.go b/server/ciphersuites_1.5.go index 7b382b5b..1283c64d 100644 --- a/server/ciphersuites_1.5.go +++ b/server/ciphersuites_1.5.go @@ -1,4 +1,4 @@ -// Copyright 2015 Apcera Inc. All rights reserved. +// Copyright 2016 Apcera Inc. All rights reserved. // +build go1.5 @@ -8,7 +8,7 @@ import ( "crypto/tls" ) -// Where we maintain all of the available 1.5 ciphers +// Where we maintain 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, @@ -29,10 +29,27 @@ var cipherMap = map[string]uint16{ func defaultCipherSuites() []uint16 { return []uint16{ - // The SHA384 versions are only in Go1.5 + // The SHA384 versions are only in Go1.5 and beyond 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, } } + +// Where we maintain available curve preferences +var curvePreferenceMap = map[string]tls.CurveID{ + "CurveP256": tls.CurveP256, + "CurveP384": tls.CurveP384, + "CurveP521": tls.CurveP521, +} + +// reorder to default to the highest level of security. See: +// https://blog.bracebin.com/achieving-perfect-ssl-labs-score-with-go +func defaultCurvePreferences() []tls.CurveID { + return []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + } +} diff --git a/server/configs/tls_bad_curve_prefs.conf b/server/configs/tls_bad_curve_prefs.conf new file mode 100644 index 00000000..fbc0caf2 --- /dev/null +++ b/server/configs/tls_bad_curve_prefs.conf @@ -0,0 +1,13 @@ + +# Simple TLS config file + +listen: localhost:4443 + +tls { + cert_file: "./configs/certs/server.pem" + key_file: "./configs/certs/key.pem" + timeout: 2 + curve_preferences: [ + "GARBAGE" + ] +} diff --git a/server/configs/tls_curve_prefs.conf b/server/configs/tls_curve_prefs.conf new file mode 100644 index 00000000..27977236 --- /dev/null +++ b/server/configs/tls_curve_prefs.conf @@ -0,0 +1,13 @@ + +# Simple TLS config file + +listen: localhost:4443 + +tls { + cert_file: "./configs/certs/server.pem" + key_file: "./configs/certs/key.pem" + timeout: 2 + curve_preferences: [ + "CurveP256" + ] +} diff --git a/server/configs/tls_empty_curve_prefs.conf b/server/configs/tls_empty_curve_prefs.conf new file mode 100644 index 00000000..b655c77b --- /dev/null +++ b/server/configs/tls_empty_curve_prefs.conf @@ -0,0 +1,12 @@ + +# Simple TLS config file + +listen: localhost:4443 + +tls { + cert_file: "./configs/certs/server.pem" + key_file: "./configs/certs/key.pem" + timeout: 2 + curve_preferences: [ + ] +} diff --git a/server/opts.go b/server/opts.go index a02962cd..70ca94f2 100644 --- a/server/opts.go +++ b/server/opts.go @@ -98,12 +98,13 @@ type authorization struct { // TLSConfigOpts holds the parsed tls config information, // used with flag parsing type TLSConfigOpts struct { - CertFile string - KeyFile string - CaFile string - Verify bool - Timeout float64 - Ciphers []uint16 + CertFile string + KeyFile string + CaFile string + Verify bool + Timeout float64 + Ciphers []uint16 + CurvePreferences []tls.CurveID } var tlsUsage = ` @@ -121,6 +122,11 @@ e.g. "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256" ] + curve_preferences: [ + "CurveP256", + "CurveP384", + "CurveP521" + ] } Available cipher suites include: @@ -471,11 +477,14 @@ func checkSubjectArray(sa []string) ([]string, error) { // PrintTLSHelpAndDie prints TLS usage and exits. func PrintTLSHelpAndDie() { - fmt.Printf("%s\n", tlsUsage) + fmt.Printf("%s", tlsUsage) for k := range cipherMap { fmt.Printf(" %s\n", k) } - fmt.Printf("\n") + fmt.Printf("\nAvailable curve preferences include:\n") + for k := range curvePreferenceMap { + fmt.Printf(" %s\n", k) + } os.Exit(0) } @@ -489,6 +498,14 @@ func parseCipher(cipherName string) (uint16, error) { return cipher, nil } +func parseCurvePreferences(curveName string) (tls.CurveID, error) { + curve, exists := curvePreferenceMap[curveName] + if !exists { + return 0, fmt.Errorf("Unrecognized curve preference %s", curveName) + } + return curve, nil +} + // Helper function to parse TLS configs. func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) { tc := TLSConfigOpts{} @@ -531,6 +548,19 @@ func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) { } tc.Ciphers = append(tc.Ciphers, cipher) } + case "curve_preferences": + ra := mv.([]interface{}) + if len(ra) == 0 { + return nil, fmt.Errorf("error parsing tls config, 'curve_preferences' cannot be empty") + } + tc.CurvePreferences = make([]tls.CurveID, 0, len(ra)) + for _, r := range ra { + cps, err := parseCurvePreferences(r.(string)) + if err != nil { + return nil, err + } + tc.CurvePreferences = append(tc.CurvePreferences, cps) + } case "timeout": at := float64(0) switch mv.(type) { @@ -550,6 +580,11 @@ func parseTLS(tlsm map[string]interface{}) (*TLSConfigOpts, error) { tc.Ciphers = defaultCipherSuites() } + // If curve preferences were not specified, then use the defaults + if tc.CurvePreferences == nil { + tc.CurvePreferences = defaultCurvePreferences() + } + return &tc, nil } @@ -569,6 +604,7 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) { // Create TLSConfig // We will determine the cipher suites that we prefer. config := tls.Config{ + CurvePreferences: tc.CurvePreferences, Certificates: []tls.Certificate{cert}, PreferServerCipherSuites: true, MinVersion: tls.VersionTLS12, diff --git a/server/opts_test.go b/server/opts_test.go index a8302ef0..2554335f 100644 --- a/server/opts_test.go +++ b/server/opts_test.go @@ -159,13 +159,49 @@ func TestTLSConfigFile(t *testing.T) { // Test an unrecognized/bad cipher opts, err = ProcessConfigFile("./configs/tls_bad_cipher.conf") if err == nil { - t.Fatalf("Did not receive an error from a unrecognized cipher.") + t.Fatal("Did not receive an error from a unrecognized cipher.") } // Test an empty cipher entry in a config file. opts, err = ProcessConfigFile("./configs/tls_empty_cipher.conf") if err == nil { - t.Fatalf("Did not receive an error from empty cipher_suites.") + t.Fatal("Did not receive an error from empty cipher_suites.") + } + + // Test a curve preference from the config. + curves := []tls.CurveID{ + tls.CurveP256, + } + + // test on a file that will load the curve preference defaults + opts, err = ProcessConfigFile("./configs/tls_ciphers.conf") + if err != nil { + t.Fatalf("Received an error reading config file: %v\n", err) + } + + if !reflect.DeepEqual(opts.TLSConfig.CurvePreferences, defaultCurvePreferences()) { + t.Fatalf("Got incorrect curve preference list: [%+v]", tlsConfig.CurvePreferences) + } + + // Test specifying a single curve preference + opts, err = ProcessConfigFile("./configs/tls_curve_prefs.conf") + if err != nil { + t.Fatal("Did not receive an error from a unrecognized cipher.") + } + + if !reflect.DeepEqual(opts.TLSConfig.CurvePreferences, curves) { + t.Fatalf("Got incorrect cipher suite list: [%+v]", tlsConfig.CurvePreferences) + } + + // Test an unrecognized/bad curve preference + opts, err = ProcessConfigFile("./configs/tls_bad_curve_prefs.conf") + if err == nil { + t.Fatal("Did not receive an error from a unrecognized curve preference.") + } + // Test an empty curve preference + opts, err = ProcessConfigFile("./configs/tls_empty_curve_prefs.conf") + if err == nil { + t.Fatal("Did not receive an error from empty curve preferences.") } } diff --git a/test/configs/tls_curve_pref.conf b/test/configs/tls_curve_pref.conf new file mode 100644 index 00000000..33894ce8 --- /dev/null +++ b/test/configs/tls_curve_pref.conf @@ -0,0 +1,24 @@ + +# Simple TLS config file + +listen: localhost:4443 + +https: 11522 + +tls { + # Server cert + cert_file: "./configs/certs/server-cert.pem" + # Server private key + key_file: "./configs/certs/server-key.pem" + # Specified time for handshake to complete + timeout: 2 + curve_preferences: [ + "CurveP256" + ] +} + +authorization { + user: derek + password: boo + timeout: 1 +} diff --git a/test/tls_test.go b/test/tls_test.go index aad5c109..0c5b9b30 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -251,3 +251,47 @@ func TestTLSBadAuthError(t *testing.T) { t.Fatalf("Excpected and auth violation, got %v\n", err) } } + +func TestTLSConnectionCurvePref(t *testing.T) { + srv, opts := RunServerWithConfig("./configs/tls_curve_pref.conf") + defer srv.Shutdown() + + if len(opts.TLSConfig.CurvePreferences) != 1 { + t.Fatal("Invalid curve preference loaded.") + } + + if opts.TLSConfig.CurvePreferences[0] != tls.CurveP256 { + t.Fatalf("Invalid curve preference loaded [%v].", opts.TLSConfig.CurvePreferences[0]) + } + + endpoint := fmt.Sprintf("%s:%d", opts.Host, opts.Port) + nurl := fmt.Sprintf("tls://%s:%s@%s/", opts.Username, opts.Password, endpoint) + nc, err := nats.Connect(nurl) + if err == nil { + t.Fatalf("Expected error trying to connect to secure server") + } + + // Do simple SecureConnect + nc, err = nats.Connect(fmt.Sprintf("tls://%s/", endpoint)) + if err == nil { + t.Fatalf("Expected error trying to connect to secure server with no auth") + } + + // Now do more advanced checking, verifying servername and using rootCA. + + nc, err = nats.Connect(nurl, nats.RootCAs("./configs/certs/ca.pem")) + if err != nil { + t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err) + } + defer nc.Close() + + subj := "foo-tls" + sub, _ := nc.SubscribeSync(subj) + + nc.Publish(subj, []byte("We are Secure!")) + nc.Flush() + nmsgs, _ := sub.QueuedMsgs() + if nmsgs != 1 { + t.Fatalf("Expected to receive a message over the TLS connection") + } +}