blob: 837ad98d66ca55812d97631236f083be188cd4dc [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Serge Bazanskibee272f2022-09-13 13:52:42 +02004package component
5
6import (
7 "crypto/ed25519"
8 "crypto/rand"
9 "crypto/tls"
10 "crypto/x509"
11 "encoding/pem"
12 "fmt"
13 "math/big"
14 "os"
15 "time"
16
17 "k8s.io/klog/v2"
18
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020019 "source.monogon.dev/osbase/pki"
Serge Bazanskibee272f2022-09-13 13:52:42 +020020)
21
22// GetDevCerts returns paths to this component's development certificate, key
23// and CA, or panics if unavailable.
Serge Bazanskia5baa872022-09-15 18:49:35 +020024func (c *ComponentConfig) GetDevCerts() (certPath, keyPath, caPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020025 klog.Infof("Using developer certificates at %s", c.DevCertsPath)
26
27 caPath = c.ensureDevCA()
28 certPath, keyPath = c.ensureDevComponent()
29 return
30}
31
32// ensureDevComponent ensures that a development certificate/key exists for this
33// component and returns paths to them. This data is either read from disk if it
34// already exists, or is generated when this function is called. If any problem
35// occurs, the code panics.
Serge Bazanskia5baa872022-09-15 18:49:35 +020036func (c *ComponentConfig) ensureDevComponent() (certPath, keyPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020037 caKeyPath := c.DevCertsPath + "/ca.key"
38 caCertPath := c.DevCertsPath + "/ca.cert"
39
40 // Load CA. By convention, we are always called after ensureDevCA.
41 ca, err := tls.LoadX509KeyPair(caCertPath, caKeyPath)
42 if err != nil {
43 klog.Exitf("Could not load Dev CA: %v", err)
44 }
45 caCert, err := x509.ParseCertificate(ca.Certificate[0])
46 if err != nil {
47 klog.Exitf("Could not parse Dev CA: %v", err)
48 }
49
50 // Check if we have keys already.
51 keyPath = c.DevCertsPath + fmt.Sprintf("/%s.key", c.ComponentName)
52 certPath = c.DevCertsPath + fmt.Sprintf("/%s.crt", c.ComponentName)
53 noKey := false
54 if _, err := os.Stat(keyPath); os.IsNotExist(err) {
55 noKey = true
56 }
57 noCert := false
58 if _, err := os.Stat(certPath); os.IsNotExist(err) {
59 noCert = true
60 }
61
62 if noKey || noCert {
63 klog.Infof("Generating developer %s certificate...", c.ComponentName)
64 } else {
65 return
66 }
67
68 // Generate key/certificate.
69 cert := pki.Server([]string{
70 fmt.Sprintf("%s.local", c.ComponentName),
71 }, nil)
72
73 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
74 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
75 if err != nil {
76 klog.Exitf("Failed to generate %s serial number: %v", c.ComponentName, err)
77 }
Serge Bazanski26c18172023-04-24 17:40:48 +020078 cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
Serge Bazanskibee272f2022-09-13 13:52:42 +020079 cert.SerialNumber = serialNumber
80 cert.NotBefore = time.Now()
81 cert.NotAfter = pki.UnknownNotAfter
82 cert.BasicConstraintsValid = true
83
84 pub, priv, err := ed25519.GenerateKey(rand.Reader)
85 if err != nil {
86 klog.Exitf("Failed to generate %s key: %v", c.ComponentName, err)
87 }
88 certBytes, err := x509.CreateCertificate(rand.Reader, &cert, caCert, pub, ca.PrivateKey)
89 if err != nil {
90 klog.Exitf("Failed to generate %s certificate: %v", c.ComponentName, err)
91 }
92
93 // And marshal them to disk.
94 privPKCS, err := x509.MarshalPKCS8PrivateKey(priv)
95 if err != nil {
96 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
97 }
98 err = os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{
99 Type: "PRIVATE KEY",
100 Bytes: privPKCS,
101 }), 0600)
102 if err != nil {
103 klog.Exitf("Failed to write %s private key: %v", c.ComponentName, err)
104 }
105 err = os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{
106 Type: "CERTIFICATE",
107 Bytes: certBytes,
108 }), 0644)
109 if err != nil {
110 klog.Exitf("Failed to write %s certificate: %v", c.ComponentName, err)
111 }
112
113 return
114}
115
116// ensureDevCA ensures that a development CA certificate/key exists and returns
117// paths to them. This data is either read from disk if it already exists, or is
118// generated when this function is called. If any problem occurs, the code
119// panics.
Serge Bazanskia5baa872022-09-15 18:49:35 +0200120func (c *ComponentConfig) ensureDevCA() (caCertPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +0200121 caKeyPath := c.DevCertsPath + "/ca.key"
122 caCertPath = c.DevCertsPath + "/ca.cert"
123
124 if err := os.MkdirAll(c.DevCertsPath, 0700); err != nil {
125 klog.Exitf("Failed to make developer certificate directory: %v", err)
126 }
127
128 // Check if we already have a key/certificate.
129 noKey := false
130 if _, err := os.Stat(caKeyPath); os.IsNotExist(err) {
131 noKey = true
132 }
133 noCert := false
134 if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
135 noCert = true
136 }
137
138 if noKey || noCert {
139 klog.Infof("Generating developer CA certificate...")
140 } else {
141 return
142 }
143 hostname, err := os.Hostname()
144 if err != nil {
145 hostname = "unknown"
146 }
147
148 // No key/certificate, generate them.
149 ca := pki.CA(fmt.Sprintf("monogon dev certs CA (%s)", hostname))
150
151 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
152 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
153 if err != nil {
154 klog.Exitf("Failed to generate CA serial number: %v", err)
155 }
156 ca.SerialNumber = serialNumber
157 ca.NotBefore = time.Now()
158 ca.NotAfter = pki.UnknownNotAfter
159 ca.BasicConstraintsValid = true
160
161 caPub, caPriv, err := ed25519.GenerateKey(rand.Reader)
162 if err != nil {
163 klog.Exitf("Failed to generate CA key: %v", err)
164 }
165 caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, caPub, caPriv)
166 if err != nil {
167 klog.Exitf("Failed to generate CA certificate: %v", err)
168 }
169
170 // And marshal them to disk.
171 caPrivPKCS, err := x509.MarshalPKCS8PrivateKey(caPriv)
172 if err != nil {
173 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
174 }
175 err = os.WriteFile(caKeyPath, pem.EncodeToMemory(&pem.Block{
176 Type: "PRIVATE KEY",
177 Bytes: caPrivPKCS,
178 }), 0600)
179 if err != nil {
180 klog.Exitf("Failed to write CA private key: %v", err)
181 }
182 err = os.WriteFile(caCertPath, pem.EncodeToMemory(&pem.Block{
183 Type: "CERTIFICATE",
184 Bytes: caBytes,
185 }), 0644)
186 if err != nil {
187 klog.Exitf("Failed to write CA certificate: %v", err)
188 }
189
190 return
191}