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