package serverutil import ( "crypto/tls" "net/http" "regexp" common "git.autistici.org/ai3/go-common" ) // TLSAuthACL describes a single access control entry. Path and // CommonName are anchored regular expressions (they must match the // entire string). type TLSAuthACL struct { Path string `yaml:"path"` CommonName string `yaml:"cn"` pathRx, cnRx *regexp.Regexp } func (p *TLSAuthACL) compile() error { var err error p.pathRx, err = regexp.Compile("^" + p.Path + "$") if err != nil { return err } p.cnRx, err = regexp.Compile("^" + p.CommonName + "$") return err } func (p *TLSAuthACL) match(req *http.Request) bool { if !p.pathRx.MatchString(req.URL.Path) { return false } for _, cert := range req.TLS.PeerCertificates { if p.cnRx.MatchString(cert.Subject.CommonName) { return true } } return false } // TLSAuthConfig stores access control lists for TLS authentication. Access // control lists are matched against the request path and the // CommonName component of the peer certificate subject. type TLSAuthConfig struct { Allow []*TLSAuthACL `yaml:"allow"` } func (c *TLSAuthConfig) match(req *http.Request) bool { // Fail *OPEN* if unconfigured. if c == nil || len(c.Allow) == 0 { return true } for _, acl := range c.Allow { if acl.match(req) { return true } } return false } // TLSServerConfig configures a TLS server with client authentication // and authorization based on the client X509 certificate. type TLSServerConfig struct { Cert string `yaml:"cert"` Key string `yaml:"key"` CA string `yaml:"ca"` Auth *TLSAuthConfig `yaml:"acl"` } // TLSConfig returns a tls.Config created with the current configuration. func (c *TLSServerConfig) TLSConfig() (*tls.Config, error) { cert, err := tls.LoadX509KeyPair(c.Cert, c.Key) if err != nil { return nil, err } cas, err := common.LoadCA(c.CA) if err != nil { return nil, err } // Set some TLS-level parameters (cipher-related), assuming // we're using EC keys. tlsConf := &tls.Config{ Certificates: []tls.Certificate{cert}, ClientAuth: tls.RequireAndVerifyClientCert, ClientCAs: cas, CipherSuites: []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, MinVersion: tls.VersionTLS12, PreferServerCipherSuites: true, } tlsConf.BuildNameToCertificate() return tlsConf, nil } // TLSAuthWrapper protects a root HTTP handler with TLS authentication. func (c *TLSServerConfig) TLSAuthWrapper(h http.Handler) (http.Handler, error) { // Compile regexps. if c.Auth != nil { for _, acl := range c.Auth.Allow { if err := acl.compile(); err != nil { return nil, err } } } // Build the wrapper function to check client certificates // identities (looking at the CN part of the X509 subject). return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if c.Auth.match(r) { h.ServeHTTP(w, r) return } http.Error(w, "Unauthorized", http.StatusUnauthorized) }), nil }