Merge pull request #412 from nats-io/tls-curve-pref

Add curve preference option to configuration files
This commit is contained in:
Derek Collison
2017-01-04 09:03:58 -08:00
committed by GitHub
12 changed files with 285 additions and 50 deletions

View File

@@ -2,6 +2,7 @@ language: go
go:
- 1.6.4
- 1.7.4
- 1.8beta2
install:
- go get github.com/nats-io/go-nats
- go get github.com/mattn/goveralls
@@ -17,4 +18,4 @@ script:
- staticcheck -ignore "$(cat staticcheck.ignore)" $EXCLUDE_VENDOR
after_success:
- if [[ "$TRAVIS_GO_VERSION" == 1.7.* ]]; then ./scripts/cov.sh TRAVIS; fi
- if [[ "$TRAVIS_GO_VERSION" == 1.7.* ]] && [ "$TRAVIS_TAG" != "" ]; then ./scripts/cross_compile.sh $TRAVIS_TAG; ghr --owner nats-io --token $GITHUB_TOKEN --draft --replace $TRAVIS_TAG pkg/; fi
- if [[ "$TRAVIS_GO_VERSION" == 1.7.* ]] && [ "$TRAVIS_TAG" != "" ]; then ./scripts/cross_compile.sh $TRAVIS_TAG; ghr --owner nats-io --token $GITHUB_TOKEN --draft --replace $TRAVIS_TAG pkg/; fi

View File

@@ -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: <a href="https://docs.docker.com/engine/articles/https/" target="_blank">https://docs.docker.com/engine/articles/https/</a>.
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.

View File

@@ -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,
}
}

View File

@@ -1,6 +1,6 @@
// Copyright 2015 Apcera Inc. All rights reserved.
// Copyright 2016 Apcera Inc. All rights reserved.
// +build go1.5
// +build go1.5,!go1.8
package server
@@ -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,
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2016 Apcera Inc. All rights reserved.
// +build go1.8
package server
import (
"crypto/tls"
)
// 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,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"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_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"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,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
func defaultCipherSuites() []uint16 {
return []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,
}
}
// Where we maintain available curve preferences
var curvePreferenceMap = map[string]tls.CurveID{
"CurveP256": tls.CurveP256,
"CurveP384": tls.CurveP384,
"CurveP521": tls.CurveP521,
"X25519": tls.X25519,
}
// 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.X25519, // faster than P256, arguably more secure
tls.CurveP256,
}
}

View File

@@ -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"
]
}

View File

@@ -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"
]
}

View File

@@ -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: [
]
}

View File

@@ -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,

View File

@@ -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.")
}
}

View File

@@ -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
}

View File

@@ -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")
}
}