diff --git a/conn.go b/conn.go index db0b6cef..38c6204d 100644 --- a/conn.go +++ b/conn.go @@ -16,6 +16,7 @@ import ( "os/user" "path" "path/filepath" + "sort" "strconv" "strings" "sync/atomic" @@ -394,6 +395,23 @@ func network(o values) (string, string) { type values map[string]string +// Hash returns a deterministic hash of values. +func (v values) Hash() []byte { + keys := make([]string, len(v)) + i := 0 + for key := range v { + keys[i] = key + i++ + } + sort.Strings(keys) + h := sha256.New() + for _, key := range keys { + h.Write([]byte(key)) + h.Write([]byte(v[key])) + } + return h.Sum(nil) +} + // scanner implements a tokenizer for libpq-style option strings. type scanner struct { s []rune diff --git a/ssl.go b/ssl.go index 881c2219..cb1998ea 100644 --- a/ssl.go +++ b/ssl.go @@ -3,17 +3,36 @@ package pq import ( "crypto/tls" "crypto/x509" + "fmt" "io/ioutil" "net" "os" "os/user" "path/filepath" + "sync" ) -// ssl generates a function to upgrade a net.Conn based on the "sslmode" and -// related settings. The function is nil when no upgrade should take place. -func ssl(o values) (func(net.Conn) (net.Conn, error), error) { +// To avoid allocating the map if we never use ssl +var configMapOnce sync.Once +var configMapMu sync.Mutex +var configMap map[string]*ssldata + +type ssldata struct { + Conf *tls.Config + VerifyCAOnly bool +} + +func getTLSConf(o values) (*ssldata, error) { verifyCaOnly := false + configMapOnce.Do(func() { + configMap = make(map[string]*ssldata) + }) + configMapMu.Lock() + conf, ok := configMap[string(o.Hash())] + configMapMu.Unlock() + if ok { + return conf, nil + } tlsConf := tls.Config{} switch mode := o["sslmode"]; mode { // "require" is the default. @@ -59,9 +78,6 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { return nil, err } - // This pseudo-parameter is not recognized by the PostgreSQL server, so let's delete it after use. - delete(o, "sslinline") - // Accept renegotiation requests initiated by the backend. // // Renegotiation was deprecated then removed from PostgreSQL 9.5, but @@ -69,10 +85,28 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { // also initiates renegotiations and cannot be reconfigured. tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient + data := &ssldata{&tlsConf, verifyCaOnly} + configMapMu.Lock() + configMap[string(o.Hash())] = data + fmt.Printf("o: %#v\n", string(o.Hash())) + configMapMu.Unlock() + return data, nil +} + +// ssl generates a function to upgrade a net.Conn based on the "sslmode" and +// related settings. The function is nil when no upgrade should take place. +func ssl(o values) (func(net.Conn) (net.Conn, error), error) { + data, err := getTLSConf(o) + if data == nil && err == nil { + return nil, nil + } + if err != nil { + return nil, err + } return func(conn net.Conn) (net.Conn, error) { - client := tls.Client(conn, &tlsConf) - if verifyCaOnly { - err := sslVerifyCertificateAuthority(client, &tlsConf) + client := tls.Client(conn, data.Conf) + if data.VerifyCAOnly { + err := sslVerifyCertificateAuthority(client, data.Conf) if err != nil { return nil, err } @@ -86,19 +120,6 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) { // in the user's home directory. The configured files must exist and have // the correct permissions. func sslClientCertificates(tlsConf *tls.Config, o values) error { - sslinline := o["sslinline"] - if sslinline == "true" { - cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"])) - // Clear out these params, in case they were to be sent to the PostgreSQL server by mistake - o["sslcert"] = "" - o["sslkey"] = "" - if err != nil { - return err - } - tlsConf.Certificates = []tls.Certificate{cert} - return nil - } - // user.Current() might fail when cross-compiling. We have to ignore the // error and continue without home directory defaults, since we wouldn't // know from where to load them. @@ -153,19 +174,9 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error { if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 { tlsConf.RootCAs = x509.NewCertPool() - sslinline := o["sslinline"] - - var cert []byte - if sslinline == "true" { - // // Clear out this param, in case it were to be sent to the PostgreSQL server by mistake - o["sslrootcert"] = "" - cert = []byte(sslrootcert) - } else { - var err error - cert, err = ioutil.ReadFile(sslrootcert) - if err != nil { - return err - } + cert, err := ioutil.ReadFile(sslrootcert) + if err != nil { + return err } if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {