Skip to content

Commit

Permalink
Add support for setting TLS min version number in tls block (#5904)
Browse files Browse the repository at this point in the history
For example, to set minimum version to TLS v1.3:

```
  tls { min_version = "1.3" }
```

Signed-off-by: Waldemar Quevedo <[email protected]>
  • Loading branch information
derekcollison authored Sep 19, 2024
2 parents a2ff03e + 77db0c0 commit 0d285e8
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 2 deletions.
32 changes: 32 additions & 0 deletions server/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,7 @@ type TLSConfigOpts struct {
CaCertsMatch []string
OCSPPeerConfig *certidp.OCSPPeerConfig
Certificates []*TLSCertPairOpt
MinVersion uint16
}

// TLSCertPairOpt are the paths to a certificate and private key.
Expand Down Expand Up @@ -4560,6 +4561,24 @@ func parseCurvePreferences(curveName string) (tls.CurveID, error) {
return curve, nil
}

func parseTLSVersion(v any) (uint16, error) {
var tlsVersionNumber uint16
switch v := v.(type) {
case string:
n, err := tlsVersionFromString(v)
if err != nil {
return 0, err
}
tlsVersionNumber = n
default:
return 0, fmt.Errorf("'min_version' wrong type: %v", v)
}
if tlsVersionNumber < tls.VersionTLS12 {
return 0, fmt.Errorf("unsupported TLS version: %s", tls.VersionName(tlsVersionNumber))
}
return tlsVersionNumber, nil
}

// Helper function to parse TLS configs.
func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
var (
Expand Down Expand Up @@ -4825,6 +4844,12 @@ func parseTLS(v any, isClientCtx bool) (t *TLSConfigOpts, retErr error) {
}
tc.Certificates[i] = certPair
}
case "min_version":
minVersion, err := parseTLSVersion(mv)
if err != nil {
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config: %v", err)}
}
tc.MinVersion = minVersion
default:
return nil, &configErr{tk, fmt.Sprintf("error parsing tls config, unknown field %q", mk)}
}
Expand Down Expand Up @@ -5199,6 +5224,13 @@ func GenTLSConfig(tc *TLSConfigOpts) (*tls.Config, error) {
}
config.ClientCAs = pool
}
// Allow setting TLS minimum version.
if tc.MinVersion > 0 {
if tc.MinVersion < tls.VersionTLS12 {
return nil, fmt.Errorf("unsupported minimum TLS version: %s", tls.VersionName(tc.MinVersion))
}
config.MinVersion = tc.MinVersion
}

return &config, nil
}
Expand Down
2 changes: 1 addition & 1 deletion server/reload.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,7 +1151,7 @@ func imposeOrder(value any) error {
slices.SortFunc(value.Gateways, func(i, j *RemoteGatewayOpts) int { return cmp.Compare(i.Name, j.Name) })
case WebsocketOpts:
slices.Sort(value.AllowedOrigins)
case string, bool, uint8, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
case string, bool, uint8, uint16, int, int32, int64, time.Duration, float64, nil, LeafNodeOpts, ClusterOpts, *tls.Config, PinnedCertSet,
*URLAccResolver, *MemAccResolver, *DirAccResolver, *CacheDirAccResolver, Authentication, MQTTOpts, jwt.TagList,
*OCSPConfig, map[string]string, JSLimitOpts, StoreCipher, *OCSPResponseCacheConfig:
// explicitly skipped types
Expand Down
14 changes: 14 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3506,6 +3506,20 @@ func tlsVersion(ver uint16) string {
return fmt.Sprintf("Unknown [0x%x]", ver)
}

func tlsVersionFromString(ver string) (uint16, error) {
switch ver {
case "1.0":
return tls.VersionTLS10, nil
case "1.1":
return tls.VersionTLS11, nil
case "1.2":
return tls.VersionTLS12, nil
case "1.3":
return tls.VersionTLS13, nil
}
return 0, fmt.Errorf("Unknown version: %v", ver)
}

// We use hex here so we don't need multiple versions
func tlsCipher(cs uint16) string {
name, present := cipherMapByID[cs]
Expand Down
97 changes: 96 additions & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,102 @@ func TestTLSVersions(t *testing.T) {
}
}

func TestTlsCipher(t *testing.T) {
func TestTLSMinVersionConfig(t *testing.T) {
tmpl := `
listen: "127.0.0.1:-1"
tls {
cert_file: "../test/configs/certs/server-cert.pem"
key_file: "../test/configs/certs/server-key.pem"
timeout: 1
min_version: %s
}
`
conf := createConfFile(t, []byte(fmt.Sprintf(tmpl, `"1.3"`)))
s, o := RunServerWithConfig(conf)
defer s.Shutdown()

connect := func(t *testing.T, tlsConf *tls.Config, expectedErr error) {
t.Helper()
opts := []nats.Option{}
if tlsConf != nil {
opts = append(opts, nats.Secure(tlsConf))
}
opts = append(opts, nats.RootCAs("../test/configs/certs/ca.pem"))
nc, err := nats.Connect(fmt.Sprintf("tls://localhost:%d", o.Port), opts...)
if expectedErr == nil {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
} else if err == nil || err.Error() != expectedErr.Error() {
nc.Close()
t.Fatalf("Expected error %v, got: %v", expectedErr, err)
}
}

// Cannot connect with client requiring a lower minimum TLS Version.
connect(t, &tls.Config{
MaxVersion: tls.VersionTLS12,
}, errors.New(`remote error: tls: protocol version not supported`))

// Should connect since matching minimum TLS version.
connect(t, &tls.Config{
MinVersion: tls.VersionTLS13,
}, nil)

// Reloading with invalid values should fail.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.0"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err == nil {
t.Fatalf("Expected reload to fail: %v", err)
}

// Reloading with original values and no changes should be ok.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.3"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Unexpected error reloading TLS version: %v", err)
}

// Reloading with a new minimum lower version.
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.2"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err != nil {
t.Fatalf("Unexpected error reloading: %v", err)
}

// Should connect since now matching minimum TLS version.
connect(t, &tls.Config{
MaxVersion: tls.VersionTLS12,
}, nil)
connect(t, &tls.Config{
MinVersion: tls.VersionTLS13,
}, nil)

// Setting unsupported TLS versions
if err := os.WriteFile(conf, []byte(fmt.Sprintf(tmpl, `"1.4"`)), 0666); err != nil {
t.Fatalf("Error creating config file: %v", err)
}
if err := s.Reload(); err == nil || !strings.Contains(err.Error(), `Unknown version: 1.4`) {
t.Fatalf("Unexpected error reloading: %v", err)
}

tc := &TLSConfigOpts{
CertFile: "../test/configs/certs/server-cert.pem",
KeyFile: "../test/configs/certs/server-key.pem",
CaFile: "../test/configs/certs/ca.pem",
Timeout: 4.0,
MinVersion: tls.VersionTLS11,
}
_, err := GenTLSConfig(tc)
if err == nil || err.Error() != `unsupported minimum TLS version: TLS 1.1` {
t.Fatalf("Expected error generating TLS config: %v", err)
}
}

func TestTLSCipher(t *testing.T) {
if strings.Compare(tlsCipher(0x0005), "TLS_RSA_WITH_RC4_128_SHA") != 0 {
t.Fatalf("Invalid tls cipher")
}
Expand Down

0 comments on commit 0d285e8

Please sign in to comment.