k8s.io/client-go@v0.31.1/util/cert/cert.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cert
    18  
    19  import (
    20  	"bytes"
    21  	"crypto"
    22  	cryptorand "crypto/rand"
    23  	"crypto/rsa"
    24  	"crypto/x509"
    25  	"crypto/x509/pkix"
    26  	"encoding/pem"
    27  	"fmt"
    28  	"math"
    29  	"math/big"
    30  	"net"
    31  	"os"
    32  	"path/filepath"
    33  	"strings"
    34  	"time"
    35  
    36  	"k8s.io/client-go/util/keyutil"
    37  	netutils "k8s.io/utils/net"
    38  )
    39  
    40  const duration365d = time.Hour * 24 * 365
    41  
    42  // Config contains the basic fields required for creating a certificate
    43  type Config struct {
    44  	CommonName   string
    45  	Organization []string
    46  	AltNames     AltNames
    47  	Usages       []x509.ExtKeyUsage
    48  	NotBefore    time.Time
    49  }
    50  
    51  // AltNames contains the domain names and IP addresses that will be added
    52  // to the API Server's x509 certificate SubAltNames field. The values will
    53  // be passed directly to the x509.Certificate object.
    54  type AltNames struct {
    55  	DNSNames []string
    56  	IPs      []net.IP
    57  }
    58  
    59  // NewSelfSignedCACert creates a CA certificate
    60  func NewSelfSignedCACert(cfg Config, key crypto.Signer) (*x509.Certificate, error) {
    61  	now := time.Now()
    62  	// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
    63  	serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	serial = new(big.Int).Add(serial, big.NewInt(1))
    68  	notBefore := now.UTC()
    69  	if !cfg.NotBefore.IsZero() {
    70  		notBefore = cfg.NotBefore.UTC()
    71  	}
    72  	tmpl := x509.Certificate{
    73  		SerialNumber: serial,
    74  		Subject: pkix.Name{
    75  			CommonName:   cfg.CommonName,
    76  			Organization: cfg.Organization,
    77  		},
    78  		DNSNames:              []string{cfg.CommonName},
    79  		NotBefore:             notBefore,
    80  		NotAfter:              now.Add(duration365d * 10).UTC(),
    81  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
    82  		BasicConstraintsValid: true,
    83  		IsCA:                  true,
    84  	}
    85  
    86  	certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	return x509.ParseCertificate(certDERBytes)
    91  }
    92  
    93  // GenerateSelfSignedCertKey creates a self-signed certificate and key for the given host.
    94  // Host may be an IP or a DNS name
    95  // You may also specify additional subject alt names (either ip or dns names) for the certificate.
    96  func GenerateSelfSignedCertKey(host string, alternateIPs []net.IP, alternateDNS []string) ([]byte, []byte, error) {
    97  	return GenerateSelfSignedCertKeyWithFixtures(host, alternateIPs, alternateDNS, "")
    98  }
    99  
   100  // GenerateSelfSignedCertKeyWithFixtures creates a self-signed certificate and key for the given host.
   101  // Host may be an IP or a DNS name. You may also specify additional subject alt names (either ip or dns names)
   102  // for the certificate.
   103  //
   104  // If fixtureDirectory is non-empty, it is a directory path which can contain pre-generated certs. The format is:
   105  // <host>_<ip>-<ip>_<alternateDNS>-<alternateDNS>.crt
   106  // <host>_<ip>-<ip>_<alternateDNS>-<alternateDNS>.key
   107  // Certs/keys not existing in that directory are created.
   108  func GenerateSelfSignedCertKeyWithFixtures(host string, alternateIPs []net.IP, alternateDNS []string, fixtureDirectory string) ([]byte, []byte, error) {
   109  	validFrom := time.Now().Add(-time.Hour) // valid an hour earlier to avoid flakes due to clock skew
   110  	maxAge := time.Hour * 24 * 365          // one year self-signed certs
   111  
   112  	baseName := fmt.Sprintf("%s_%s_%s", host, strings.Join(ipsToStrings(alternateIPs), "-"), strings.Join(alternateDNS, "-"))
   113  	certFixturePath := filepath.Join(fixtureDirectory, baseName+".crt")
   114  	keyFixturePath := filepath.Join(fixtureDirectory, baseName+".key")
   115  	if len(fixtureDirectory) > 0 {
   116  		cert, err := os.ReadFile(certFixturePath)
   117  		if err == nil {
   118  			key, err := os.ReadFile(keyFixturePath)
   119  			if err == nil {
   120  				return cert, key, nil
   121  			}
   122  			return nil, nil, fmt.Errorf("cert %s can be read, but key %s cannot: %v", certFixturePath, keyFixturePath, err)
   123  		}
   124  		maxAge = 100 * time.Hour * 24 * 365 // 100 years fixtures
   125  	}
   126  
   127  	caKey, err := rsa.GenerateKey(cryptorand.Reader, 2048)
   128  	if err != nil {
   129  		return nil, nil, err
   130  	}
   131  	// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
   132  	serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
   133  	if err != nil {
   134  		return nil, nil, err
   135  	}
   136  	serial = new(big.Int).Add(serial, big.NewInt(1))
   137  	caTemplate := x509.Certificate{
   138  		SerialNumber: serial,
   139  		Subject: pkix.Name{
   140  			CommonName: fmt.Sprintf("%s-ca@%d", host, time.Now().Unix()),
   141  		},
   142  		NotBefore: validFrom,
   143  		NotAfter:  validFrom.Add(maxAge),
   144  
   145  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   146  		BasicConstraintsValid: true,
   147  		IsCA:                  true,
   148  	}
   149  
   150  	caDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
   151  	if err != nil {
   152  		return nil, nil, err
   153  	}
   154  
   155  	caCertificate, err := x509.ParseCertificate(caDERBytes)
   156  	if err != nil {
   157  		return nil, nil, err
   158  	}
   159  
   160  	priv, err := rsa.GenerateKey(cryptorand.Reader, 2048)
   161  	if err != nil {
   162  		return nil, nil, err
   163  	}
   164  	// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
   165  	serial, err = cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
   166  	if err != nil {
   167  		return nil, nil, err
   168  	}
   169  	serial = new(big.Int).Add(serial, big.NewInt(1))
   170  	template := x509.Certificate{
   171  		SerialNumber: serial,
   172  		Subject: pkix.Name{
   173  			CommonName: fmt.Sprintf("%s@%d", host, time.Now().Unix()),
   174  		},
   175  		NotBefore: validFrom,
   176  		NotAfter:  validFrom.Add(maxAge),
   177  
   178  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   179  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   180  		BasicConstraintsValid: true,
   181  	}
   182  
   183  	if ip := netutils.ParseIPSloppy(host); ip != nil {
   184  		template.IPAddresses = append(template.IPAddresses, ip)
   185  	} else {
   186  		template.DNSNames = append(template.DNSNames, host)
   187  	}
   188  
   189  	template.IPAddresses = append(template.IPAddresses, alternateIPs...)
   190  	template.DNSNames = append(template.DNSNames, alternateDNS...)
   191  
   192  	derBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, caCertificate, &priv.PublicKey, caKey)
   193  	if err != nil {
   194  		return nil, nil, err
   195  	}
   196  
   197  	// Generate cert, followed by ca
   198  	certBuffer := bytes.Buffer{}
   199  	if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: derBytes}); err != nil {
   200  		return nil, nil, err
   201  	}
   202  	if err := pem.Encode(&certBuffer, &pem.Block{Type: CertificateBlockType, Bytes: caDERBytes}); err != nil {
   203  		return nil, nil, err
   204  	}
   205  
   206  	// Generate key
   207  	keyBuffer := bytes.Buffer{}
   208  	if err := pem.Encode(&keyBuffer, &pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
   209  		return nil, nil, err
   210  	}
   211  
   212  	if len(fixtureDirectory) > 0 {
   213  		if err := os.WriteFile(certFixturePath, certBuffer.Bytes(), 0644); err != nil {
   214  			return nil, nil, fmt.Errorf("failed to write cert fixture to %s: %v", certFixturePath, err)
   215  		}
   216  		if err := os.WriteFile(keyFixturePath, keyBuffer.Bytes(), 0600); err != nil {
   217  			return nil, nil, fmt.Errorf("failed to write key fixture to %s: %v", certFixturePath, err)
   218  		}
   219  	}
   220  
   221  	return certBuffer.Bytes(), keyBuffer.Bytes(), nil
   222  }
   223  
   224  func ipsToStrings(ips []net.IP) []string {
   225  	ss := make([]string, 0, len(ips))
   226  	for _, ip := range ips {
   227  		ss = append(ss, ip.String())
   228  	}
   229  	return ss
   230  }