1/*
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}
207