source: code/trunk/vendor/github.com/lib/pq/ssl.go@ 822

Last change on this file since 822 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 6.3 KB
Line 
1package pq
2
3import (
4 "crypto/tls"
5 "crypto/x509"
6 "io/ioutil"
7 "net"
8 "os"
9 "os/user"
10 "path/filepath"
11 "strings"
12)
13
14// ssl generates a function to upgrade a net.Conn based on the "sslmode" and
15// related settings. The function is nil when no upgrade should take place.
16func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
17 verifyCaOnly := false
18 tlsConf := tls.Config{}
19 switch mode := o["sslmode"]; mode {
20 // "require" is the default.
21 case "", "require":
22 // We must skip TLS's own verification since it requires full
23 // verification since Go 1.3.
24 tlsConf.InsecureSkipVerify = true
25
26 // From http://www.postgresql.org/docs/current/static/libpq-ssl.html:
27 //
28 // Note: For backwards compatibility with earlier versions of
29 // PostgreSQL, if a root CA file exists, the behavior of
30 // sslmode=require will be the same as that of verify-ca, meaning the
31 // server certificate is validated against the CA. Relying on this
32 // behavior is discouraged, and applications that need certificate
33 // validation should always use verify-ca or verify-full.
34 if sslrootcert, ok := o["sslrootcert"]; ok {
35 if _, err := os.Stat(sslrootcert); err == nil {
36 verifyCaOnly = true
37 } else {
38 delete(o, "sslrootcert")
39 }
40 }
41 case "verify-ca":
42 // We must skip TLS's own verification since it requires full
43 // verification since Go 1.3.
44 tlsConf.InsecureSkipVerify = true
45 verifyCaOnly = true
46 case "verify-full":
47 tlsConf.ServerName = o["host"]
48 case "disable":
49 return nil, nil
50 default:
51 return nil, fmterrorf(`unsupported sslmode %q; only "require" (default), "verify-full", "verify-ca", and "disable" supported`, mode)
52 }
53
54 // Set Server Name Indication (SNI), if enabled by connection parameters.
55 // By default SNI is on, any value which is not starting with "1" disables
56 // SNI -- that is the same check vanilla libpq uses.
57 if sslsni := o["sslsni"]; sslsni == "" || strings.HasPrefix(sslsni, "1") {
58 // RFC 6066 asks to not set SNI if the host is a literal IP address (IPv4
59 // or IPv6). This check is coded already crypto.tls.hostnameInSNI, so
60 // just always set ServerName here and let crypto/tls do the filtering.
61 tlsConf.ServerName = o["host"]
62 }
63
64 err := sslClientCertificates(&tlsConf, o)
65 if err != nil {
66 return nil, err
67 }
68 err = sslCertificateAuthority(&tlsConf, o)
69 if err != nil {
70 return nil, err
71 }
72
73 // Accept renegotiation requests initiated by the backend.
74 //
75 // Renegotiation was deprecated then removed from PostgreSQL 9.5, but
76 // the default configuration of older versions has it enabled. Redshift
77 // also initiates renegotiations and cannot be reconfigured.
78 tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
79
80 return func(conn net.Conn) (net.Conn, error) {
81 client := tls.Client(conn, &tlsConf)
82 if verifyCaOnly {
83 err := sslVerifyCertificateAuthority(client, &tlsConf)
84 if err != nil {
85 return nil, err
86 }
87 }
88 return client, nil
89 }, nil
90}
91
92// sslClientCertificates adds the certificate specified in the "sslcert" and
93// "sslkey" settings, or if they aren't set, from the .postgresql directory
94// in the user's home directory. The configured files must exist and have
95// the correct permissions.
96func sslClientCertificates(tlsConf *tls.Config, o values) error {
97 sslinline := o["sslinline"]
98 if sslinline == "true" {
99 cert, err := tls.X509KeyPair([]byte(o["sslcert"]), []byte(o["sslkey"]))
100 if err != nil {
101 return err
102 }
103 tlsConf.Certificates = []tls.Certificate{cert}
104 return nil
105 }
106
107 // user.Current() might fail when cross-compiling. We have to ignore the
108 // error and continue without home directory defaults, since we wouldn't
109 // know from where to load them.
110 user, _ := user.Current()
111
112 // In libpq, the client certificate is only loaded if the setting is not blank.
113 //
114 // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1036-L1037
115 sslcert := o["sslcert"]
116 if len(sslcert) == 0 && user != nil {
117 sslcert = filepath.Join(user.HomeDir, ".postgresql", "postgresql.crt")
118 }
119 // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1045
120 if len(sslcert) == 0 {
121 return nil
122 }
123 // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1050:L1054
124 if _, err := os.Stat(sslcert); os.IsNotExist(err) {
125 return nil
126 } else if err != nil {
127 return err
128 }
129
130 // In libpq, the ssl key is only loaded if the setting is not blank.
131 //
132 // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L1123-L1222
133 sslkey := o["sslkey"]
134 if len(sslkey) == 0 && user != nil {
135 sslkey = filepath.Join(user.HomeDir, ".postgresql", "postgresql.key")
136 }
137
138 if len(sslkey) > 0 {
139 if err := sslKeyPermissions(sslkey); err != nil {
140 return err
141 }
142 }
143
144 cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
145 if err != nil {
146 return err
147 }
148
149 tlsConf.Certificates = []tls.Certificate{cert}
150 return nil
151}
152
153// sslCertificateAuthority adds the RootCA specified in the "sslrootcert" setting.
154func sslCertificateAuthority(tlsConf *tls.Config, o values) error {
155 // In libpq, the root certificate is only loaded if the setting is not blank.
156 //
157 // https://github.com/postgres/postgres/blob/REL9_6_2/src/interfaces/libpq/fe-secure-openssl.c#L950-L951
158 if sslrootcert := o["sslrootcert"]; len(sslrootcert) > 0 {
159 tlsConf.RootCAs = x509.NewCertPool()
160
161 sslinline := o["sslinline"]
162
163 var cert []byte
164 if sslinline == "true" {
165 cert = []byte(sslrootcert)
166 } else {
167 var err error
168 cert, err = ioutil.ReadFile(sslrootcert)
169 if err != nil {
170 return err
171 }
172 }
173
174 if !tlsConf.RootCAs.AppendCertsFromPEM(cert) {
175 return fmterrorf("couldn't parse pem in sslrootcert")
176 }
177 }
178
179 return nil
180}
181
182// sslVerifyCertificateAuthority carries out a TLS handshake to the server and
183// verifies the presented certificate against the CA, i.e. the one specified in
184// sslrootcert or the system CA if sslrootcert was not specified.
185func sslVerifyCertificateAuthority(client *tls.Conn, tlsConf *tls.Config) error {
186 err := client.Handshake()
187 if err != nil {
188 return err
189 }
190 certs := client.ConnectionState().PeerCertificates
191 opts := x509.VerifyOptions{
192 DNSName: client.ConnectionState().ServerName,
193 Intermediates: x509.NewCertPool(),
194 Roots: tlsConf.RootCAs,
195 }
196 for i, cert := range certs {
197 if i == 0 {
198 continue
199 }
200 opts.Intermediates.AddCert(cert)
201 }
202 _, err = certs[0].Verify(opts)
203 return err
204}
Note: See TracBrowser for help on using the repository browser.