From c78eb50eb8e97f8195c7162952440374cab57b9b Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Mon, 1 Jun 2015 23:22:11 -0600 Subject: [PATCH 1/2] tls: Client authentication --- config/setup/tls.go | 5 +++++ config/setup/tls_test.go | 31 +++++++++++++++++++++++++++++ server/config.go | 1 + server/server.go | 42 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/config/setup/tls.go b/config/setup/tls.go index 94f2e11b..db311823 100644 --- a/config/setup/tls.go +++ b/config/setup/tls.go @@ -53,6 +53,11 @@ func TLS(c *Controller) (middleware.Middleware, error) { } c.TLS.Ciphers = append(c.TLS.Ciphers, value) } + case "clients": + c.TLS.ClientCerts = c.RemainingArgs() + if len(c.TLS.ClientCerts) == 0 { + return nil, c.ArgErr() + } default: return nil, c.Errf("Unknown keyword '%s'") } diff --git a/config/setup/tls_test.go b/config/setup/tls_test.go index a3f02e08..04ac6cce 100644 --- a/config/setup/tls_test.go +++ b/config/setup/tls_test.go @@ -127,3 +127,34 @@ func TestTLSParseWithWrongOptionalParams(t *testing.T) { t.Errorf("Expected errors, but no error returned") } } + +func TestTLSParseWithClientAuth(t *testing.T) { + params := `tls cert.crt cert.key { + clients client_ca.crt client2_ca.crt + }` + c := newTestController(params) + _, err := TLS(c) + if err != nil { + t.Errorf("Expected no errors, got: %v", err) + } + + if count := len(c.TLS.ClientCerts); count != 2 { + t.Fatalf("Expected two client certs, had %d", count) + } + if actual := c.TLS.ClientCerts[0]; actual != "client_ca.crt" { + t.Errorf("Expected first client cert file to be '%s', but was '%s'", "client_ca.crt", actual) + } + if actual := c.TLS.ClientCerts[1]; actual != "client2_ca.crt" { + t.Errorf("Expected second client cert file to be '%s', but was '%s'", "client2_ca.crt", actual) + } + + // Test missing client cert file + params = `tls cert.crt cert.key { + clients + }` + c = newTestController(params) + _, err = TLS(c) + if err == nil { + t.Errorf("Expected an error, but no error returned") + } +} diff --git a/server/config.go b/server/config.go index c02aec9a..6575b763 100644 --- a/server/config.go +++ b/server/config.go @@ -64,4 +64,5 @@ type TLSConfig struct { ProtocolMinVersion uint16 ProtocolMaxVersion uint16 PreferServerCipherSuites bool + ClientCerts []string } diff --git a/server/server.go b/server/server.go index 46e27c23..f50a26f1 100644 --- a/server/server.go +++ b/server/server.go @@ -5,7 +5,9 @@ package server import ( "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" "log" "net" "net/http" @@ -137,15 +139,53 @@ func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error { config.CipherSuites = tlsConfigs[0].Ciphers config.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites - conn, err := net.Listen("tcp", addr) + // TLS client authentication, if user enabled it + err = setupClientAuth(tlsConfigs, config) if err != nil { return err } + // Create listener and we're on our way + conn, err := net.Listen("tcp", addr) + if err != nil { + return err + } tlsListener := tls.NewListener(conn, config) + return srv.Serve(tlsListener) } +// setupClientAuth sets up TLS client authentication only if +// any of the TLS configs specified at least one cert file. +func setupClientAuth(tlsConfigs []TLSConfig, config *tls.Config) error { + var clientAuth bool + for _, cfg := range tlsConfigs { + if len(cfg.ClientCerts) > 0 { + clientAuth = true + break + } + } + + if clientAuth { + pool := x509.NewCertPool() + for _, cfg := range tlsConfigs { + for _, caFile := range cfg.ClientCerts { + caCrt, err := ioutil.ReadFile(caFile) // Anyone that gets a cert from Matt Holt can connect + if err != nil { + return err + } + if !pool.AppendCertsFromPEM(caCrt) { + return fmt.Errorf("Error loading client certificate '%s': no certificates were successfully parsed", caFile) + } + } + } + config.ClientCAs = pool + config.ClientAuth = tls.RequireAndVerifyClientCert + } + + return nil +} + // ServeHTTP is the entry point for every request to the address that s // is bound to. It acts as a multiplexer for the requests hostname as // defined in the Host header so that the correct virtualhost From 6c523681247aaef8b46cbff0797db11e0928a0b2 Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 2 Jun 2015 07:42:38 -0600 Subject: [PATCH 2/2] tls: Fix format string --- config/setup/tls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/setup/tls.go b/config/setup/tls.go index db311823..36ecac05 100644 --- a/config/setup/tls.go +++ b/config/setup/tls.go @@ -59,7 +59,7 @@ func TLS(c *Controller) (middleware.Middleware, error) { return nil, c.ArgErr() } default: - return nil, c.Errf("Unknown keyword '%s'") + return nil, c.Errf("Unknown keyword '%s'", c.Val()) } } }