Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ unreleased

- Support `ssl_min_protocol_version` and `ssl_max_protocol_version` ([#1277]).

- Support `sslrootcert=system` and use `~/.postgresql/root.crt` as the default
value of sslrootcert ([#1280], [#1281]).

### Fixes

- Fix SSL key permission check to allow modes stricter than 0600/0640#1265 ([#1265]).
Expand All @@ -41,6 +44,8 @@ unreleased
[#1272]: https://github.com/lib/pq/pull/1272
[#1277]: https://github.com/lib/pq/pull/1277
[#1278]: https://github.com/lib/pq/pull/1278
[#1280]: https://github.com/lib/pq/pull/1280
[#1281]: https://github.com/lib/pq/pull/1281

v1.11.2 (2026-02-10)
--------------------
Expand Down
18 changes: 17 additions & 1 deletion connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const (
var sslModes = []SSLMode{SSLModeDisable, SSLModeAllow, SSLModePrefer, SSLModeRequire,
SSLModeVerifyFull, SSLModeVerifyCA}

func (s SSLMode) ssl() bool {
func (s SSLMode) useSSL() bool {
switch s {
case SSLModePrefer, SSLModeRequire, SSLModeVerifyCA, SSLModeVerifyFull:
return true
Expand Down Expand Up @@ -322,6 +322,12 @@ type Config struct {

// Path to root certificate. The file must contain PEM encoded data.
//
// The special value "system" can be used to load the system's root
// certificates ([x509.SystemCertPool]). This will change the default
// sslmode to verify-full and issue an error if a lower setting is used – as
// anyone can register a valid certificate hostname verification becomes
// essential.
//
// Defaults to ~/.postgresql/root.crt.
SSLRootCert string `postgres:"sslrootcert" env:"PGSSLROOTCERT"`

Expand Down Expand Up @@ -608,6 +614,16 @@ func newConfig(dsn string, env []string) (Config, error) {
cfg.SSLMode)
}
}
if cfg.SSLRootCert == "system" {
if !cfg.isset("sslmode") {
cfg.SSLMode = SSLModeVerifyFull
}
if cfg.SSLMode != SSLModeVerifyFull {
return Config{}, fmt.Errorf(
`pq: weak sslmode %q may not be used with sslrootcert=system (use "verify-full")`,
cfg.SSLMode)
}
}

return cfg, nil
}
Expand Down
17 changes: 13 additions & 4 deletions ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,14 @@ func getTLSConfigClone(key string) *tls.Config {
// in case of sslmode=allow or prefer.
func ssl(cfg Config, mode SSLMode) (func(net.Conn) (net.Conn, error), error) {
var (
home = pqutil.Home()
// Don't set defaults here, because tlsConf may be overwritten if a
// custom one was registered. Set it after the sslmode switch.
tlsConf = &tls.Config{}
tlsConf = &tls.Config{}
// Only verify the CA signing but not the hostname.
verifyCaOnly = false
home = pqutil.Home()
)
if mode.ssl() && !cfg.SSLInline && cfg.SSLRootCert == "" && home != "" {
if mode.useSSL() && !cfg.SSLInline && cfg.SSLRootCert == "" && home != "" {
f := filepath.Join(home, "root.crt")
if _, err := os.Stat(f); err == nil {
cfg.SSLRootCert = f
Expand Down Expand Up @@ -99,7 +100,7 @@ func ssl(cfg Config, mode SSLMode) (func(net.Conn) (net.Conn, error), error) {
verifyCaOnly = true
} else if _, err := os.Stat(cfg.SSLRootCert); err == nil {
verifyCaOnly = true
} else {
} else if cfg.SSLRootCert != "system" {
cfg.SSLRootCert = ""
}
}
Expand Down Expand Up @@ -230,13 +231,21 @@ func sslClientCertificates(tlsConf *tls.Config, cfg Config, home string) error {
return nil
}

var testSystemRoots *x509.CertPool

// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
func sslCertificateAuthority(tlsConf *tls.Config, cfg Config) ([]byte, error) {
// Only load root certificate if not blank, like libpq.
if cfg.SSLRootCert == "" {
return nil, nil
}

if cfg.SSLRootCert == "system" {
// No work to do as system CAs are used by default if RootCAs is nil.
tlsConf.RootCAs = testSystemRoots
return nil, nil
}

tlsConf.RootCAs = x509.NewCertPool()

var cert []byte
Expand Down
40 changes: 40 additions & 0 deletions ssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
_ "crypto/sha256"
"crypto/tls"
"crypto/x509"
"database/sql"
"fmt"
"io"
Expand Down Expand Up @@ -421,6 +422,45 @@ func TestSSLDefaults(t *testing.T) {
})
}

func TestRootCA(t *testing.T) {
startSSLTest(t, "pqgossl")

// TODO: can remove this once https://github.com/lib/pq/pull/1271 is merged.
os.Unsetenv("PGSSLMODE")
t.Cleanup(func() {
os.Setenv("PGSSLMODE", "disable")
testSystemRoots = nil
})
testSystemRoots = x509.NewCertPool()
if !testSystemRoots.AppendCertsFromPEM(pqtest.Read(t, "testdata/init/root.crt")) {
t.Fatal()
}

tests := []struct {
connect string
wantErr string
}{
{"", ``},
{"sslmode=verify-full", ``},

{"sslmode=verify-ca", `weak sslmode`},
{"sslmode=disable", `weak sslmode`},
{"sslmode=allow", `weak sslmode`},
}

for _, tt := range tests {
t.Run("", func(t *testing.T) {
db := pqtest.MustDB(t, "host=postgres user=pqgossl sslrootcert=system "+tt.connect)
defer db.Close()

err := db.Ping()
if !pqtest.ErrorContains(err, tt.wantErr) {
t.Fatalf("wrong error:\nhave: %v\nwant: %s", err, tt.wantErr)
}
})
}
}

func TestUnreadableHome(t *testing.T) {
// Ignore HOME being unset or not a directory
for _, h := range []string{"", "/dev/null"} {
Expand Down