vitess.io/vitess@v0.16.2/go/vt/vtorc/ssl/ssl.go (about)

     1  package ssl
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"encoding/pem"
     7  	"errors"
     8  	"fmt"
     9  	nethttp "net/http"
    10  	"os"
    11  
    12  	"vitess.io/vitess/go/vt/log"
    13  
    14  	"github.com/howeyc/gopass"
    15  )
    16  
    17  // Determine if a string element is in a string array
    18  func HasString(elem string, arr []string) bool {
    19  	for _, s := range arr {
    20  		if s == elem {
    21  			return true
    22  		}
    23  	}
    24  	return false
    25  }
    26  
    27  // NewTLSConfig returns an initialized TLS configuration suitable for client
    28  // authentication. If caFile is non-empty, it will be loaded.
    29  func NewTLSConfig(caFile string, verifyCert bool) (*tls.Config, error) {
    30  	var c tls.Config
    31  
    32  	// Set to TLS 1.2 as a minimum.  This is overridden for mysql communication
    33  	c.MinVersion = tls.VersionTLS12
    34  
    35  	if verifyCert {
    36  		log.Info("verifyCert requested, client certificates will be verified")
    37  		c.ClientAuth = tls.VerifyClientCertIfGiven
    38  	}
    39  	caPool, err := ReadCAFile(caFile)
    40  	if err != nil {
    41  		return &c, err
    42  	}
    43  	c.ClientCAs = caPool
    44  	return &c, nil
    45  }
    46  
    47  // Returns CA certificate. If caFile is non-empty, it will be loaded.
    48  func ReadCAFile(caFile string) (*x509.CertPool, error) {
    49  	var caCertPool *x509.CertPool
    50  	if caFile != "" {
    51  		data, err := os.ReadFile(caFile)
    52  		if err != nil {
    53  			return nil, err
    54  		}
    55  		caCertPool = x509.NewCertPool()
    56  		if !caCertPool.AppendCertsFromPEM(data) {
    57  			return nil, errors.New("No certificates parsed")
    58  		}
    59  		log.Infof("Read in CA file: %v", caFile)
    60  	}
    61  	return caCertPool, nil
    62  }
    63  
    64  // AppendKeyPair loads the given TLS key pair and appends it to
    65  // tlsConfig.Certificates.
    66  func AppendKeyPair(tlsConfig *tls.Config, certFile string, keyFile string) error {
    67  	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
    72  	return nil
    73  }
    74  
    75  // Read in a keypair where the key is password protected
    76  func AppendKeyPairWithPassword(tlsConfig *tls.Config, certFile string, keyFile string, pemPass []byte) error {
    77  
    78  	// Certificates aren't usually password protected, but we're kicking the password
    79  	// along just in case.  It won't be used if the file isn't encrypted
    80  	certData, err := ReadPEMData(certFile, pemPass)
    81  	if err != nil {
    82  		return err
    83  	}
    84  	keyData, err := ReadPEMData(keyFile, pemPass)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	cert, err := tls.X509KeyPair(certData, keyData)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
    93  	return nil
    94  }
    95  
    96  // Read a PEM file and ask for a password to decrypt it if needed
    97  func ReadPEMData(pemFile string, pemPass []byte) ([]byte, error) {
    98  	pemData, err := os.ReadFile(pemFile)
    99  	if err != nil {
   100  		return pemData, err
   101  	}
   102  
   103  	// We should really just get the pem.Block back here, if there's other
   104  	// junk on the end, warn about it.
   105  	pemBlock, rest := pem.Decode(pemData)
   106  	if len(rest) > 0 {
   107  		log.Warning("Didn't parse all of", pemFile)
   108  	}
   109  
   110  	if x509.IsEncryptedPEMBlock(pemBlock) { //nolint SA1019
   111  		// Decrypt and get the ASN.1 DER bytes here
   112  		pemData, err = x509.DecryptPEMBlock(pemBlock, pemPass) //nolint SA1019
   113  		if err != nil {
   114  			return pemData, err
   115  		}
   116  		log.Infof("Decrypted %v successfully", pemFile)
   117  		// Shove the decrypted DER bytes into a new pem Block with blank headers
   118  		var newBlock pem.Block
   119  		newBlock.Type = pemBlock.Type
   120  		newBlock.Bytes = pemData
   121  		// This is now like reading in an uncrypted key from a file and stuffing it
   122  		// into a byte stream
   123  		pemData = pem.EncodeToMemory(&newBlock)
   124  	}
   125  	return pemData, nil
   126  }
   127  
   128  // Print a password prompt on the terminal and collect a password
   129  func GetPEMPassword(pemFile string) []byte {
   130  	fmt.Printf("Password for %s: ", pemFile)
   131  	pass, err := gopass.GetPasswd()
   132  	if err != nil {
   133  		// We'll error with an incorrect password at DecryptPEMBlock
   134  		return []byte("")
   135  	}
   136  	return pass
   137  }
   138  
   139  // Determine if PEM file is encrypted
   140  func IsEncryptedPEM(pemFile string) bool {
   141  	pemData, err := os.ReadFile(pemFile)
   142  	if err != nil {
   143  		return false
   144  	}
   145  	pemBlock, _ := pem.Decode(pemData)
   146  	if len(pemBlock.Bytes) == 0 {
   147  		return false
   148  	}
   149  	return x509.IsEncryptedPEMBlock(pemBlock) //nolint SA1019
   150  }
   151  
   152  // ListenAndServeTLS acts identically to http.ListenAndServeTLS, except that it
   153  // expects TLS configuration.
   154  // TODO: refactor so this is testable?
   155  func ListenAndServeTLS(addr string, handler nethttp.Handler, tlsConfig *tls.Config) error {
   156  	if addr == "" {
   157  		// On unix Listen calls getaddrinfo to parse the port, so named ports are fine as long
   158  		// as they exist in /etc/services
   159  		addr = ":https"
   160  	}
   161  	l, err := tls.Listen("tcp", addr, tlsConfig)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	return nethttp.Serve(l, handler)
   166  }