0
Fork 0
mirror of https://github.com/caddyserver/caddy.git synced 2024-12-23 22:27:38 -05:00

Merge pull request #98 from mholt/clientauth

tls: Client authentication
This commit is contained in:
Matt Holt 2015-06-02 12:05:05 -06:00
commit 9d7639a9d3
4 changed files with 79 additions and 2 deletions

View file

@ -53,8 +53,13 @@ func TLS(c *Controller) (middleware.Middleware, error) {
} }
c.TLS.Ciphers = append(c.TLS.Ciphers, value) 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: default:
return nil, c.Errf("Unknown keyword '%s'") return nil, c.Errf("Unknown keyword '%s'", c.Val())
} }
} }
} }

View file

@ -127,3 +127,34 @@ func TestTLSParseWithWrongOptionalParams(t *testing.T) {
t.Errorf("Expected errors, but no error returned") 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")
}
}

View file

@ -64,4 +64,5 @@ type TLSConfig struct {
ProtocolMinVersion uint16 ProtocolMinVersion uint16
ProtocolMaxVersion uint16 ProtocolMaxVersion uint16
PreferServerCipherSuites bool PreferServerCipherSuites bool
ClientCerts []string
} }

View file

@ -5,7 +5,9 @@ package server
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -137,15 +139,53 @@ func ListenAndServeTLSWithSNI(srv *http.Server, tlsConfigs []TLSConfig) error {
config.CipherSuites = tlsConfigs[0].Ciphers config.CipherSuites = tlsConfigs[0].Ciphers
config.PreferServerCipherSuites = tlsConfigs[0].PreferServerCipherSuites 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 { if err != nil {
return err 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) tlsListener := tls.NewListener(conn, config)
return srv.Serve(tlsListener) 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 // 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 // is bound to. It acts as a multiplexer for the requests hostname as
// defined in the Host header so that the correct virtualhost // defined in the Host header so that the correct virtualhost