github.com/uber/kraken@v0.1.4/utils/httputil/tls.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package httputil 15 16 import ( 17 "bytes" 18 "crypto/tls" 19 "crypto/x509" 20 "encoding/pem" 21 "errors" 22 "fmt" 23 "io" 24 "io/ioutil" 25 26 "github.com/uber/kraken/utils/log" 27 ) 28 29 // ErrEmptyCommonName is returned when common name is not provided for key generation. 30 var ErrEmptyCommonName = errors.New("empty common name") 31 32 // TLSConfig defines TLS configuration. 33 type TLSConfig struct { 34 Name string `yaml:"name"` 35 Server X509Pair `yaml:"server"` 36 Client X509Pair `yaml:"client"` 37 CAs []Secret `yaml:"cas"` 38 39 // Lazy init. 40 tls *tls.Config 41 } 42 43 // X509Pair contains x509 cert configuration. 44 // Both Cert and Key should be already in pem format. 45 type X509Pair struct { 46 Disabled bool `yaml:"disabled"` 47 Cert Secret `yaml:"cert"` 48 Key Secret `yaml:"key"` 49 Passphrase Secret `yaml:"passphrase"` 50 } 51 52 // Secret contains secret path configuration. 53 type Secret struct { 54 Path string `yaml:"path"` 55 } 56 57 // BuildClient builts tls.Config for http client. 58 func (c *TLSConfig) BuildClient() (*tls.Config, error) { 59 if c.Client.Disabled { 60 log.Infof("Client TLS is disabled") 61 return nil, nil 62 } 63 if c.tls != nil { 64 return c.tls, nil 65 } 66 67 var caPool *x509.CertPool 68 var certs []tls.Certificate 69 var err error 70 if len(c.CAs) > 0 { 71 caPool, err = createCertPool(c.CAs) 72 if err != nil { 73 return nil, fmt.Errorf("create cert pool: %s", err) 74 } 75 } 76 if c.Client.Cert.Path != "" { 77 certPEM, err := parseCert(c.Client.Cert.Path) 78 if err != nil { 79 return nil, fmt.Errorf("parse client cert: %s", err) 80 } 81 keyPEM, err := parseKey(c.Client.Key.Path, c.Client.Passphrase.Path) 82 if err != nil { 83 return nil, fmt.Errorf("parse client key: %s", err) 84 } 85 cert, err := tls.X509KeyPair(certPEM, keyPEM) 86 if err != nil { 87 return nil, fmt.Errorf("load client x509 key pair: %s", err) 88 } 89 certs = []tls.Certificate{cert} 90 } 91 c.tls = &tls.Config{ 92 Certificates: certs, 93 RootCAs: caPool, 94 ServerName: c.Name, 95 PreferServerCipherSuites: true, 96 InsecureSkipVerify: false, // This is important to enforce verification of server. 97 } 98 return c.tls, nil 99 } 100 101 // WriteCABundle writes a list of CA to a writer. 102 func (c *TLSConfig) WriteCABundle(w io.Writer) error { 103 pems, err := concatSecrets(c.CAs) 104 if err != nil { 105 return fmt.Errorf("concat secrets: %s", err) 106 } 107 if _, err := w.Write(pems); err != nil { 108 return fmt.Errorf("write cas: %s", err) 109 } 110 return nil 111 } 112 113 func createCertPool(secrets []Secret) (*x509.CertPool, error) { 114 pool, err := x509.SystemCertPool() 115 if err != nil { 116 return nil, fmt.Errorf("create system cert pool: %s", err) 117 } 118 // No system certs provided. Create an empty cert pool. 119 if pool == nil { 120 pool = x509.NewCertPool() 121 } 122 pems, err := concatSecrets(secrets) 123 if err != nil { 124 return nil, fmt.Errorf("concat secrets: %s", err) 125 } 126 if ok := pool.AppendCertsFromPEM(pems); !ok { 127 return nil, fmt.Errorf("cannot append cert") 128 } 129 return pool, nil 130 } 131 132 func concatSecrets(secrets []Secret) ([]byte, error) { 133 result := bytes.Buffer{} 134 for _, s := range secrets { 135 pem, err := parseCert(s.Path) 136 if err != nil { 137 return nil, fmt.Errorf("parse cert: %s", err) 138 } 139 result.Write(pem) 140 } 141 return result.Bytes(), nil 142 } 143 144 func parseCert(path string) ([]byte, error) { 145 certBytes, err := ioutil.ReadFile(path) 146 if err != nil { 147 return nil, fmt.Errorf("read file: %s", err) 148 } 149 return certBytes, nil 150 } 151 152 // parseKey reads key from file and decrypts if passphrase is provided. 153 func parseKey(path, passphrasePath string) ([]byte, error) { 154 keyPEM, err := ioutil.ReadFile(path) 155 if err != nil { 156 return nil, fmt.Errorf("read file: %s", err) 157 } 158 if passphrasePath != "" { 159 passphrase, err := ioutil.ReadFile(passphrasePath) 160 if err != nil { 161 return nil, fmt.Errorf("read passphrase file: %s", err) 162 } 163 keyBytes, err := decryptPEMBlock(keyPEM, passphrase) 164 if err != nil { 165 return nil, fmt.Errorf("decrypt key: %s", err) 166 } 167 keyPEM, err = encodePEMKey(keyBytes) 168 if err != nil { 169 return nil, fmt.Errorf("encode key: %s", err) 170 } 171 } 172 return keyPEM, nil 173 } 174 175 // decryptPEMBlock decrypts the block of data. 176 func decryptPEMBlock(data, secret []byte) ([]byte, error) { 177 block, _ := pem.Decode(data) 178 if block == nil || len(block.Bytes) < 1 { 179 return nil, errors.New("empty block") 180 } 181 decoded, err := x509.DecryptPEMBlock(block, secret) 182 if err != nil { 183 return nil, fmt.Errorf("decrypt block: %s", err) 184 } 185 return decoded, nil 186 } 187 188 // encodePEMKey marshals the DER-encoded private key. 189 func encodePEMKey(data []byte) ([]byte, error) { 190 buf := new(bytes.Buffer) 191 err := pem.Encode(buf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: data}) 192 if err != nil { 193 return nil, fmt.Errorf("encode key: %s", err) 194 } 195 return buf.Bytes(), nil 196 }