1// Copyright (c) 2015-2021 MinIO, Inc.
2//
3// This file is part of MinIO Object Storage stack
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18package certs
19
20import (
21	"context"
22	"crypto/tls"
23	"crypto/x509"
24	"errors"
25	"fmt"
26	"os"
27	"path/filepath"
28	"sync"
29	"time"
30
31	"github.com/rjeczalik/notify"
32)
33
34// LoadX509KeyPairFunc is a function that parses a private key and
35// certificate file and returns a TLS certificate on success.
36type LoadX509KeyPairFunc func(certFile, keyFile string) (tls.Certificate, error)
37
38// GetCertificateFunc is a callback that allows a TLS stack deliver different
39// certificates based on the client trying to establish a TLS connection.
40//
41// For example, a GetCertificateFunc can return different TLS certificates depending
42// upon the TLS SNI sent by the client.
43type GetCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error)
44
45// Manager is a TLS certificate manager that can handle multiple certificates.
46// When a client tries to establish a TLS connection, Manager will try to
47// pick a certificate that can be validated by the client.
48//
49// For instance, if the client specifies a TLS SNI then Manager will try to
50// find the corresponding certificate. If there is no such certificate it
51// will fallback to the certificate named public.crt.
52//
53// Manager will automatically reload certificates if the corresponding file changes.
54type Manager struct {
55	lock         sync.RWMutex
56	certificates map[pair]*tls.Certificate // Mapping: certificate file name => TLS certificates
57	defaultCert  pair
58
59	loadX509KeyPair LoadX509KeyPairFunc
60	events          chan notify.EventInfo
61	ctx             context.Context
62}
63
64// pair represents a certificate and private key file tuple.
65type pair struct {
66	KeyFile  string
67	CertFile string
68}
69
70// NewManager returns a new Manager that handles one certificate specified via
71// the certFile and keyFile. It will use the loadX509KeyPair function to (re)load
72// certificates.
73//
74// The certificate loaded from certFile is considered the default certificate.
75// If a client does not send the TLS SNI extension then Manager will return
76// this certificate.
77func NewManager(ctx context.Context, certFile, keyFile string, loadX509KeyPair LoadX509KeyPairFunc) (manager *Manager, err error) {
78	certFile, err = filepath.Abs(certFile)
79	if err != nil {
80		return nil, err
81	}
82	keyFile, err = filepath.Abs(keyFile)
83	if err != nil {
84		return nil, err
85	}
86
87	manager = &Manager{
88		certificates: map[pair]*tls.Certificate{},
89		defaultCert: pair{
90			KeyFile:  keyFile,
91			CertFile: certFile,
92		},
93		loadX509KeyPair: loadX509KeyPair,
94		events:          make(chan notify.EventInfo, 1),
95		ctx:             ctx,
96	}
97	if err := manager.AddCertificate(certFile, keyFile); err != nil {
98		return nil, err
99	}
100	go manager.watchFileEvents()
101	return manager, nil
102}
103
104// AddCertificate adds the TLS certificate in certFile resp. keyFile
105// to the Manager.
106//
107// If there is already a certificate with the same base name it will be
108// replaced by the newly added one.
109func (m *Manager) AddCertificate(certFile, keyFile string) (err error) {
110	certFile, err = filepath.Abs(certFile)
111	if err != nil {
112		return err
113	}
114	keyFile, err = filepath.Abs(keyFile)
115	if err != nil {
116		return err
117	}
118	certFileIsLink, err := isSymlink(certFile)
119	if err != nil {
120		return err
121	}
122	keyFileIsLink, err := isSymlink(keyFile)
123	if err != nil {
124		return err
125	}
126	if certFileIsLink && !keyFileIsLink {
127		return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", certFile, keyFile)
128	}
129	if keyFileIsLink && !certFileIsLink {
130		return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", keyFile, certFile)
131	}
132
133	certificate, err := m.loadX509KeyPair(certFile, keyFile)
134	if err != nil {
135		return err
136	}
137	// We set the certificate leaf to the actual certificate such that
138	// we don't have to do the parsing (multiple times) when matching the
139	// certificate to the client hello. This a performance optimisation.
140	if certificate.Leaf == nil {
141		certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
142		if err != nil {
143			return err
144		}
145	}
146
147	p := pair{
148		CertFile: certFile,
149		KeyFile:  keyFile,
150	}
151	m.lock.Lock()
152	defer m.lock.Unlock()
153
154	// We don't allow IP SANs in certificates - except for the "default" certificate
155	// which is, by convention, the first certificate added to the manager. The problem
156	// with allowing IP SANs in more than one certificate is that the manager usually can't
157	// match the client SNI to a SAN since the SNI is meant to communicate the destination
158	// host name and clients will not set the SNI to an IP address.
159	// Allowing multiple certificates with IP SANs lead to errors that confuses users - like:
160	// "It works for `https://instance.minio.local` but not for `https://10.0.2.1`"
161	if len(m.certificates) > 0 && len(certificate.Leaf.IPAddresses) > 0 {
162		return errors.New("cert: certificate must not contain any IP SANs: only the default certificate may contain IP SANs")
163	}
164	m.certificates[p] = &certificate
165
166	if certFileIsLink && keyFileIsLink {
167		go m.watchSymlinks(certFile, keyFile)
168	} else {
169		// Windows doesn't allow for watching file changes but instead allows
170		// for directory changes only, while we can still watch for changes
171		// on files on other platforms. Watch parent directory on all platforms
172		// for simplicity.
173		if err = notify.Watch(filepath.Dir(certFile), m.events, eventWrite...); err != nil {
174			return err
175		}
176		if err = notify.Watch(filepath.Dir(keyFile), m.events, eventWrite...); err != nil {
177			return err
178		}
179	}
180	return nil
181}
182
183// watchSymlinks starts an endless loop reloading the
184// certFile and keyFile periodically.
185func (m *Manager) watchSymlinks(certFile, keyFile string) {
186	for {
187		select {
188		case <-m.ctx.Done():
189			return // Once stopped exits this routine.
190		case <-time.After(24 * time.Hour):
191			certificate, err := m.loadX509KeyPair(certFile, keyFile)
192			if err != nil {
193				continue
194			}
195			if certificate.Leaf == nil { // This is a performance optimisation
196				certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
197				if err != nil {
198					continue
199				}
200			}
201
202			p := pair{
203				CertFile: certFile,
204				KeyFile:  keyFile,
205			}
206			m.lock.Lock()
207			m.certificates[p] = &certificate
208			m.lock.Unlock()
209		}
210	}
211}
212
213// watchFileEvents starts an endless loop waiting for file systems events.
214// Once an event occurs it reloads the private key and certificate that
215// has changed, if any.
216func (m *Manager) watchFileEvents() {
217	for {
218		select {
219		case <-m.ctx.Done():
220			return
221		case event := <-m.events:
222			if !isWriteEvent(event.Event()) {
223				continue
224			}
225
226			for pair := range m.certificates {
227				if p := event.Path(); pair.KeyFile == p || pair.CertFile == p {
228					certificate, err := m.loadX509KeyPair(pair.CertFile, pair.KeyFile)
229					if err != nil {
230						continue
231					}
232					if certificate.Leaf == nil { // This is performance optimisation
233						certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0])
234						if err != nil {
235							continue
236						}
237					}
238					m.lock.Lock()
239					m.certificates[pair] = &certificate
240					m.lock.Unlock()
241				}
242			}
243		}
244	}
245}
246
247// GetCertificate returns a TLS certificate based on the client hello.
248//
249// It tries to find a certificate that would be accepted by the client
250// according to the client hello. However, if no certificate can be
251// found GetCertificate returns the certificate loaded from the
252// Public file.
253func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
254	m.lock.RLock()
255	defer m.lock.RUnlock()
256
257	// If the client does not send a SNI we return the "default"
258	// certificate. A client may not send a SNI - e.g. when trying
259	// to connect to an IP directly (https://<ip>:<port>).
260	//
261	// In this case we don't know which the certificate the client
262	// asks for. It may be a public-facing certificate issued by a
263	// public CA or an internal certificate containing internal domain
264	// names.
265	// Now, we should not serve "the first" certificate that would be
266	// accepted by the client based on the Client Hello. Otherwise, we
267	// may expose an internal certificate to the client that contains
268	// internal domain names. That way we would disclose internal
269	// infrastructure details.
270	//
271	// Therefore, we serve the "default" certificate - which by convention
272	// is the first certificate added to the Manager. It's the calling code's
273	// responsibility to ensure that the "public-facing" certificate is used
274	// when creating a Manager instance.
275	if hello.ServerName == "" {
276		certificate := m.certificates[m.defaultCert]
277		return certificate, nil
278	}
279
280	// Optimization: If there is just one certificate, always serve that one.
281	if len(m.certificates) == 1 {
282		for _, certificate := range m.certificates {
283			return certificate, nil
284		}
285	}
286
287	// Iterate over all certificates and return the first one that would
288	// be accepted by the peer (TLS client) based on the client hello.
289	// In particular, the client usually specifies the requested host/domain
290	// via SNI.
291	//
292	// Note: The certificate.Leaf should be non-nil and contain the actual
293	// client certificate of MinIO that should be presented to the peer (TLS client).
294	// Otherwise, the leaf certificate has to be parsed again - which is kind of
295	// expensive and may cause a performance issue. For more information, check the
296	// docs of tls.ClientHelloInfo.SupportsCertificate.
297	for _, certificate := range m.certificates {
298		if err := hello.SupportsCertificate(certificate); err == nil {
299			return certificate, nil
300		}
301	}
302	return nil, errors.New("certs: no server certificate is supported by peer")
303}
304
305// GetClientCertificate returns a TLS certificate for mTLS based on the
306// certificate request.
307//
308// It tries to find a certificate that would be accepted by the server
309// according to the certificate request. However, if no certificate can be
310// found GetClientCertificate returns the certificate loaded from the
311// Public file.
312func (m *Manager) GetClientCertificate(reqInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) {
313	m.lock.RLock()
314	defer m.lock.RUnlock()
315
316	// Optimization: If there is just one certificate, always serve that one.
317	if len(m.certificates) == 1 {
318		for _, certificate := range m.certificates {
319			return certificate, nil
320		}
321	}
322
323	// Iterate over all certificates and return the first one that would
324	// be accepted by the peer (TLS server) based on reqInfo.
325	//
326	// Note: The certificate.Leaf should be non-nil and contain the actual
327	// client certificate of MinIO that should be presented to the peer (TLS server).
328	// Otherwise, the leaf certificate has to be parsed again - which is kind of
329	// expensive and may cause a performance issue. For more information, check the
330	// docs of tls.CertificateRequestInfo.SupportsCertificate.
331	for _, certificate := range m.certificates {
332		if err := reqInfo.SupportsCertificate(certificate); err == nil {
333			return certificate, nil
334		}
335	}
336	return nil, errors.New("certs: no client certificate is supported by peer")
337}
338
339// isSymlink returns true if the given file
340// is a symbolic link.
341func isSymlink(file string) (bool, error) {
342	st, err := os.Lstat(file)
343	if err != nil {
344		return false, err
345	}
346	return st.Mode()&os.ModeSymlink == os.ModeSymlink, nil
347}
348