/
pki.go
157 lines (126 loc) · 4.46 KB
/
pki.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors
// Package pki provides a simple way to generate a CA and signed server keypair.
package pki
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"time"
"github.com/defenseunicorns/pkg/helpers"
"github.com/defenseunicorns/zarf/src/pkg/k8s"
"github.com/defenseunicorns/zarf/src/pkg/message"
)
// Based off of https://github.com/dmcgowan/quicktls/blob/master/main.go
// Use 2048 because we are aiming for low-resource / max-compatibility.
const rsaBits = 2048
const org = "Zarf Cluster"
// 13 months is the max length allowed by browsers.
const validFor = time.Hour * 24 * 375
// GeneratePKI create a CA and signed server keypair.
func GeneratePKI(host string, dnsNames ...string) k8s.GeneratedPKI {
results := k8s.GeneratedPKI{}
ca, caKey, err := generateCA(validFor)
if err != nil {
message.Fatal(err, "Unable to generate the ephemeral CA")
}
hostCert, hostKey, err := generateCert(host, ca, caKey, validFor, dnsNames...)
if err != nil {
message.Fatalf(err, "Unable to generate the cert for %s", host)
}
results.CA = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: ca.Raw,
})
results.Cert = pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: hostCert.Raw,
})
results.Key = pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(hostKey),
})
return results
}
// newCertificate creates a new template.
func newCertificate(validFor time.Duration) *x509.Certificate {
notBefore := time.Now()
notAfter := notBefore.Add(validFor)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
message.Fatalf(err, "failed to generate the certificate serial number")
}
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{org},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}
}
// newPrivateKey creates a new private key.
func newPrivateKey() (*rsa.PrivateKey, error) {
return rsa.GenerateKey(rand.Reader, rsaBits)
}
// generateCA creates a new CA certificate, saves the certificate
// and returns the x509 certificate and crypto private key. This
// private key should never be saved to disk, but rather used to
// immediately generate further certificates.
func generateCA(validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) {
template := newCertificate(validFor)
template.IsCA = true
template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
template.Subject.CommonName = "ca.private.zarf.dev"
template.Subject.Organization = []string{"Zarf Community"}
priv, err := newPrivateKey()
if err != nil {
return nil, nil, err
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, priv.Public(), priv)
if err != nil {
return nil, nil, err
}
ca, err := x509.ParseCertificate(derBytes)
if err != nil {
return nil, nil, err
}
return ca, priv, nil
}
// generateCert generates a new certificate for the given host using the
// provided certificate authority. The cert and key files are stored in
// the provided files.
func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, validFor time.Duration, dnsNames ...string) (*x509.Certificate, *rsa.PrivateKey, error) {
template := newCertificate(validFor)
template.IPAddresses = append(template.IPAddresses, net.ParseIP(helpers.IPV4Localhost))
// Only use SANs to keep golang happy, https://go-review.googlesource.com/c/go/+/231379
if ip := net.ParseIP(host); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, host)
template.DNSNames = append(template.DNSNames, dnsNames...)
}
template.Subject.CommonName = host
privateKey, err := newPrivateKey()
if err != nil {
return nil, nil, err
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, ca, privateKey.Public(), caKey)
if err != nil {
return nil, nil, err
}
cert, err := x509.ParseCertificate(derBytes)
if err != nil {
return nil, nil, err
}
return cert, privateKey, nil
}