1
16
17 package certs
18
19
20
21
22
23
24
25 import (
26 "crypto"
27 "crypto/ecdsa"
28 "crypto/elliptic"
29 crand "crypto/rand"
30 "crypto/x509"
31 "crypto/x509/pkix"
32 "encoding/pem"
33 "fmt"
34 "math/big"
35 "net"
36 "time"
37
38 certutil "k8s.io/client-go/util/cert"
39 )
40
41 var (
42 ellipticCurve = elliptic.P256()
43 bigOne = big.NewInt(1)
44 )
45
46
47 type CertPair struct {
48 Key crypto.Signer
49 Cert *x509.Certificate
50 }
51
52
53 func (k CertPair) CertBytes() []byte {
54 return pem.EncodeToMemory(&pem.Block{
55 Type: "CERTIFICATE",
56 Bytes: k.Cert.Raw,
57 })
58 }
59
60
61
62 func (k CertPair) AsBytes() (cert []byte, key []byte, err error) {
63 cert = k.CertBytes()
64
65 rawKeyData, err := x509.MarshalPKCS8PrivateKey(k.Key)
66 if err != nil {
67 return nil, nil, fmt.Errorf("unable to encode private key: %w", err)
68 }
69
70 key = pem.EncodeToMemory(&pem.Block{
71 Type: "PRIVATE KEY",
72 Bytes: rawKeyData,
73 })
74
75 return cert, key, nil
76 }
77
78
79
80 type TinyCA struct {
81 CA CertPair
82 orgName string
83
84 nextSerial *big.Int
85 }
86
87
88
89 func newPrivateKey() (crypto.Signer, error) {
90 return ecdsa.GenerateKey(ellipticCurve, crand.Reader)
91 }
92
93
94
95 func NewTinyCA() (*TinyCA, error) {
96 caPrivateKey, err := newPrivateKey()
97 if err != nil {
98 return nil, fmt.Errorf("unable to generate private key for CA: %w", err)
99 }
100 caCfg := certutil.Config{CommonName: "envtest-environment", Organization: []string{"envtest"}}
101 caCert, err := certutil.NewSelfSignedCACert(caCfg, caPrivateKey)
102 if err != nil {
103 return nil, fmt.Errorf("unable to generate certificate for CA: %w", err)
104 }
105
106 return &TinyCA{
107 CA: CertPair{Key: caPrivateKey, Cert: caCert},
108 orgName: "envtest",
109 nextSerial: big.NewInt(1),
110 }, nil
111 }
112
113 func (c *TinyCA) makeCert(cfg certutil.Config) (CertPair, error) {
114 now := time.Now()
115
116 key, err := newPrivateKey()
117 if err != nil {
118 return CertPair{}, fmt.Errorf("unable to create private key: %w", err)
119 }
120
121 serial := new(big.Int).Set(c.nextSerial)
122 c.nextSerial.Add(c.nextSerial, bigOne)
123
124 template := x509.Certificate{
125 Subject: pkix.Name{CommonName: cfg.CommonName, Organization: cfg.Organization},
126 DNSNames: cfg.AltNames.DNSNames,
127 IPAddresses: cfg.AltNames.IPs,
128 SerialNumber: serial,
129
130 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
131 ExtKeyUsage: cfg.Usages,
132
133
134 NotBefore: now.UTC(),
135
136
137
138 NotAfter: now.Add(168 * time.Hour).UTC(),
139 }
140
141 certRaw, err := x509.CreateCertificate(crand.Reader, &template, c.CA.Cert, key.Public(), c.CA.Key)
142 if err != nil {
143 return CertPair{}, fmt.Errorf("unable to create certificate: %w", err)
144 }
145
146 cert, err := x509.ParseCertificate(certRaw)
147 if err != nil {
148 return CertPair{}, fmt.Errorf("generated invalid certificate, could not parse: %w", err)
149 }
150
151 return CertPair{
152 Key: key,
153 Cert: cert,
154 }, nil
155 }
156
157
158 func (c *TinyCA) NewServingCert(names ...string) (CertPair, error) {
159 if len(names) == 0 {
160 names = []string{"localhost"}
161 }
162 dnsNames, ips, err := resolveNames(names)
163 if err != nil {
164 return CertPair{}, err
165 }
166
167 return c.makeCert(certutil.Config{
168 CommonName: "localhost",
169 Organization: []string{c.orgName},
170 AltNames: certutil.AltNames{
171 DNSNames: dnsNames,
172 IPs: ips,
173 },
174 Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
175 })
176 }
177
178
179
180 type ClientInfo struct {
181
182 Name string
183
184
185 Groups []string
186 }
187
188
189
190 func (c *TinyCA) NewClientCert(user ClientInfo) (CertPair, error) {
191 return c.makeCert(certutil.Config{
192 CommonName: user.Name,
193 Organization: user.Groups,
194 Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
195 })
196 }
197
198 func resolveNames(names []string) ([]string, []net.IP, error) {
199 dnsNames := []string{}
200 ips := []net.IP{}
201 for _, name := range names {
202 if name == "" {
203 continue
204 }
205 ip := net.ParseIP(name)
206 if ip == nil {
207 dnsNames = append(dnsNames, name)
208
209 nameIPs, err := net.LookupHost(name)
210 if err != nil {
211 return nil, nil, err
212 }
213 for _, nameIP := range nameIPs {
214 ip = net.ParseIP(nameIP)
215 if ip != nil {
216 ips = append(ips, ip)
217 }
218 }
219 } else {
220 ips = append(ips, ip)
221 }
222 }
223 return dnsNames, ips, nil
224 }
225
View as plain text