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 }