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