package connect import ( "crypto/tls" "crypto/x509" "errors" "io/ioutil" "sync" "github.com/hashicorp/consul/agent/connect" ) // verifyFunc is the type of tls.Config.VerifyPeerCertificate for convenience. type verifyFunc func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error // defaultTLSConfig returns the standard config. func defaultTLSConfig(verify verifyFunc) *tls.Config { return &tls.Config{ MinVersion: tls.VersionTLS12, ClientAuth: tls.RequireAndVerifyClientCert, // We don't have access to go internals that decide if AES hardware // acceleration is available in order to prefer CHA CHA if not. So let's // just always prefer AES for now. We can look into doing something uglier // later like using an external lib for AES checking if it seems important. // https://github.com/golang/go/blob/df91b8044dbe790c69c16058330f545be069cc1f/src/crypto/tls/common.go#L919:14 CipherSuites: []uint16{ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, }, // We have to set this since otherwise Go will attempt to verify DNS names // match DNS SAN/CN which we don't want. We hook up VerifyPeerCertificate to // do our own path validation as well as Connect AuthZ. InsecureSkipVerify: true, VerifyPeerCertificate: verify, // Include h2 to allow connect http servers to automatically support http2. // See: https://github.com/golang/go/blob/917c33fe8672116b04848cf11545296789cafd3b/src/net/http/server.go#L2724-L2731 NextProtos: []string{"h2"}, } } // reloadableTLSConfig exposes a tls.Config that can have it's certificates // reloaded. On a server, this uses GetConfigForClient to pass the current // tls.Config or client certificate for each acceptted connection. On a client, // this uses GetClientCertificate to provide the current client certificate. type reloadableTLSConfig struct { mu sync.Mutex // cfg is the current config to use for new connections cfg *tls.Config } // newReloadableTLSConfig returns a reloadable config currently set to base. func newReloadableTLSConfig(base *tls.Config) *reloadableTLSConfig { c := &reloadableTLSConfig{} c.SetTLSConfig(base) return c } // TLSConfig returns a *tls.Config that will dynamically load certs. It's // suitable for use in either a client or server. func (c *reloadableTLSConfig) TLSConfig() *tls.Config { c.mu.Lock() cfgCopy := c.cfg c.mu.Unlock() return cfgCopy } // SetTLSConfig sets the config used for future connections. It is safe to call // from any goroutine. func (c *reloadableTLSConfig) SetTLSConfig(cfg *tls.Config) error { copy := cfg.Clone() copy.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { current := c.TLSConfig() if len(current.Certificates) < 1 { return nil, errors.New("tls: no certificates configured") } return ¤t.Certificates[0], nil } copy.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { return c.TLSConfig(), nil } c.mu.Lock() defer c.mu.Unlock() c.cfg = copy return nil } // devTLSConfigFromFiles returns a default TLS Config but with certs and CAs // based on local files for dev. func devTLSConfigFromFiles(caFile, certFile, keyFile string) (*tls.Config, error) { roots := x509.NewCertPool() bs, err := ioutil.ReadFile(caFile) if err != nil { return nil, err } roots.AppendCertsFromPEM(bs) cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return nil, err } // Insecure no verification cfg := defaultTLSConfig(nil) cfg.Certificates = []tls.Certificate{cert} cfg.RootCAs = roots cfg.ClientCAs = roots return cfg, nil } // verifyServerCertMatchesURI is used on tls connections dialled to a connect // server to ensure that the certificate it presented has the correct identity. func verifyServerCertMatchesURI(certs []*x509.Certificate, expected connect.CertURI) error { expectedStr := expected.URI().String() if len(certs) < 1 { return errors.New("peer certificate mismatch") } // Only check the first cert assuming this is the only leaf. It's not clear if // services might ever legitimately present multiple leaf certificates or if // the slice is just to allow presenting the whole chain of intermediates. cert := certs[0] // Our certs will only ever have a single URI for now so only check that if len(cert.URIs) < 1 { return errors.New("peer certificate mismatch") } // We may want to do better than string matching later in some special // cases and/or encapsulate the "match" logic inside the CertURI // implementation but for now this is all we need. if cert.URIs[0].String() == expectedStr { return nil } return errors.New("peer certificate mismatch") } // serverVerifyCerts is the verifyFunc for use on Connect servers. func serverVerifyCerts(rawCerts [][]byte, chains [][]*x509.Certificate) error { // TODO(banks): implement me return nil } // clientVerifyCerts is the verifyFunc for use on Connect clients. func clientVerifyCerts(rawCerts [][]byte, chains [][]*x509.Certificate) error { // TODO(banks): implement me return nil }