Skip to content

Commit

Permalink
Added in support for requiring client certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
derekcollison committed Nov 8, 2015
1 parent a7b7446 commit 1c7f708
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 5 deletions.
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# General

- [ ] SSL/TLS support
- [ ] Better user/pass support using bcrypt etc.
- [ ] Pedantic state
- [ ] brew, apt-get, rpm, chocately (windows)
- [ ] Dynamic socket buffer sizes
Expand Down
19 changes: 19 additions & 0 deletions server/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type authorization struct {
type tlsConfig struct {
certFile string
keyFile string
caFile string
verify bool
}

// ProcessConfigFile processes a configuration file.
Expand Down Expand Up @@ -207,6 +209,19 @@ func parseTLS(tlsm map[string]interface{}, opts *Options) error {
return fmt.Errorf("error parsing tls config, expected 'key_file' to be filename")
}
tc.keyFile = keyFile
case "ca_file":
caFile, ok := mv.(string)
if !ok {
return fmt.Errorf("error parsing tls config, expected 'ca_file' to be filename")
}
tc.caFile = caFile
case "verify":
verify, ok := mv.(bool)
if !ok {
return fmt.Errorf("error parsing tls config, expected 'veridy' to be a boolean")
}
tc.verify = verify

default:
return fmt.Errorf("error parsing tls config, unknown field [%q]", mk)
}
Expand Down Expand Up @@ -234,6 +249,10 @@ func parseTLS(tlsm map[string]interface{}, opts *Options) error {
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
// Require client certificates as needed
if tc.verify == true {
config.ClientAuth = tls.RequireAnyClientCert
}
opts.TLSConfig = &config
return nil
}
Expand Down
13 changes: 11 additions & 2 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ type Info struct {
Host string `json:"host"`
Port int `json:"port"`
AuthRequired bool `json:"auth_required"`
TLSRequired bool `json:"ssl_required"` // ssl json used for older clients
SSLRequired bool `json:"ssl_required"` // ssl json used for older clients
TLSRequired bool `json:"tls_required"`
TLSVerify bool `json:"tls_verify"`
MaxPayload int `json:"max_payload"`
}

Expand Down Expand Up @@ -75,14 +77,21 @@ type stats struct {
// New will setup a new server struct after parsing the options.
func New(opts *Options) *Server {
processOptions(opts)

// Process TLS options, including whether we require client certificates.
tlsReq := opts.TLSConfig != nil
verify := (tlsReq == true && opts.TLSConfig.ClientAuth == tls.RequireAnyClientCert)

info := Info{
ID: genID(),
Version: VERSION,
GoVersion: runtime.Version(),
Host: opts.Host,
Port: opts.Port,
AuthRequired: false,
TLSRequired: opts.TLSConfig != nil,
TLSRequired: tlsReq,
SSLRequired: tlsReq,
TLSVerify: verify,
MaxPayload: opts.MaxPayload,
}

Expand Down
7 changes: 4 additions & 3 deletions test/configs/tls.conf
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ port: 4443
net: localhost

tls {
# ca_file: "./configs/certs/ca.pem"
cert_file: "./configs/certs/server-cert.pem"
key_file: "./configs/certs/server-key.pem"
# Server cert
cert_file: "./configs/certs/server-cert.pem"
# Server private key
key_file: "./configs/certs/server-key.pem"
}

authorization {
Expand Down
16 changes: 16 additions & 0 deletions test/configs/tlsverify.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

# Simple TLS config file

port: 4443
net: localhost

tls {
# Server cert
cert_file: "./configs/certs/server-cert.pem"
# Server private key
key_file: "./configs/certs/server-key.pem"
# Optional certificate authority for clients
ca_file: "./configs/certs/ca.pem"
# Require a client certificate
verify: true
}
55 changes: 55 additions & 0 deletions test/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,58 @@ func TestTLSConnection(t *testing.T) {
nc.Flush()
defer nc.Close()
}

func TestTLSClientCertificate(t *testing.T) {
srv, opts := RunServerWithConfig("./configs/tlsverify.conf")
defer srv.Shutdown()

nurl := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)

_, err := nats.Connect(nurl)
if err == nil {
t.Fatalf("Expected error trying to connect to secure server without a certificate")
}

_, err = nats.SecureConnect(nurl)
if err == nil {
t.Fatalf("Expected error trying to secure connect to secure server without a certificate")
}

// Load client certificate to sucessfully connect.
certFile := "./configs/certs/client-cert.pem"
keyFile := "./configs/certs/client-key.pem"
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
t.Fatalf("error parsing X509 certificate/key pair: %v", err)
}

// Load in root CA for server verification
rootPEM, err := ioutil.ReadFile("./configs/certs/ca.pem")
if err != nil || rootPEM == nil {
t.Fatalf("failed to read root certificate")
}
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
t.Fatalf("failed to parse root certificate")
}

config := &tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: opts.Host,
RootCAs: pool,
MinVersion: tls.VersionTLS12,
}

copts := nats.DefaultOptions
copts.Url = nurl
copts.Secure = true
copts.TLSConfig = config

nc, err := copts.Connect()
if err != nil {
t.Fatalf("Got an error on Connect with Secure Options: %+v\n", err)
}
nc.Flush()
defer nc.Close()
}

0 comments on commit 1c7f708

Please sign in to comment.