Commit c78eb50e authored by Matthew Holt's avatar Matthew Holt

tls: Client authentication

parent 9ce0e8e1
......@@ -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'")
}
......
......@@ -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")
}
}
......@@ -64,4 +64,5 @@ type TLSConfig struct {
ProtocolMinVersion uint16
ProtocolMaxVersion uint16
PreferServerCipherSuites bool
ClientCerts []string
}
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment