1 | package pq
|
---|
2 |
|
---|
3 | import (
|
---|
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.
|
---|
16 | func 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.
|
---|
96 | func 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.
|
---|
154 | func 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.
|
---|
185 | func 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 | }
|
---|