...

Source file src/github.com/docker/go-connections/tlsconfig/config.go

Documentation: github.com/docker/go-connections/tlsconfig

     1  // Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
     2  //
     3  // As a reminder from https://golang.org/pkg/crypto/tls/#Config:
     4  //
     5  //	A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified.
     6  //	A Config may be reused; the tls package will also not modify it.
     7  package tlsconfig
     8  
     9  import (
    10  	"crypto/tls"
    11  	"crypto/x509"
    12  	"encoding/pem"
    13  	"errors"
    14  	"fmt"
    15  	"os"
    16  )
    17  
    18  // Options represents the information needed to create client and server TLS configurations.
    19  type Options struct {
    20  	CAFile string
    21  
    22  	// If either CertFile or KeyFile is empty, Client() will not load them
    23  	// preventing the client from authenticating to the server.
    24  	// However, Server() requires them and will error out if they are empty.
    25  	CertFile string
    26  	KeyFile  string
    27  
    28  	// client-only option
    29  	InsecureSkipVerify bool
    30  	// server-only option
    31  	ClientAuth tls.ClientAuthType
    32  	// If ExclusiveRootPools is set, then if a CA file is provided, the root pool used for TLS
    33  	// creds will include exclusively the roots in that CA file.  If no CA file is provided,
    34  	// the system pool will be used.
    35  	ExclusiveRootPools bool
    36  	MinVersion         uint16
    37  	// If Passphrase is set, it will be used to decrypt a TLS private key
    38  	// if the key is encrypted.
    39  	//
    40  	// Deprecated: Use of encrypted TLS private keys has been deprecated, and
    41  	// will be removed in a future release. Golang has deprecated support for
    42  	// legacy PEM encryption (as specified in RFC 1423), as it is insecure by
    43  	// design (see https://go-review.googlesource.com/c/go/+/264159).
    44  	Passphrase string
    45  }
    46  
    47  // Extra (server-side) accepted CBC cipher suites - will phase out in the future
    48  var acceptedCBCCiphers = []uint16{
    49  	tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
    50  	tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
    51  	tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
    52  	tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
    53  }
    54  
    55  // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
    56  // options struct but wants to use a commonly accepted set of TLS cipher suites, with
    57  // known weak algorithms removed.
    58  var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
    59  
    60  // ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
    61  func ServerDefault(ops ...func(*tls.Config)) *tls.Config {
    62  	tlsConfig := &tls.Config{
    63  		// Avoid fallback by default to SSL protocols < TLS1.2
    64  		MinVersion:               tls.VersionTLS12,
    65  		PreferServerCipherSuites: true,
    66  		CipherSuites:             DefaultServerAcceptedCiphers,
    67  	}
    68  
    69  	for _, op := range ops {
    70  		op(tlsConfig)
    71  	}
    72  
    73  	return tlsConfig
    74  }
    75  
    76  // ClientDefault returns a secure-enough TLS configuration for the client TLS configuration.
    77  func ClientDefault(ops ...func(*tls.Config)) *tls.Config {
    78  	tlsConfig := &tls.Config{
    79  		// Prefer TLS1.2 as the client minimum
    80  		MinVersion:   tls.VersionTLS12,
    81  		CipherSuites: clientCipherSuites,
    82  	}
    83  
    84  	for _, op := range ops {
    85  		op(tlsConfig)
    86  	}
    87  
    88  	return tlsConfig
    89  }
    90  
    91  // certPool returns an X.509 certificate pool from `caFile`, the certificate file.
    92  func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
    93  	// If we should verify the server, we need to load a trusted ca
    94  	var (
    95  		certPool *x509.CertPool
    96  		err      error
    97  	)
    98  	if exclusivePool {
    99  		certPool = x509.NewCertPool()
   100  	} else {
   101  		certPool, err = SystemCertPool()
   102  		if err != nil {
   103  			return nil, fmt.Errorf("failed to read system certificates: %v", err)
   104  		}
   105  	}
   106  	pemData, err := os.ReadFile(caFile)
   107  	if err != nil {
   108  		return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err)
   109  	}
   110  	if !certPool.AppendCertsFromPEM(pemData) {
   111  		return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
   112  	}
   113  	return certPool, nil
   114  }
   115  
   116  // allTLSVersions lists all the TLS versions and is used by the code that validates
   117  // a uint16 value as a TLS version.
   118  var allTLSVersions = map[uint16]struct{}{
   119  	tls.VersionTLS10: {},
   120  	tls.VersionTLS11: {},
   121  	tls.VersionTLS12: {},
   122  	tls.VersionTLS13: {},
   123  }
   124  
   125  // isValidMinVersion checks that the input value is a valid tls minimum version
   126  func isValidMinVersion(version uint16) bool {
   127  	_, ok := allTLSVersions[version]
   128  	return ok
   129  }
   130  
   131  // adjustMinVersion sets the MinVersion on `config`, the input configuration.
   132  // It assumes the current MinVersion on the `config` is the lowest allowed.
   133  func adjustMinVersion(options Options, config *tls.Config) error {
   134  	if options.MinVersion > 0 {
   135  		if !isValidMinVersion(options.MinVersion) {
   136  			return fmt.Errorf("invalid minimum TLS version: %x", options.MinVersion)
   137  		}
   138  		if options.MinVersion < config.MinVersion {
   139  			return fmt.Errorf("requested minimum TLS version is too low. Should be at-least: %x", config.MinVersion)
   140  		}
   141  		config.MinVersion = options.MinVersion
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  // IsErrEncryptedKey returns true if the 'err' is an error of incorrect
   148  // password when trying to decrypt a TLS private key.
   149  //
   150  // Deprecated: Use of encrypted TLS private keys has been deprecated, and
   151  // will be removed in a future release. Golang has deprecated support for
   152  // legacy PEM encryption (as specified in RFC 1423), as it is insecure by
   153  // design (see https://go-review.googlesource.com/c/go/+/264159).
   154  func IsErrEncryptedKey(err error) bool {
   155  	return errors.Is(err, x509.IncorrectPasswordError)
   156  }
   157  
   158  // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
   159  // If the private key is encrypted, 'passphrase' is used to decrypted the
   160  // private key.
   161  func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
   162  	// this section makes some small changes to code from notary/tuf/utils/x509.go
   163  	pemBlock, _ := pem.Decode(keyBytes)
   164  	if pemBlock == nil {
   165  		return nil, fmt.Errorf("no valid private key found")
   166  	}
   167  
   168  	var err error
   169  	if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated)
   170  		keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated)
   171  		if err != nil {
   172  			return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err)
   173  		}
   174  		keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
   175  	}
   176  
   177  	return keyBytes, nil
   178  }
   179  
   180  // getCert returns a Certificate from the CertFile and KeyFile in 'options',
   181  // if the key is encrypted, the Passphrase in 'options' will be used to
   182  // decrypt it.
   183  func getCert(options Options) ([]tls.Certificate, error) {
   184  	if options.CertFile == "" && options.KeyFile == "" {
   185  		return nil, nil
   186  	}
   187  
   188  	cert, err := os.ReadFile(options.CertFile)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	prKeyBytes, err := os.ReadFile(options.KeyFile)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	tlsCert, err := tls.X509KeyPair(cert, prKeyBytes)
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	return []tls.Certificate{tlsCert}, nil
   209  }
   210  
   211  // Client returns a TLS configuration meant to be used by a client.
   212  func Client(options Options) (*tls.Config, error) {
   213  	tlsConfig := ClientDefault()
   214  	tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
   215  	if !options.InsecureSkipVerify && options.CAFile != "" {
   216  		CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
   217  		if err != nil {
   218  			return nil, err
   219  		}
   220  		tlsConfig.RootCAs = CAs
   221  	}
   222  
   223  	tlsCerts, err := getCert(options)
   224  	if err != nil {
   225  		return nil, fmt.Errorf("could not load X509 key pair: %w", err)
   226  	}
   227  	tlsConfig.Certificates = tlsCerts
   228  
   229  	if err := adjustMinVersion(options, tlsConfig); err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	return tlsConfig, nil
   234  }
   235  
   236  // Server returns a TLS configuration meant to be used by a server.
   237  func Server(options Options) (*tls.Config, error) {
   238  	tlsConfig := ServerDefault()
   239  	tlsConfig.ClientAuth = options.ClientAuth
   240  	tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
   241  	if err != nil {
   242  		if os.IsNotExist(err) {
   243  			return nil, fmt.Errorf("could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
   244  		}
   245  		return nil, fmt.Errorf("error reading X509 key pair - make sure the key is not encrypted (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
   246  	}
   247  	tlsConfig.Certificates = []tls.Certificate{tlsCert}
   248  	if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" {
   249  		CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  		tlsConfig.ClientCAs = CAs
   254  	}
   255  
   256  	if err := adjustMinVersion(options, tlsConfig); err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	return tlsConfig, nil
   261  }
   262  

View as plain text