github.com/zntrio/harp/v2@v2.0.9/pkg/sdk/tlsconfig/config.go (about)

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