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