176 lines
4.8 KiB
Go
176 lines
4.8 KiB
Go
|
package pq
|
||
|
|
||
|
import (
|
||
|
"crypto/tls"
|
||
|
"crypto/x509"
|
||
|
"io/ioutil"
|
||
|
"net"
|
||
|
"os"
|
||
|
"os/user"
|
||
|
"path/filepath"
|
||
|
)
|
||
|
|
||
|
// 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 {
|
||
|
verifyCaOnly := false
|
||
|
tlsConf := tls.Config{}
|
||
|
switch mode := o.Get("sslmode"); mode {
|
||
|
// "require" is the default.
|
||
|
case "", "require":
|
||
|
// 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 _, err := os.Stat(o.Get("sslrootcert")); err == nil {
|
||
|
verifyCaOnly = true
|
||
|
} else {
|
||
|
o.Set("sslrootcert", "")
|
||
|
}
|
||
|
case "verify-ca":
|
||
|
// We must skip TLS's own verification since it requires full
|
||
|
// verification since Go 1.3.
|
||
|
tlsConf.InsecureSkipVerify = true
|
||
|
verifyCaOnly = true
|
||
|
case "verify-full":
|
||
|
tlsConf.ServerName = o.Get("host")
|
||
|
case "disable":
|
||
|
return nil
|
||
|
default:
|
||
|
errorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
|
||
|
}
|
||
|
|
||
|
sslClientCertificates(&tlsConf, o)
|
||
|
sslCertificateAuthority(&tlsConf, o)
|
||
|
sslRenegotiation(&tlsConf)
|
||
|
|
||
|
return func(conn net.Conn) net.Conn {
|
||
|
client := tls.Client(conn, &tlsConf)
|
||
|
if verifyCaOnly {
|
||
|
sslVerifyCertificateAuthority(client, &tlsConf)
|
||
|
}
|
||
|
return client
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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, o values) {
|
||
|
sslkey := o.Get("sslkey")
|
||
|
sslcert := o.Get("sslcert")
|
||
|
|
||
|
var cinfo, kinfo os.FileInfo
|
||
|
var err error
|
||
|
|
||
|
if sslcert != "" && sslkey != "" {
|
||
|
// Check that both files exist. Note that we don't do any more extensive
|
||
|
// checks than this (such as checking that the paths aren't directories);
|
||
|
// LoadX509KeyPair() will take care of the rest.
|
||
|
cinfo, err = os.Stat(sslcert)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
kinfo, err = os.Stat(sslkey)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
} else {
|
||
|
// Automatically find certificates from ~/.postgresql
|
||
|
sslcert, sslkey, cinfo, kinfo = sslHomeCertificates()
|
||
|
|
||
|
if cinfo == nil || kinfo == nil {
|
||
|
// No certificates to load
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The files must also have the correct permissions
|
||
|
sslCertificatePermissions(cinfo, kinfo)
|
||
|
|
||
|
cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
tlsConf.Certificates = []tls.Certificate{cert}
|
||
|
}
|
||
|
|
||
|
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
|
||
|
func sslCertificateAuthority(tlsConf *tls.Config, o values) {
|
||
|
if sslrootcert := o.Get("sslrootcert"); sslrootcert != "" {
|
||
|
tlsConf.RootCAs = x509.NewCertPool()
|
||
|
|
||
|
cert, err := ioutil.ReadFile(sslrootcert)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
ok := tlsConf.RootCAs.AppendCertsFromPEM(cert)
|
||
|
if !ok {
|
||
|
errorf("couldn't parse pem in sslrootcert")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// sslHomeCertificates returns the path and stats of certificates in the current
|
||
|
// user's home directory.
|
||
|
func sslHomeCertificates() (cert, key string, cinfo, kinfo os.FileInfo) {
|
||
|
user, err := user.Current()
|
||
|
|
||
|
if err != nil {
|
||
|
// user.Current() might fail when cross-compiling. We have to ignore the
|
||
|
// error and continue without client certificates, since we wouldn't know
|
||
|
// from where to load them.
|
||
|
return
|
||
|
}
|
||
|
|
||
|
cert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
|
||
|
key = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
|
||
|
|
||
|
cinfo, err = os.Stat(cert)
|
||
|
if err != nil {
|
||
|
cinfo = nil
|
||
|
}
|
||
|
|
||
|
kinfo, err = os.Stat(key)
|
||
|
if err != nil {
|
||
|
kinfo = nil
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
|
||
|
// verifies the presented certificate against the CA, i.e. the one specified in
|
||
|
// sslrootcert or the system CA if sslrootcert was not specified.
|
||
|
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) {
|
||
|
err := client.Handshake()
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
certs := client.ConnectionState().PeerCertificates
|
||
|
opts := x509.VerifyOptions{
|
||
|
DNSName: client.ConnectionState().ServerName,
|
||
|
Intermediates: x509.NewCertPool(),
|
||
|
Roots: tlsConf.RootCAs,
|
||
|
}
|
||
|
for i, cert := range certs {
|
||
|
if i == 0 {
|
||
|
continue
|
||
|
}
|
||
|
opts.Intermediates.AddCert(cert)
|
||
|
}
|
||
|
_, err = certs[0].Verify(opts)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|