Skip to content

Commit 37475fc

Browse files
committed
Add support for sslcrl option
1 parent 9e747ca commit 37475fc

File tree

2 files changed

+97
-26
lines changed

2 files changed

+97
-26
lines changed

conn.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ func isDriverSetting(key string) bool {
11061106
return true
11071107
case "password":
11081108
return true
1109-
case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline":
1109+
case "sslmode", "sslcert", "sslkey", "sslrootcert", "sslinline", "sslcrl":
11101110
return true
11111111
case "fallback_application_name":
11121112
return true
@@ -1995,7 +1995,9 @@ func parseEnviron(env []string) (out map[string]string) {
19951995
accrue("sslkey")
19961996
case "PGSSLROOTCERT":
19971997
accrue("sslrootcert")
1998-
case "PGREQUIRESSL", "PGSSLCRL":
1998+
case "PGSSLCRL":
1999+
accrue("sslcrl")
2000+
case "PGREQUIRESSL":
19992001
unsupported()
20002002
case "PGREQUIREPEER":
20012003
unsupported()

ssl.go

Lines changed: 93 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ package pq
33
import (
44
"crypto/tls"
55
"crypto/x509"
6+
"crypto/x509/pkix"
67
"io/ioutil"
78
"net"
89
"os"
910
"os/user"
1011
"path/filepath"
12+
"time"
1113
)
1214

15+
type tlsConfWithCrl struct {
16+
tls.Config
17+
18+
crl *pkix.CertificateList
19+
}
20+
1321
// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
1422
// related settings. The function is nil when no upgrade should take place.
1523
func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
1624
verifyCaOnly := false
17-
tlsConf := tls.Config{}
25+
tlsConf := tlsConfWithCrl{}
1826
switch mode := o["sslmode"]; mode {
1927
// "require" is the default.
2028
case "", "require":
@@ -67,12 +75,9 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
6775
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
6876

6977
return func(conn net.Conn) (net.Conn, error) {
70-
client := tls.Client(conn, &tlsConf)
71-
if verifyCaOnly {
72-
err := sslVerifyCertificateAuthority(client, &tlsConf)
73-
if err != nil {
74-
return nil, err
75-
}
78+
client := tls.Client(conn, &tlsConf.Config)
79+
if err := sslVerifyExtra(client, &tlsConf, verifyCaOnly); err != nil {
80+
return nil, err
7681
}
7782
return client, nil
7883
}, nil
@@ -82,7 +87,7 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
8287
// "sslkey" settings, or if they aren't set, from the .postgresql directory
8388
// in the user's home directory. The configured files must exist and have
8489
// the correct permissions.
85-
func sslClientCertificates(tlsConf *tls.Config, o values) error {
90+
func sslClientCertificates(tlsConf *tlsConfWithCrl, o values) error {
8691
sslinline := o["sslinline"]
8792
if sslinline == "true" {
8893
cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
@@ -98,6 +103,23 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error {
98103
// know from where to load them.
99104
user, _ := user.Current()
100105

106+
// Load CRL, https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L974
107+
sslcrl := o["sslcrl"]
108+
if len(sslcrl) == 0 && user != nil {
109+
sslcrl = filepath.Join(user.HomeDir, ".postgresql", "root.crl")
110+
}
111+
if len(sslcrl) > 0 {
112+
crlcontent, err := ioutil.ReadFile(sslcrl)
113+
if err != nil && !os.IsNotExist(err) { // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1002
114+
return err
115+
} else if err == nil {
116+
tlsConf.crl, err = x509.ParseCRL(crlcontent)
117+
if err != nil {
118+
return err
119+
}
120+
}
121+
}
122+
101123
// In libpq, the client certificate is only loaded if the setting is not blank.
102124
//
103125
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
@@ -140,7 +162,7 @@ func sslClientCertificates(tlsConf *tls.Config, o values) error {
140162
}
141163

142164
// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
143-
func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
165+
func sslCertificateAuthority(tlsConf *tlsConfWithCrl, o values) error {
144166
// In libpq, the root certificate is only loaded if the setting is not blank.
145167
//
146168
// https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
@@ -168,26 +190,73 @@ func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
168190
return nil
169191
}
170192

171-
// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
172-
// verifies the presented certificate against the CA, i.e. the one specified in
173-
// sslrootcert or the system CA if sslrootcert was not specified.
174-
func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
193+
// sslVerifyExtra carries out a TLS handshake to the server and
194+
// carries out extra verification that Go's TLS package doesn't do:
195+
// * if verifyCaOnly is true, verifies the presented certificate against the CA,
196+
// i.e. the one specified in sslrootcert or the system CA if sslrootcert was not specified.
197+
// * verifies the PeerCertificates against CRL if sslcrl was specified
198+
func sslVerifyExtra(client *tls.Conn, tlsConf *tlsConfWithCrl, verifyCaOnly bool) error {
175199
err := client.Handshake()
176200
if err != nil {
177201
return err
178202
}
179-
certs := client.ConnectionState().PeerCertificates
180-
opts := x509.VerifyOptions{
181-
DNSName: client.ConnectionState().ServerName,
182-
Intermediates: x509.NewCertPool(),
183-
Roots: tlsConf.RootCAs,
203+
204+
state := client.ConnectionState()
205+
if verifyCaOnly {
206+
opts := x509.VerifyOptions{
207+
DNSName: client.ConnectionState().ServerName,
208+
Intermediates: x509.NewCertPool(),
209+
Roots: tlsConf.RootCAs,
210+
}
211+
for i, cert := range state.PeerCertificates {
212+
if i == 0 {
213+
continue
214+
}
215+
opts.Intermediates.AddCert(cert)
216+
}
217+
218+
state.VerifiedChains, err = state.PeerCertificates[0].Verify(opts)
219+
if err != nil {
220+
return err
221+
}
184222
}
185-
for i, cert := range certs {
186-
if i == 0 {
187-
continue
223+
224+
if crl := tlsConf.crl; crl != nil {
225+
if crl.HasExpired(time.Now()) {
226+
return fmterrorf("sslcrl has expired on %v", crl.TBSCertList.NextUpdate)
227+
}
228+
229+
crlVerified := false
230+
crlIssuer := crl.TBSCertList.Issuer.String()
231+
232+
VerifiedChainLoop:
233+
for _, chain := range state.VerifiedChains {
234+
for i := len(chain) - 1; i >= 0; i-- {
235+
cert := chain[i]
236+
if cert.Subject.ToRDNSequence().String() != crlIssuer {
237+
continue
238+
}
239+
240+
if err := cert.CheckCRLSignature(crl); err != nil {
241+
return fmterrorf("sslcrl failed to verify with cert subject %s: %w", cert.Subject.String(), err)
242+
}
243+
crlVerified = true
244+
break VerifiedChainLoop
245+
}
246+
}
247+
248+
if !crlVerified {
249+
return fmterrorf("sslcrl failed to verify with all root certificates.")
250+
}
251+
252+
for _, cert := range state.PeerCertificates {
253+
for _, revoked := range crl.TBSCertList.RevokedCertificates {
254+
if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 {
255+
return fmterrorf("certificate %s was revoked at %v", cert.SerialNumber.String(), revoked.RevocationTime)
256+
}
257+
}
188258
}
189-
opts.Intermediates.AddCert(cert)
190259
}
191-
_, err = certs[0].Verify(opts)
192-
return err
260+
261+
return nil
193262
}

0 commit comments

Comments
 (0)