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  }