blob: fa12911b6956bd9b7697546b540eecb5b86f8662 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package transport
16
17import (
18 "crypto/ecdsa"
19 "crypto/elliptic"
20 "crypto/rand"
21 "crypto/tls"
22 "crypto/x509"
23 "crypto/x509/pkix"
24 "encoding/pem"
25 "errors"
26 "fmt"
27 "math/big"
28 "net"
29 "os"
30 "path/filepath"
31 "strings"
32 "time"
33
34 "go.etcd.io/etcd/pkg/tlsutil"
35
36 "go.uber.org/zap"
37)
38
39// NewListener creates a new listner.
40func NewListener(addr, scheme string, tlsinfo *TLSInfo) (l net.Listener, err error) {
41 if l, err = newListener(addr, scheme); err != nil {
42 return nil, err
43 }
44 return wrapTLS(scheme, tlsinfo, l)
45}
46
47func newListener(addr string, scheme string) (net.Listener, error) {
48 if scheme == "unix" || scheme == "unixs" {
49 // unix sockets via unix://laddr
50 return NewUnixListener(addr)
51 }
52 return net.Listen("tcp", addr)
53}
54
55func wrapTLS(scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) {
56 if scheme != "https" && scheme != "unixs" {
57 return l, nil
58 }
59 if tlsinfo != nil && tlsinfo.SkipClientSANVerify {
60 return NewTLSListener(l, tlsinfo)
61 }
62 return newTLSListener(l, tlsinfo, checkSAN)
63}
64
65type TLSInfo struct {
66 CertFile string
67 KeyFile string
68 TrustedCAFile string
69 ClientCertAuth bool
70 CRLFile string
71 InsecureSkipVerify bool
72 SkipClientSANVerify bool
73
74 // ServerName ensures the cert matches the given host in case of discovery / virtual hosting
75 ServerName string
76
77 // HandshakeFailure is optionally called when a connection fails to handshake. The
78 // connection will be closed immediately afterwards.
79 HandshakeFailure func(*tls.Conn, error)
80
81 // CipherSuites is a list of supported cipher suites.
82 // If empty, Go auto-populates it by default.
83 // Note that cipher suites are prioritized in the given order.
84 CipherSuites []uint16
85
86 selfCert bool
87
88 // parseFunc exists to simplify testing. Typically, parseFunc
89 // should be left nil. In that case, tls.X509KeyPair will be used.
90 parseFunc func([]byte, []byte) (tls.Certificate, error)
91
92 // AllowedCN is a CN which must be provided by a client.
93 AllowedCN string
94
95 // AllowedHostname is an IP address or hostname that must match the TLS
96 // certificate provided by a client.
97 AllowedHostname string
98
99 // Logger logs TLS errors.
100 // If nil, all logs are discarded.
101 Logger *zap.Logger
102
103 // EmptyCN indicates that the cert must have empty CN.
104 // If true, ClientConfig() will return an error for a cert with non empty CN.
105 EmptyCN bool
106}
107
108func (info TLSInfo) String() string {
109 return fmt.Sprintf("cert = %s, key = %s, trusted-ca = %s, client-cert-auth = %v, crl-file = %s", info.CertFile, info.KeyFile, info.TrustedCAFile, info.ClientCertAuth, info.CRLFile)
110}
111
112func (info TLSInfo) Empty() bool {
113 return info.CertFile == "" && info.KeyFile == ""
114}
115
116func SelfCert(lg *zap.Logger, dirpath string, hosts []string, additionalUsages ...x509.ExtKeyUsage) (info TLSInfo, err error) {
117 if err = os.MkdirAll(dirpath, 0700); err != nil {
118 return
119 }
120 info.Logger = lg
121
122 certPath := filepath.Join(dirpath, "cert.pem")
123 keyPath := filepath.Join(dirpath, "key.pem")
124 _, errcert := os.Stat(certPath)
125 _, errkey := os.Stat(keyPath)
126 if errcert == nil && errkey == nil {
127 info.CertFile = certPath
128 info.KeyFile = keyPath
129 info.selfCert = true
130 return
131 }
132
133 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
134 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
135 if err != nil {
136 if info.Logger != nil {
137 info.Logger.Warn(
138 "cannot generate random number",
139 zap.Error(err),
140 )
141 }
142 return
143 }
144
145 tmpl := x509.Certificate{
146 SerialNumber: serialNumber,
147 Subject: pkix.Name{Organization: []string{"etcd"}},
148 NotBefore: time.Now(),
149 NotAfter: time.Now().Add(365 * (24 * time.Hour)),
150
151 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
152 ExtKeyUsage: append([]x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, additionalUsages...),
153 BasicConstraintsValid: true,
154 }
155
156 for _, host := range hosts {
157 h, _, _ := net.SplitHostPort(host)
158 if ip := net.ParseIP(h); ip != nil {
159 tmpl.IPAddresses = append(tmpl.IPAddresses, ip)
160 } else {
161 tmpl.DNSNames = append(tmpl.DNSNames, h)
162 }
163 }
164
165 priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
166 if err != nil {
167 if info.Logger != nil {
168 info.Logger.Warn(
169 "cannot generate ECDSA key",
170 zap.Error(err),
171 )
172 }
173 return
174 }
175
176 derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
177 if err != nil {
178 if info.Logger != nil {
179 info.Logger.Warn(
180 "cannot generate x509 certificate",
181 zap.Error(err),
182 )
183 }
184 return
185 }
186
187 certOut, err := os.Create(certPath)
188 if err != nil {
189 info.Logger.Warn(
190 "cannot cert file",
191 zap.String("path", certPath),
192 zap.Error(err),
193 )
194 return
195 }
196 pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
197 certOut.Close()
198 if info.Logger != nil {
199 info.Logger.Info("created cert file", zap.String("path", certPath))
200 }
201
202 b, err := x509.MarshalECPrivateKey(priv)
203 if err != nil {
204 return
205 }
206 keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
207 if err != nil {
208 if info.Logger != nil {
209 info.Logger.Warn(
210 "cannot key file",
211 zap.String("path", keyPath),
212 zap.Error(err),
213 )
214 }
215 return
216 }
217 pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
218 keyOut.Close()
219 if info.Logger != nil {
220 info.Logger.Info("created key file", zap.String("path", keyPath))
221 }
222 return SelfCert(lg, dirpath, hosts)
223}
224
225// baseConfig is called on initial TLS handshake start.
226//
227// Previously,
228// 1. Server has non-empty (*tls.Config).Certificates on client hello
229// 2. Server calls (*tls.Config).GetCertificate iff:
230// - Server's (*tls.Config).Certificates is not empty, or
231// - Client supplies SNI; non-empty (*tls.ClientHelloInfo).ServerName
232//
233// When (*tls.Config).Certificates is always populated on initial handshake,
234// client is expected to provide a valid matching SNI to pass the TLS
235// verification, thus trigger server (*tls.Config).GetCertificate to reload
236// TLS assets. However, a cert whose SAN field does not include domain names
237// but only IP addresses, has empty (*tls.ClientHelloInfo).ServerName, thus
238// it was never able to trigger TLS reload on initial handshake; first
239// ceritifcate object was being used, never being updated.
240//
241// Now, (*tls.Config).Certificates is created empty on initial TLS client
242// handshake, in order to trigger (*tls.Config).GetCertificate and populate
243// rest of the certificates on every new TLS connection, even when client
244// SNI is empty (e.g. cert only includes IPs).
245func (info TLSInfo) baseConfig() (*tls.Config, error) {
246 if info.KeyFile == "" || info.CertFile == "" {
247 return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile)
248 }
249 if info.Logger == nil {
250 info.Logger = zap.NewNop()
251 }
252
253 _, err := tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
254 if err != nil {
255 return nil, err
256 }
257
258 cfg := &tls.Config{
259 MinVersion: tls.VersionTLS12,
260 ServerName: info.ServerName,
261 }
262
263 if len(info.CipherSuites) > 0 {
264 cfg.CipherSuites = info.CipherSuites
265 }
266
267 // Client certificates may be verified by either an exact match on the CN,
268 // or a more general check of the CN and SANs.
269 var verifyCertificate func(*x509.Certificate) bool
270 if info.AllowedCN != "" {
271 if info.AllowedHostname != "" {
272 return nil, fmt.Errorf("AllowedCN and AllowedHostname are mutually exclusive (cn=%q, hostname=%q)", info.AllowedCN, info.AllowedHostname)
273 }
274 verifyCertificate = func(cert *x509.Certificate) bool {
275 return info.AllowedCN == cert.Subject.CommonName
276 }
277 }
278 if info.AllowedHostname != "" {
279 verifyCertificate = func(cert *x509.Certificate) bool {
280 return cert.VerifyHostname(info.AllowedHostname) == nil
281 }
282 }
283 if verifyCertificate != nil {
284 cfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
285 for _, chains := range verifiedChains {
286 if len(chains) != 0 {
287 if verifyCertificate(chains[0]) {
288 return nil
289 }
290 }
291 }
292 return errors.New("client certificate authentication failed")
293 }
294 }
295
296 // this only reloads certs when there's a client request
297 // TODO: support server-side refresh (e.g. inotify, SIGHUP), caching
298 cfg.GetCertificate = func(clientHello *tls.ClientHelloInfo) (cert *tls.Certificate, err error) {
299 cert, err = tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
300 if os.IsNotExist(err) {
301 if info.Logger != nil {
302 info.Logger.Warn(
303 "failed to find peer cert files",
304 zap.String("cert-file", info.CertFile),
305 zap.String("key-file", info.KeyFile),
306 zap.Error(err),
307 )
308 }
309 } else if err != nil {
310 if info.Logger != nil {
311 info.Logger.Warn(
312 "failed to create peer certificate",
313 zap.String("cert-file", info.CertFile),
314 zap.String("key-file", info.KeyFile),
315 zap.Error(err),
316 )
317 }
318 }
319 return cert, err
320 }
321 cfg.GetClientCertificate = func(unused *tls.CertificateRequestInfo) (cert *tls.Certificate, err error) {
322 cert, err = tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc)
323 if os.IsNotExist(err) {
324 if info.Logger != nil {
325 info.Logger.Warn(
326 "failed to find client cert files",
327 zap.String("cert-file", info.CertFile),
328 zap.String("key-file", info.KeyFile),
329 zap.Error(err),
330 )
331 }
332 } else if err != nil {
333 if info.Logger != nil {
334 info.Logger.Warn(
335 "failed to create client certificate",
336 zap.String("cert-file", info.CertFile),
337 zap.String("key-file", info.KeyFile),
338 zap.Error(err),
339 )
340 }
341 }
342 return cert, err
343 }
344 return cfg, nil
345}
346
347// cafiles returns a list of CA file paths.
348func (info TLSInfo) cafiles() []string {
349 cs := make([]string, 0)
350 if info.TrustedCAFile != "" {
351 cs = append(cs, info.TrustedCAFile)
352 }
353 return cs
354}
355
356// ServerConfig generates a tls.Config object for use by an HTTP server.
357func (info TLSInfo) ServerConfig() (*tls.Config, error) {
358 cfg, err := info.baseConfig()
359 if err != nil {
360 return nil, err
361 }
362
363 cfg.ClientAuth = tls.NoClientCert
364 if info.TrustedCAFile != "" || info.ClientCertAuth {
365 cfg.ClientAuth = tls.RequireAndVerifyClientCert
366 }
367
368 cs := info.cafiles()
369 if len(cs) > 0 {
370 cp, err := tlsutil.NewCertPool(cs)
371 if err != nil {
372 return nil, err
373 }
374 cfg.ClientCAs = cp
375 }
376
377 // "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
378 cfg.NextProtos = []string{"h2"}
379
380 // go1.13 enables TLS 1.3 by default
381 // and in TLS 1.3, cipher suites are not configurable
382 // setting Max TLS version to TLS 1.2 for go 1.13
383 cfg.MaxVersion = tls.VersionTLS12
384
385 return cfg, nil
386}
387
388// ClientConfig generates a tls.Config object for use by an HTTP client.
389func (info TLSInfo) ClientConfig() (*tls.Config, error) {
390 var cfg *tls.Config
391 var err error
392
393 if !info.Empty() {
394 cfg, err = info.baseConfig()
395 if err != nil {
396 return nil, err
397 }
398 } else {
399 cfg = &tls.Config{ServerName: info.ServerName}
400 }
401 cfg.InsecureSkipVerify = info.InsecureSkipVerify
402
403 cs := info.cafiles()
404 if len(cs) > 0 {
405 cfg.RootCAs, err = tlsutil.NewCertPool(cs)
406 if err != nil {
407 return nil, err
408 }
409 }
410
411 if info.selfCert {
412 cfg.InsecureSkipVerify = true
413 }
414
415 if info.EmptyCN {
416 hasNonEmptyCN := false
417 cn := ""
418 tlsutil.NewCert(info.CertFile, info.KeyFile, func(certPEMBlock []byte, keyPEMBlock []byte) (tls.Certificate, error) {
419 var block *pem.Block
420 block, _ = pem.Decode(certPEMBlock)
421 cert, err := x509.ParseCertificate(block.Bytes)
422 if err != nil {
423 return tls.Certificate{}, err
424 }
425 if len(cert.Subject.CommonName) != 0 {
426 hasNonEmptyCN = true
427 cn = cert.Subject.CommonName
428 }
429 return tls.X509KeyPair(certPEMBlock, keyPEMBlock)
430 })
431 if hasNonEmptyCN {
432 return nil, fmt.Errorf("cert has non empty Common Name (%s)", cn)
433 }
434 }
435
436 // go1.13 enables TLS 1.3 by default
437 // and in TLS 1.3, cipher suites are not configurable
438 // setting Max TLS version to TLS 1.2 for go 1.13
439 cfg.MaxVersion = tls.VersionTLS12
440
441 return cfg, nil
442}
443
444// IsClosedConnError returns true if the error is from closing listener, cmux.
445// copied from golang.org/x/net/http2/http2.go
446func IsClosedConnError(err error) bool {
447 // 'use of closed network connection' (Go <=1.8)
448 // 'use of closed file or network connection' (Go >1.8, internal/poll.ErrClosing)
449 // 'mux: listener closed' (cmux.ErrListenerClosed)
450 return err != nil && strings.Contains(err.Error(), "closed")
451}