Files
Bel LaPointe 886c4aabff vendor
2026-03-09 09:42:09 -06:00

251 lines
6.9 KiB
Go

package pq
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"github.com/lib/pq/internal/pqutil"
)
// Registry for custom tls.Configs
var (
tlsConfs = make(map[string]*tls.Config)
tlsConfsMu sync.RWMutex
)
// RegisterTLSConfig registers a custom [tls.Config]. They are used by using
// sslmode=pqgo-«key» in the connection string.
//
// Set the config to nil to remove a configuration.
func RegisterTLSConfig(key string, config *tls.Config) error {
key = strings.TrimPrefix(key, "pqgo-")
if config == nil {
tlsConfsMu.Lock()
delete(tlsConfs, key)
tlsConfsMu.Unlock()
return nil
}
tlsConfsMu.Lock()
tlsConfs[key] = config
tlsConfsMu.Unlock()
return nil
}
func hasTLSConfig(key string) bool {
tlsConfsMu.RLock()
defer tlsConfsMu.RUnlock()
_, ok := tlsConfs[key]
return ok
}
func getTLSConfigClone(key string) *tls.Config {
tlsConfsMu.RLock()
defer tlsConfsMu.RUnlock()
if v, ok := tlsConfs[key]; ok {
return v.Clone()
}
return 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(cfg Config) (func(net.Conn) (net.Conn, error), error) {
var (
verifyCaOnly = false
tlsConf = &tls.Config{}
mode = cfg.SSLMode
)
switch {
// "require" is the default.
case mode == "" || mode == SSLModeRequire:
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
// From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
//
// Note: For backwards compatibility with earlier versions of
// PostgreSQL, if a root CA file exists, the behavior of
// sslmode=require will be the same as that of verify-ca, meaning the
// server certificate is validated against the CA. Relying on this
// behavior is discouraged, and applications that need certificate
// validation should always use verify-ca or verify-full.
if cfg.SSLRootCert != "" {
if _, err := os.Stat(cfg.SSLRootCert); err == nil {
verifyCaOnly = true
} else {
cfg.SSLRootCert = ""
}
}
case mode == SSLModeVerifyCA:
// We must skip TLS's own verification since it requires full
// verification since Go 1.3.
tlsConf.InsecureSkipVerify = true
verifyCaOnly = true
case mode == SSLModeVerifyFull:
tlsConf.ServerName = cfg.Host
case mode == SSLModeDisable:
return nil, nil
case strings.HasPrefix(string(mode), "pqgo-"):
tlsConf = getTLSConfigClone(string(mode[5:]))
if tlsConf == nil {
return nil, fmt.Errorf(`pq: unknown custom sslmode %q`, mode)
}
default:
return nil, fmt.Errorf(
`pq: unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`,
mode)
}
// Set Server Name Indication (SNI), if enabled by connection parameters.
if cfg.SSLSNI {
// RFC 6066 asks to not set SNI if the host is a literal IP address (IPv4
// or IPv6). This check is coded already crypto.tls.hostnameInSNI, so
// just always set ServerName here and let crypto/tls do the filtering.
tlsConf.ServerName = cfg.Host
}
err := sslClientCertificates(tlsConf, cfg)
if err != nil {
return nil, err
}
err = sslCertificateAuthority(tlsConf, cfg)
if err != nil {
return nil, err
}
// Accept renegotiation requests initiated by the backend.
//
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
// the default configuration of older versions has it enabled. Redshift
// also initiates renegotiations and cannot be reconfigured.
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
return func(conn net.Conn) (net.Conn, error) {
client := tls.Client(conn, tlsConf)
if verifyCaOnly {
err := client.Handshake()
if err != nil {
return client, err
}
var (
certs = client.ConnectionState().PeerCertificates
opts = x509.VerifyOptions{Intermediates: x509.NewCertPool(), Roots: tlsConf.RootCAs}
)
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
return client, err
}
return client, nil
}, nil
}
// sslClientCertificates adds the certificate specified in the "sslcert" and
//
// "sslkey" settings, or if they aren't set, from the .postgresql directory
// in the user's home directory. The configured files must exist and have
// the correct permissions.
func sslClientCertificates(tlsConf *tls.Config, cfg Config) error {
if cfg.SSLInline {
cert, err := tls.X509KeyPair([]byte(cfg.SSLCert), []byte(cfg.SSLKey))
if err != nil {
return err
}
tlsConf.Certificates = []tls.Certificate{cert}
return nil
}
home := pqutil.Home()
// In libpq, the client certificate is only loaded if the setting is not blank.
//
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
sslcert := cfg.SSLCert
if len(sslcert) == 0 && home != "" {
if runtime.GOOS == "windows" {
sslcert = filepath.Join(sslcert, "postgresql.crt")
} else {
sslcert = filepath.Join(home, ".postgresql/postgresql.crt")
}
}
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045
if len(sslcert) == 0 {
return nil
}
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054
_, err := os.Stat(sslcert)
if err != nil {
perr := new(os.PathError)
if errors.As(err, &perr) && (perr.Err == syscall.ENOENT || perr.Err == syscall.ENOTDIR) {
return nil
}
return err
}
// In libpq, the ssl key is only loaded if the setting is not blank.
//
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222
sslkey := cfg.SSLKey
if len(sslkey) == 0 && home != "" {
if runtime.GOOS == "windows" {
sslkey = filepath.Join(home, "postgresql.key")
} else {
sslkey = filepath.Join(home, ".postgresql/postgresql.key")
}
}
if len(sslkey) > 0 {
err := pqutil.SSLKeyPermissions(sslkey)
if err != nil {
return err
}
}
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
if err != nil {
return err
}
tlsConf.Certificates = []tls.Certificate{cert}
return nil
}
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
func sslCertificateAuthority(tlsConf *tls.Config, cfg Config) error {
// In libpq, the root certificate is only loaded if the setting is not blank.
//
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
if sslrootcert := cfg.SSLRootCert; len(sslrootcert) > 0 {
tlsConf.RootCAs = x509.NewCertPool()
var cert []byte
if cfg.SSLInline {
cert = []byte(sslrootcert)
} else {
var err error
cert, err = os.ReadFile(sslrootcert)
if err != nil {
return err
}
}
if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
return errors.New("pq: couldn't parse pem in sslrootcert")
}
}
return nil
}