1package internal 2 3// NB(directxman12): nothing has verified that this has good settings. In fact, 4// the setting generated here are probably terrible, but they're fine for integration 5// tests. These ABSOLUTELY SHOULD NOT ever be exposed in the public API. They're 6// ONLY for use with envtest's ability to configure webhook testing. 7// If I didn't otherwise not want to add a dependency on cfssl, I'd just use that. 8 9import ( 10 "crypto" 11 crand "crypto/rand" 12 "crypto/rsa" 13 "crypto/x509" 14 "crypto/x509/pkix" 15 "encoding/pem" 16 "fmt" 17 "math/big" 18 "net" 19 "time" 20 21 certutil "k8s.io/client-go/util/cert" 22) 23 24var ( 25 rsaKeySize = 2048 // a decent number, as of 2019 26 bigOne = big.NewInt(1) 27) 28 29// CertPair is a private key and certificate for use for client auth, as a CA, or serving. 30type CertPair struct { 31 Key crypto.Signer 32 Cert *x509.Certificate 33} 34 35// CertBytes returns the PEM-encoded version of the certificate for this pair. 36func (k CertPair) CertBytes() []byte { 37 return pem.EncodeToMemory(&pem.Block{ 38 Type: "CERTIFICATE", 39 Bytes: k.Cert.Raw, 40 }) 41} 42 43// AsBytes encodes keypair in the appropriate formats for on-disk storage (PEM and 44// PKCS8, respectively). 45func (k CertPair) AsBytes() (cert []byte, key []byte, err error) { 46 cert = k.CertBytes() 47 48 rawKeyData, err := x509.MarshalPKCS8PrivateKey(k.Key) 49 if err != nil { 50 return nil, nil, fmt.Errorf("unable to encode private key: %v", err) 51 } 52 53 key = pem.EncodeToMemory(&pem.Block{ 54 Type: "PRIVATE KEY", 55 Bytes: rawKeyData, 56 }) 57 58 return cert, key, nil 59} 60 61// TinyCA supports signing serving certs and client-certs, 62// and can be used as an auth mechanism with envtest. 63type TinyCA struct { 64 CA CertPair 65 orgName string 66 67 nextSerial *big.Int 68} 69 70// newPrivateKey generates a new private key of a relatively sane size (see 71// rsaKeySize). 72func newPrivateKey() (crypto.Signer, error) { 73 return rsa.GenerateKey(crand.Reader, rsaKeySize) 74} 75 76func NewTinyCA() (*TinyCA, error) { 77 caPrivateKey, err := newPrivateKey() 78 if err != nil { 79 return nil, fmt.Errorf("unable to generate private key for CA: %v", err) 80 } 81 caCfg := certutil.Config{CommonName: "envtest-environment", Organization: []string{"envtest"}} 82 caCert, err := certutil.NewSelfSignedCACert(caCfg, caPrivateKey) 83 if err != nil { 84 return nil, fmt.Errorf("unable to generate certificate for CA: %v", err) 85 } 86 87 return &TinyCA{ 88 CA: CertPair{Key: caPrivateKey, Cert: caCert}, 89 orgName: "envtest", 90 nextSerial: big.NewInt(1), 91 }, nil 92} 93 94func (c *TinyCA) makeCert(cfg certutil.Config) (CertPair, error) { 95 now := time.Now() 96 97 key, err := newPrivateKey() 98 if err != nil { 99 return CertPair{}, fmt.Errorf("unable to create private key: %v", err) 100 } 101 102 serial := new(big.Int).Set(c.nextSerial) 103 c.nextSerial.Add(c.nextSerial, bigOne) 104 105 template := x509.Certificate{ 106 Subject: pkix.Name{CommonName: cfg.CommonName, Organization: cfg.Organization}, 107 DNSNames: cfg.AltNames.DNSNames, 108 IPAddresses: cfg.AltNames.IPs, 109 SerialNumber: serial, 110 111 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 112 ExtKeyUsage: cfg.Usages, 113 114 // technically not necessary for testing, but let's set anyway just in case. 115 NotBefore: now.UTC(), 116 // 1 week -- the default for cfssl, and just long enough for a 117 // long-term test, but not too long that anyone would try to use this 118 // seriously. 119 NotAfter: now.Add(168 * time.Hour).UTC(), 120 } 121 122 certRaw, err := x509.CreateCertificate(crand.Reader, &template, c.CA.Cert, key.Public(), c.CA.Key) 123 if err != nil { 124 return CertPair{}, fmt.Errorf("unable to create certificate: %v", err) 125 } 126 127 cert, err := x509.ParseCertificate(certRaw) 128 if err != nil { 129 return CertPair{}, fmt.Errorf("generated invalid certificate, could not parse: %v", err) 130 } 131 132 return CertPair{ 133 Key: key, 134 Cert: cert, 135 }, nil 136} 137 138// NewServingCert returns a new CertPair for a serving HTTPS on localhost. 139func (c *TinyCA) NewServingCert() (CertPair, error) { 140 return c.makeCert(certutil.Config{ 141 CommonName: "localhost", 142 Organization: []string{c.orgName}, 143 AltNames: certutil.AltNames{ 144 DNSNames: []string{"localhost"}, 145 IPs: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, 146 }, 147 Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 148 }) 149} 150