Files
open-cluster-management/vendor/github.com/openshift/library-go/pkg/crypto/crypto.go

1183 lines
38 KiB
Go

package crypto
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
"io"
"io/ioutil"
"math/big"
mathrand "math/rand"
"net"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"time"
"k8s.io/klog/v2"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/util/cert"
)
// TLS versions that are known to golang. Go 1.13 adds support for
// TLS 1.3 that's opt-out with a build flag.
var versions = map[string]uint16{
"VersionTLS10": tls.VersionTLS10,
"VersionTLS11": tls.VersionTLS11,
"VersionTLS12": tls.VersionTLS12,
"VersionTLS13": tls.VersionTLS13,
}
// TLS versions that are enabled.
var supportedVersions = map[string]uint16{
"VersionTLS10": tls.VersionTLS10,
"VersionTLS11": tls.VersionTLS11,
"VersionTLS12": tls.VersionTLS12,
"VersionTLS13": tls.VersionTLS13,
}
// TLSVersionToNameOrDie given a tls version as an int, return its readable name
func TLSVersionToNameOrDie(intVal uint16) string {
matches := []string{}
for key, version := range versions {
if version == intVal {
matches = append(matches, key)
}
}
if len(matches) == 0 {
panic(fmt.Sprintf("no name found for %d", intVal))
}
if len(matches) > 1 {
panic(fmt.Sprintf("multiple names found for %d: %v", intVal, matches))
}
return matches[0]
}
func TLSVersion(versionName string) (uint16, error) {
if len(versionName) == 0 {
return DefaultTLSVersion(), nil
}
if version, ok := versions[versionName]; ok {
return version, nil
}
return 0, fmt.Errorf("unknown tls version %q", versionName)
}
func TLSVersionOrDie(versionName string) uint16 {
version, err := TLSVersion(versionName)
if err != nil {
panic(err)
}
return version
}
// TLS versions that are known to golang, but may not necessarily be enabled.
func GolangTLSVersions() []string {
supported := []string{}
for k := range versions {
supported = append(supported, k)
}
sort.Strings(supported)
return supported
}
// Returns the build enabled TLS versions.
func ValidTLSVersions() []string {
validVersions := []string{}
for k := range supportedVersions {
validVersions = append(validVersions, k)
}
sort.Strings(validVersions)
return validVersions
}
func DefaultTLSVersion() uint16 {
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
return tls.VersionTLS12
}
// ciphersTLS13 copies golang 1.13 implementation, where TLS1.3 suites are not
// configurable (cipherSuites field is ignored for TLS1.3 flows and all of the
// below three - and none other - are used)
var ciphersTLS13 = map[string]uint16{
"TLS_AES_128_GCM_SHA256": tls.TLS_AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384": tls.TLS_AES_256_GCM_SHA384,
"TLS_CHACHA20_POLY1305_SHA256": tls.TLS_CHACHA20_POLY1305_SHA256,
}
var ciphers = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
"TLS_RSA_WITH_AES_256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
"TLS_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
}
// openSSLToIANACiphersMap maps OpenSSL cipher suite names to IANA names
// ref: https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
var openSSLToIANACiphersMap = map[string]string{
// TLS 1.3 ciphers - not configurable in go 1.13, all of them are used in TLSv1.3 flows
// "TLS_AES_128_GCM_SHA256": "TLS_AES_128_GCM_SHA256", // 0x13,0x01
// "TLS_AES_256_GCM_SHA384": "TLS_AES_256_GCM_SHA384", // 0x13,0x02
// "TLS_CHACHA20_POLY1305_SHA256": "TLS_CHACHA20_POLY1305_SHA256", // 0x13,0x03
// TLS 1.2
"ECDHE-ECDSA-AES128-GCM-SHA256": "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2B
"ECDHE-RSA-AES128-GCM-SHA256": "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2F
"ECDHE-ECDSA-AES256-GCM-SHA384": "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", // 0xC0,0x2C
"ECDHE-RSA-AES256-GCM-SHA384": "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", // 0xC0,0x30
"ECDHE-ECDSA-CHACHA20-POLY1305": "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", // 0xCC,0xA9
"ECDHE-RSA-CHACHA20-POLY1305": "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", // 0xCC,0xA8
"ECDHE-ECDSA-AES128-SHA256": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", // 0xC0,0x23
"ECDHE-RSA-AES128-SHA256": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", // 0xC0,0x27
"AES128-GCM-SHA256": "TLS_RSA_WITH_AES_128_GCM_SHA256", // 0x00,0x9C
"AES256-GCM-SHA384": "TLS_RSA_WITH_AES_256_GCM_SHA384", // 0x00,0x9D
"AES128-SHA256": "TLS_RSA_WITH_AES_128_CBC_SHA256", // 0x00,0x3C
// TLS 1
"ECDHE-ECDSA-AES128-SHA": "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", // 0xC0,0x09
"ECDHE-RSA-AES128-SHA": "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", // 0xC0,0x13
"ECDHE-ECDSA-AES256-SHA": "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", // 0xC0,0x0A
"ECDHE-RSA-AES256-SHA": "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", // 0xC0,0x14
// SSL 3
"AES128-SHA": "TLS_RSA_WITH_AES_128_CBC_SHA", // 0x00,0x2F
"AES256-SHA": "TLS_RSA_WITH_AES_256_CBC_SHA", // 0x00,0x35
"DES-CBC3-SHA": "TLS_RSA_WITH_3DES_EDE_CBC_SHA", // 0x00,0x0A
}
// CipherSuitesToNamesOrDie given a list of cipher suites as ints, return their readable names
func CipherSuitesToNamesOrDie(intVals []uint16) []string {
ret := []string{}
for _, intVal := range intVals {
ret = append(ret, CipherSuiteToNameOrDie(intVal))
}
return ret
}
// CipherSuiteToNameOrDie given a cipher suite as an int, return its readable name
func CipherSuiteToNameOrDie(intVal uint16) string {
// The following suite ids appear twice in the cipher map (with
// and without the _SHA256 suffix) for the purposes of backwards
// compatibility. Always return the current rather than the legacy
// name.
switch intVal {
case tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
return "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
case tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
return "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
}
matches := []string{}
for key, version := range ciphers {
if version == intVal {
matches = append(matches, key)
}
}
if len(matches) == 0 {
panic(fmt.Sprintf("no name found for %d", intVal))
}
if len(matches) > 1 {
panic(fmt.Sprintf("multiple names found for %d: %v", intVal, matches))
}
return matches[0]
}
func CipherSuite(cipherName string) (uint16, error) {
if cipher, ok := ciphers[cipherName]; ok {
return cipher, nil
}
if _, ok := ciphersTLS13[cipherName]; ok {
return 0, fmt.Errorf("all golang TLSv1.3 ciphers are always used for TLSv1.3 flows")
}
return 0, fmt.Errorf("unknown cipher name %q", cipherName)
}
func CipherSuitesOrDie(cipherNames []string) []uint16 {
if len(cipherNames) == 0 {
return DefaultCiphers()
}
cipherValues := []uint16{}
for _, cipherName := range cipherNames {
cipher, err := CipherSuite(cipherName)
if err != nil {
panic(err)
}
cipherValues = append(cipherValues, cipher)
}
return cipherValues
}
func ValidCipherSuites() []string {
validCipherSuites := []string{}
for k := range ciphers {
validCipherSuites = append(validCipherSuites, k)
}
sort.Strings(validCipherSuites)
return validCipherSuites
}
func DefaultCiphers() []uint16 {
// HTTP/2 mandates TLS 1.2 or higher with an AEAD cipher
// suite (GCM, Poly1305) and ephemeral key exchange (ECDHE, DHE) for
// perfect forward secrecy. Servers may provide additional cipher
// suites for backwards compatibility with HTTP/1.1 clients.
// See RFC7540, section 9.2 (Use of TLS Features) and Appendix A
// (TLS 1.2 Cipher Suite Black List).
return []uint16{
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // required by http/2
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, // forbidden by http/2, not flagged by http2isBadCipher() in go1.8
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, // forbidden by http/2
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, // forbidden by http/2
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2
tls.TLS_RSA_WITH_AES_128_GCM_SHA256, // forbidden by http/2
tls.TLS_RSA_WITH_AES_256_GCM_SHA384, // forbidden by http/2
// the next one is in the intermediate suite, but go1.8 http2isBadCipher() complains when it is included at the recommended index
// because it comes after ciphers forbidden by the http/2 spec
// tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
// tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack
// tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, // forbidden by http/2, disabled to mitigate SWEET32 attack
tls.TLS_RSA_WITH_AES_128_CBC_SHA, // forbidden by http/2
tls.TLS_RSA_WITH_AES_256_CBC_SHA, // forbidden by http/2
}
}
// SecureTLSConfig enforces the default minimum security settings for the cluster.
func SecureTLSConfig(config *tls.Config) *tls.Config {
if config.MinVersion == 0 {
config.MinVersion = DefaultTLSVersion()
}
config.PreferServerCipherSuites = true
if len(config.CipherSuites) == 0 {
config.CipherSuites = DefaultCiphers()
}
return config
}
// OpenSSLToIANACipherSuites maps input OpenSSL Cipher Suite names to their
// IANA counterparts.
// Unknown ciphers are left out.
func OpenSSLToIANACipherSuites(ciphers []string) []string {
ianaCiphers := make([]string, 0, len(ciphers))
for _, c := range ciphers {
ianaCipher, found := openSSLToIANACiphersMap[c]
if found {
ianaCiphers = append(ianaCiphers, ianaCipher)
}
}
return ianaCiphers
}
type TLSCertificateConfig struct {
Certs []*x509.Certificate
Key crypto.PrivateKey
}
type TLSCARoots struct {
Roots []*x509.Certificate
}
func (c *TLSCertificateConfig) WriteCertConfigFile(certFile, keyFile string) error {
// ensure parent dir
if err := os.MkdirAll(filepath.Dir(certFile), os.FileMode(0755)); err != nil {
return err
}
certFileWriter, err := os.OpenFile(certFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(keyFile), os.FileMode(0755)); err != nil {
return err
}
keyFileWriter, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
if err := writeCertificates(certFileWriter, c.Certs...); err != nil {
return err
}
if err := writeKeyFile(keyFileWriter, c.Key); err != nil {
return err
}
if err := certFileWriter.Close(); err != nil {
return err
}
if err := keyFileWriter.Close(); err != nil {
return err
}
return nil
}
func (c *TLSCertificateConfig) WriteCertConfig(certFile, keyFile io.Writer) error {
if err := writeCertificates(certFile, c.Certs...); err != nil {
return err
}
if err := writeKeyFile(keyFile, c.Key); err != nil {
return err
}
return nil
}
func (c *TLSCertificateConfig) GetPEMBytes() ([]byte, []byte, error) {
certBytes, err := EncodeCertificates(c.Certs...)
if err != nil {
return nil, nil, err
}
keyBytes, err := encodeKey(c.Key)
if err != nil {
return nil, nil, err
}
return certBytes, keyBytes, nil
}
func GetTLSCertificateConfig(certFile, keyFile string) (*TLSCertificateConfig, error) {
if len(certFile) == 0 {
return nil, errors.New("certFile missing")
}
if len(keyFile) == 0 {
return nil, errors.New("keyFile missing")
}
certPEMBlock, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
certs, err := cert.ParseCertsPEM(certPEMBlock)
if err != nil {
return nil, fmt.Errorf("Error reading %s: %s", certFile, err)
}
keyPEMBlock, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, err
}
keyPairCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
return nil, err
}
key := keyPairCert.PrivateKey
return &TLSCertificateConfig{certs, key}, nil
}
func GetTLSCertificateConfigFromBytes(certBytes, keyBytes []byte) (*TLSCertificateConfig, error) {
if len(certBytes) == 0 {
return nil, errors.New("certFile missing")
}
if len(keyBytes) == 0 {
return nil, errors.New("keyFile missing")
}
certs, err := cert.ParseCertsPEM(certBytes)
if err != nil {
return nil, fmt.Errorf("Error reading cert: %s", err)
}
keyPairCert, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return nil, err
}
key := keyPairCert.PrivateKey
return &TLSCertificateConfig{certs, key}, nil
}
const (
DefaultCertificateLifetimeInDays = 365 * 2 // 2 years
DefaultCACertificateLifetimeInDays = 365 * 5 // 5 years
// Default keys are 2048 bits
keyBits = 2048
)
type CA struct {
Config *TLSCertificateConfig
SerialGenerator SerialGenerator
}
// SerialGenerator is an interface for getting a serial number for the cert. It MUST be thread-safe.
type SerialGenerator interface {
Next(template *x509.Certificate) (int64, error)
}
// SerialFileGenerator returns a unique, monotonically increasing serial number and ensures the CA on disk records that value.
type SerialFileGenerator struct {
SerialFile string
// lock guards access to the Serial field
lock sync.Mutex
Serial int64
}
func NewSerialFileGenerator(serialFile string) (*SerialFileGenerator, error) {
// read serial file, it must already exist
serial, err := fileToSerial(serialFile)
if err != nil {
return nil, err
}
generator := &SerialFileGenerator{
Serial: serial,
SerialFile: serialFile,
}
// 0 is unused and 1 is reserved for the CA itself
// Thus we need to guarantee that the first external call to SerialFileGenerator.Next returns 2+
// meaning that SerialFileGenerator.Serial must not be less than 1 (it is guaranteed to be non-negative)
if generator.Serial < 1 {
// fake a call to Next so the file stays in sync and Serial is incremented
if _, err := generator.Next(&x509.Certificate{}); err != nil {
return nil, err
}
}
return generator, nil
}
// Next returns a unique, monotonically increasing serial number and ensures the CA on disk records that value.
func (s *SerialFileGenerator) Next(template *x509.Certificate) (int64, error) {
s.lock.Lock()
defer s.lock.Unlock()
// do a best effort check to make sure concurrent external writes are not occurring to the underlying serial file
serial, err := fileToSerial(s.SerialFile)
if err != nil {
return 0, err
}
if serial != s.Serial {
return 0, fmt.Errorf("serial file %s out of sync ram=%d disk=%d", s.SerialFile, s.Serial, serial)
}
next := s.Serial + 1
s.Serial = next
// Output in hex, padded to multiples of two characters for OpenSSL's sake
serialText := fmt.Sprintf("%X", next)
if len(serialText)%2 == 1 {
serialText = "0" + serialText
}
// always add a newline at the end to have a valid file
serialText += "\n"
if err := ioutil.WriteFile(s.SerialFile, []byte(serialText), os.FileMode(0640)); err != nil {
return 0, err
}
return next, nil
}
func fileToSerial(serialFile string) (int64, error) {
serialData, err := ioutil.ReadFile(serialFile)
if err != nil {
return 0, err
}
// read the file as a single hex number after stripping any whitespace
serial, err := strconv.ParseInt(string(bytes.TrimSpace(serialData)), 16, 64)
if err != nil {
return 0, err
}
if serial < 0 {
return 0, fmt.Errorf("invalid negative serial %d in serial file %s", serial, serialFile)
}
return serial, nil
}
// RandomSerialGenerator returns a serial based on time.Now and the subject
type RandomSerialGenerator struct {
}
func (s *RandomSerialGenerator) Next(template *x509.Certificate) (int64, error) {
return randomSerialNumber(), nil
}
// randomSerialNumber returns a random int64 serial number based on
// time.Now. It is defined separately from the generator interface so
// that the caller doesn't have to worry about an input template or
// error - these are unnecessary when creating a random serial.
func randomSerialNumber() int64 {
r := mathrand.New(mathrand.NewSource(time.Now().UTC().UnixNano()))
return r.Int63()
}
// EnsureCA returns a CA, whether it was created (as opposed to pre-existing), and any error
// if serialFile is empty, a RandomSerialGenerator will be used
func EnsureCA(certFile, keyFile, serialFile, name string, expireDays int) (*CA, bool, error) {
if ca, err := GetCA(certFile, keyFile, serialFile); err == nil {
return ca, false, err
}
ca, err := MakeSelfSignedCA(certFile, keyFile, serialFile, name, expireDays)
return ca, true, err
}
// if serialFile is empty, a RandomSerialGenerator will be used
func GetCA(certFile, keyFile, serialFile string) (*CA, error) {
caConfig, err := GetTLSCertificateConfig(certFile, keyFile)
if err != nil {
return nil, err
}
var serialGenerator SerialGenerator
if len(serialFile) > 0 {
serialGenerator, err = NewSerialFileGenerator(serialFile)
if err != nil {
return nil, err
}
} else {
serialGenerator = &RandomSerialGenerator{}
}
return &CA{
SerialGenerator: serialGenerator,
Config: caConfig,
}, nil
}
func GetCAFromBytes(certBytes, keyBytes []byte) (*CA, error) {
caConfig, err := GetTLSCertificateConfigFromBytes(certBytes, keyBytes)
if err != nil {
return nil, err
}
return &CA{
SerialGenerator: &RandomSerialGenerator{},
Config: caConfig,
}, nil
}
// if serialFile is empty, a RandomSerialGenerator will be used
func MakeSelfSignedCA(certFile, keyFile, serialFile, name string, expireDays int) (*CA, error) {
klog.V(2).Infof("Generating new CA for %s cert, and key in %s, %s", name, certFile, keyFile)
caConfig, err := MakeSelfSignedCAConfig(name, expireDays)
if err != nil {
return nil, err
}
if err := caConfig.WriteCertConfigFile(certFile, keyFile); err != nil {
return nil, err
}
var serialGenerator SerialGenerator
if len(serialFile) > 0 {
// create / overwrite the serial file with a zero padded hex value (ending in a newline to have a valid file)
if err := ioutil.WriteFile(serialFile, []byte("00\n"), 0644); err != nil {
return nil, err
}
serialGenerator, err = NewSerialFileGenerator(serialFile)
if err != nil {
return nil, err
}
} else {
serialGenerator = &RandomSerialGenerator{}
}
return &CA{
SerialGenerator: serialGenerator,
Config: caConfig,
}, nil
}
func MakeSelfSignedCAConfig(name string, expireDays int) (*TLSCertificateConfig, error) {
subject := pkix.Name{CommonName: name}
return MakeSelfSignedCAConfigForSubject(subject, expireDays)
}
func MakeSelfSignedCAConfigForSubject(subject pkix.Name, expireDays int) (*TLSCertificateConfig, error) {
var caLifetimeInDays = DefaultCACertificateLifetimeInDays
if expireDays > 0 {
caLifetimeInDays = expireDays
}
if caLifetimeInDays > DefaultCACertificateLifetimeInDays {
warnAboutCertificateLifeTime(subject.CommonName, DefaultCACertificateLifetimeInDays)
}
caLifetime := time.Duration(caLifetimeInDays) * 24 * time.Hour
return makeSelfSignedCAConfigForSubjectAndDuration(subject, caLifetime)
}
func MakeSelfSignedCAConfigForDuration(name string, caLifetime time.Duration) (*TLSCertificateConfig, error) {
subject := pkix.Name{CommonName: name}
return makeSelfSignedCAConfigForSubjectAndDuration(subject, caLifetime)
}
func makeSelfSignedCAConfigForSubjectAndDuration(subject pkix.Name, caLifetime time.Duration) (*TLSCertificateConfig, error) {
// Create CA cert
rootcaPublicKey, rootcaPrivateKey, publicKeyHash, err := newKeyPairWithHash()
if err != nil {
return nil, err
}
// AuthorityKeyId and SubjectKeyId should match for a self-signed CA
authorityKeyId := publicKeyHash
subjectKeyId := publicKeyHash
rootcaTemplate := newSigningCertificateTemplateForDuration(subject, caLifetime, time.Now, authorityKeyId, subjectKeyId)
rootcaCert, err := signCertificate(rootcaTemplate, rootcaPublicKey, rootcaTemplate, rootcaPrivateKey)
if err != nil {
return nil, err
}
caConfig := &TLSCertificateConfig{
Certs: []*x509.Certificate{rootcaCert},
Key: rootcaPrivateKey,
}
return caConfig, nil
}
func MakeCAConfigForDuration(name string, caLifetime time.Duration, issuer *CA) (*TLSCertificateConfig, error) {
// Create CA cert
signerPublicKey, signerPrivateKey, publicKeyHash, err := newKeyPairWithHash()
if err != nil {
return nil, err
}
authorityKeyId := issuer.Config.Certs[0].SubjectKeyId
subjectKeyId := publicKeyHash
signerTemplate := newSigningCertificateTemplateForDuration(pkix.Name{CommonName: name}, caLifetime, time.Now, authorityKeyId, subjectKeyId)
signerCert, err := issuer.signCertificate(signerTemplate, signerPublicKey)
if err != nil {
return nil, err
}
signerConfig := &TLSCertificateConfig{
Certs: append([]*x509.Certificate{signerCert}, issuer.Config.Certs...),
Key: signerPrivateKey,
}
return signerConfig, nil
}
func (ca *CA) EnsureServerCert(certFile, keyFile string, hostnames sets.String, expireDays int) (*TLSCertificateConfig, bool, error) {
certConfig, err := GetServerCert(certFile, keyFile, hostnames)
if err != nil {
certConfig, err = ca.MakeAndWriteServerCert(certFile, keyFile, hostnames, expireDays)
return certConfig, true, err
}
return certConfig, false, nil
}
func GetServerCert(certFile, keyFile string, hostnames sets.String) (*TLSCertificateConfig, error) {
server, err := GetTLSCertificateConfig(certFile, keyFile)
if err != nil {
return nil, err
}
cert := server.Certs[0]
ips, dns := IPAddressesDNSNames(hostnames.List())
missingIps := ipsNotInSlice(ips, cert.IPAddresses)
missingDns := stringsNotInSlice(dns, cert.DNSNames)
if len(missingIps) == 0 && len(missingDns) == 0 {
klog.V(4).Infof("Found existing server certificate in %s", certFile)
return server, nil
}
return nil, fmt.Errorf("Existing server certificate in %s was missing some hostnames (%v) or IP addresses (%v).", certFile, missingDns, missingIps)
}
func (ca *CA) MakeAndWriteServerCert(certFile, keyFile string, hostnames sets.String, expireDays int) (*TLSCertificateConfig, error) {
klog.V(4).Infof("Generating server certificate in %s, key in %s", certFile, keyFile)
server, err := ca.MakeServerCert(hostnames, expireDays)
if err != nil {
return nil, err
}
if err := server.WriteCertConfigFile(certFile, keyFile); err != nil {
return server, err
}
return server, nil
}
// CertificateExtensionFunc is passed a certificate that it may extend, or return an error
// if the extension attempt failed.
type CertificateExtensionFunc func(*x509.Certificate) error
func (ca *CA) MakeServerCert(hostnames sets.String, expireDays int, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash()
authorityKeyId := ca.Config.Certs[0].SubjectKeyId
subjectKeyId := publicKeyHash
serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), expireDays, time.Now, authorityKeyId, subjectKeyId)
for _, fn := range fns {
if err := fn(serverTemplate); err != nil {
return nil, err
}
}
serverCrt, err := ca.signCertificate(serverTemplate, serverPublicKey)
if err != nil {
return nil, err
}
server := &TLSCertificateConfig{
Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...),
Key: serverPrivateKey,
}
return server, nil
}
func (ca *CA) MakeServerCertForDuration(hostnames sets.String, lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash()
authorityKeyId := ca.Config.Certs[0].SubjectKeyId
subjectKeyId := publicKeyHash
serverTemplate := newServerCertificateTemplateForDuration(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), lifetime, time.Now, authorityKeyId, subjectKeyId)
for _, fn := range fns {
if err := fn(serverTemplate); err != nil {
return nil, err
}
}
serverCrt, err := ca.signCertificate(serverTemplate, serverPublicKey)
if err != nil {
return nil, err
}
server := &TLSCertificateConfig{
Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...),
Key: serverPrivateKey,
}
return server, nil
}
func (ca *CA) EnsureClientCertificate(certFile, keyFile string, u user.Info, expireDays int) (*TLSCertificateConfig, bool, error) {
certConfig, err := GetTLSCertificateConfig(certFile, keyFile)
if err != nil {
certConfig, err = ca.MakeClientCertificate(certFile, keyFile, u, expireDays)
return certConfig, true, err // true indicates we wrote the files.
}
return certConfig, false, nil
}
func (ca *CA) MakeClientCertificate(certFile, keyFile string, u user.Info, expireDays int) (*TLSCertificateConfig, error) {
klog.V(4).Infof("Generating client cert in %s and key in %s", certFile, keyFile)
// ensure parent dirs
if err := os.MkdirAll(filepath.Dir(certFile), os.FileMode(0755)); err != nil {
return nil, err
}
if err := os.MkdirAll(filepath.Dir(keyFile), os.FileMode(0755)); err != nil {
return nil, err
}
clientPublicKey, clientPrivateKey, _ := NewKeyPair()
clientTemplate := newClientCertificateTemplate(userToSubject(u), expireDays, time.Now)
clientCrt, err := ca.signCertificate(clientTemplate, clientPublicKey)
if err != nil {
return nil, err
}
certData, err := EncodeCertificates(clientCrt)
if err != nil {
return nil, err
}
keyData, err := encodeKey(clientPrivateKey)
if err != nil {
return nil, err
}
if err = ioutil.WriteFile(certFile, certData, os.FileMode(0644)); err != nil {
return nil, err
}
if err = ioutil.WriteFile(keyFile, keyData, os.FileMode(0600)); err != nil {
return nil, err
}
return GetTLSCertificateConfig(certFile, keyFile)
}
func (ca *CA) MakeClientCertificateForDuration(u user.Info, lifetime time.Duration) (*TLSCertificateConfig, error) {
clientPublicKey, clientPrivateKey, _ := NewKeyPair()
clientTemplate := newClientCertificateTemplateForDuration(userToSubject(u), lifetime, time.Now)
clientCrt, err := ca.signCertificate(clientTemplate, clientPublicKey)
if err != nil {
return nil, err
}
certData, err := EncodeCertificates(clientCrt)
if err != nil {
return nil, err
}
keyData, err := encodeKey(clientPrivateKey)
if err != nil {
return nil, err
}
return GetTLSCertificateConfigFromBytes(certData, keyData)
}
type sortedForDER []string
func (s sortedForDER) Len() int {
return len(s)
}
func (s sortedForDER) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortedForDER) Less(i, j int) bool {
l1 := len(s[i])
l2 := len(s[j])
if l1 == l2 {
return s[i] < s[j]
}
return l1 < l2
}
func userToSubject(u user.Info) pkix.Name {
// Ok we are going to order groups in a peculiar way here to workaround a
// 2 bugs, 1 in golang (https://github.com/golang/go/issues/24254) which
// incorrectly encodes Multivalued RDNs and another in GNUTLS clients
// which are too picky (https://gitlab.com/gnutls/gnutls/issues/403)
// and try to "correct" this issue when reading client certs.
//
// This workaround should be killed once Golang's pkix module is fixed to
// generate a correct DER encoding.
//
// The workaround relies on the fact that the first octect that differs
// between the encoding of two group RDNs will end up being the encoded
// length which is directly related to the group name's length. So we'll
// sort such that shortest names come first.
ugroups := u.GetGroups()
groups := make([]string, len(ugroups))
copy(groups, ugroups)
sort.Sort(sortedForDER(groups))
return pkix.Name{
CommonName: u.GetName(),
SerialNumber: u.GetUID(),
Organization: groups,
}
}
func (ca *CA) signCertificate(template *x509.Certificate, requestKey crypto.PublicKey) (*x509.Certificate, error) {
// Increment and persist serial
serial, err := ca.SerialGenerator.Next(template)
if err != nil {
return nil, err
}
template.SerialNumber = big.NewInt(serial)
return signCertificate(template, requestKey, ca.Config.Certs[0], ca.Config.Key)
}
func NewKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) {
return newRSAKeyPair()
}
func newKeyPairWithHash() (crypto.PublicKey, crypto.PrivateKey, []byte, error) {
publicKey, privateKey, err := newRSAKeyPair()
var publicKeyHash []byte
if err == nil {
hash := sha1.New()
hash.Write(publicKey.N.Bytes())
publicKeyHash = hash.Sum(nil)
}
return publicKey, privateKey, publicKeyHash, err
}
func newRSAKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, keyBits)
if err != nil {
return nil, nil, err
}
return &privateKey.PublicKey, privateKey, nil
}
// Can be used for CA or intermediate signing certs
func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate {
return &x509.Certificate{
Subject: subject,
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: currentTime().Add(-1 * time.Second),
NotAfter: currentTime().Add(caLifetime),
// Specify a random serial number to avoid the same issuer+serial
// number referring to different certs in a chain of trust if the
// signing certificate is ever rotated.
SerialNumber: big.NewInt(randomSerialNumber()),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
IsCA: true,
AuthorityKeyId: authorityKeyId,
SubjectKeyId: subjectKeyId,
}
}
// Can be used for ListenAndServeTLS
func newServerCertificateTemplate(subject pkix.Name, hosts []string, expireDays int, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate {
var lifetimeInDays = DefaultCertificateLifetimeInDays
if expireDays > 0 {
lifetimeInDays = expireDays
}
if lifetimeInDays > DefaultCertificateLifetimeInDays {
warnAboutCertificateLifeTime(subject.CommonName, DefaultCertificateLifetimeInDays)
}
lifetime := time.Duration(lifetimeInDays) * 24 * time.Hour
return newServerCertificateTemplateForDuration(subject, hosts, lifetime, currentTime, authorityKeyId, subjectKeyId)
}
// Can be used for ListenAndServeTLS
func newServerCertificateTemplateForDuration(subject pkix.Name, hosts []string, lifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate {
template := &x509.Certificate{
Subject: subject,
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: currentTime().Add(-1 * time.Second),
NotAfter: currentTime().Add(lifetime),
SerialNumber: big.NewInt(1),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
AuthorityKeyId: authorityKeyId,
SubjectKeyId: subjectKeyId,
}
template.IPAddresses, template.DNSNames = IPAddressesDNSNames(hosts)
return template
}
func IPAddressesDNSNames(hosts []string) ([]net.IP, []string) {
ips := []net.IP{}
dns := []string{}
for _, host := range hosts {
if ip := net.ParseIP(host); ip != nil {
ips = append(ips, ip)
} else {
dns = append(dns, host)
}
}
// Include IP addresses as DNS subjectAltNames in the cert as well, for the sake of Python, Windows (< 10), and unnamed other libraries
// Ensure these technically invalid DNS subjectAltNames occur after the valid ones, to avoid triggering cert errors in Firefox
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1148766
for _, ip := range ips {
dns = append(dns, ip.String())
}
return ips, dns
}
func CertsFromPEM(pemCerts []byte) ([]*x509.Certificate, error) {
ok := false
certs := []*x509.Certificate{}
for len(pemCerts) > 0 {
var block *pem.Block
block, pemCerts = pem.Decode(pemCerts)
if block == nil {
break
}
if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return certs, err
}
certs = append(certs, cert)
ok = true
}
if !ok {
return certs, errors.New("Could not read any certificates")
}
return certs, nil
}
// Can be used as a certificate in http.Transport TLSClientConfig
func newClientCertificateTemplate(subject pkix.Name, expireDays int, currentTime func() time.Time) *x509.Certificate {
var lifetimeInDays = DefaultCertificateLifetimeInDays
if expireDays > 0 {
lifetimeInDays = expireDays
}
if lifetimeInDays > DefaultCertificateLifetimeInDays {
warnAboutCertificateLifeTime(subject.CommonName, DefaultCertificateLifetimeInDays)
}
lifetime := time.Duration(lifetimeInDays) * 24 * time.Hour
return newClientCertificateTemplateForDuration(subject, lifetime, currentTime)
}
// Can be used as a certificate in http.Transport TLSClientConfig
func newClientCertificateTemplateForDuration(subject pkix.Name, lifetime time.Duration, currentTime func() time.Time) *x509.Certificate {
return &x509.Certificate{
Subject: subject,
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: currentTime().Add(-1 * time.Second),
NotAfter: currentTime().Add(lifetime),
SerialNumber: big.NewInt(1),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
}
func warnAboutCertificateLifeTime(name string, defaultLifetimeInDays int) {
defaultLifetimeInYears := defaultLifetimeInDays / 365
fmt.Fprintf(os.Stderr, "WARNING: Validity period of the certificate for %q is greater than %d years!\n", name, defaultLifetimeInYears)
fmt.Fprintln(os.Stderr, "WARNING: By security reasons it is strongly recommended to change this period and make it smaller!")
}
func signCertificate(template *x509.Certificate, requestKey crypto.PublicKey, issuer *x509.Certificate, issuerKey crypto.PrivateKey) (*x509.Certificate, error) {
derBytes, err := x509.CreateCertificate(rand.Reader, template, issuer, requestKey, issuerKey)
if err != nil {
return nil, err
}
certs, err := x509.ParseCertificates(derBytes)
if err != nil {
return nil, err
}
if len(certs) != 1 {
return nil, errors.New("Expected a single certificate")
}
return certs[0], nil
}
func EncodeCertificates(certs ...*x509.Certificate) ([]byte, error) {
b := bytes.Buffer{}
for _, cert := range certs {
if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
return []byte{}, err
}
}
return b.Bytes(), nil
}
func encodeKey(key crypto.PrivateKey) ([]byte, error) {
b := bytes.Buffer{}
switch key := key.(type) {
case *ecdsa.PrivateKey:
keyBytes, err := x509.MarshalECPrivateKey(key)
if err != nil {
return []byte{}, err
}
if err := pem.Encode(&b, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyBytes}); err != nil {
return b.Bytes(), err
}
case *rsa.PrivateKey:
if err := pem.Encode(&b, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
return []byte{}, err
}
default:
return []byte{}, errors.New("Unrecognized key type")
}
return b.Bytes(), nil
}
func writeCertificates(f io.Writer, certs ...*x509.Certificate) error {
bytes, err := EncodeCertificates(certs...)
if err != nil {
return err
}
if _, err := f.Write(bytes); err != nil {
return err
}
return nil
}
func writeKeyFile(f io.Writer, key crypto.PrivateKey) error {
bytes, err := encodeKey(key)
if err != nil {
return err
}
if _, err := f.Write(bytes); err != nil {
return err
}
return nil
}
func stringsNotInSlice(needles []string, haystack []string) []string {
missing := []string{}
for _, needle := range needles {
if !stringInSlice(needle, haystack) {
missing = append(missing, needle)
}
}
return missing
}
func stringInSlice(needle string, haystack []string) bool {
for _, straw := range haystack {
if needle == straw {
return true
}
}
return false
}
func ipsNotInSlice(needles []net.IP, haystack []net.IP) []net.IP {
missing := []net.IP{}
for _, needle := range needles {
if !ipInSlice(needle, haystack) {
missing = append(missing, needle)
}
}
return missing
}
func ipInSlice(needle net.IP, haystack []net.IP) bool {
for _, straw := range haystack {
if needle.Equal(straw) {
return true
}
}
return false
}