storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/certs/certs.go (about)

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