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
package marketnetwork
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/hex"
"encoding/pem"
"math/big"
"os"
"path/filepath"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/api/v1alpha1"
"github.com/johnandersen777/socialweb-computer-kcp/internal/app/compose"
)
const (
GuestTLSDir = "guest-tls"
GuestTLSHost = v1alpha1.GuestTLSHost
guestTLSDays = 30
guestCAFile = "ca.crt"
guestCAKeyFile = "ca.key"
guestLeafFile = GuestTLSHost + ".crt"
guestLeafKey = GuestTLSHost + ".key"
keysDir = "keys"
hexKeyBytes = 32
certRenewBefore = 24 * time.Hour
)
func pemBlock(kind string, der []byte) []byte {
return pem.EncodeToMemory(&pem.Block{Type: kind, Bytes: der})
}
func serial() (*big.Int, error) {
return rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 120))
}
func certValid(path string) bool {
b, err := os.ReadFile(path)
if err != nil {
return false
}
blk, _ := pem.Decode(b)
if blk == nil {
return false
}
c, err := x509.ParseCertificate(blk.Bytes)
return err == nil && time.Until(c.NotAfter) > certRenewBefore
}
func EnsureGuestTLS(dir string) error {
if certValid(filepath.Join(dir, guestLeafFile)) && certValid(filepath.Join(dir, guestCAFile)) {
return nil
}
if err := os.MkdirAll(dir, 0o700); err != nil {
return err
}
now := time.Now()
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
sn, err := serial()
if err != nil {
return err
}
caTmpl := &x509.Certificate{SerialNumber: sn, Subject: pkix.Name{CommonName: "market network guest CA"},
NotBefore: now.Add(-time.Minute), NotAfter: now.AddDate(0, 0, guestTLSDays), IsCA: true, BasicConstraintsValid: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign}
caDER, err := x509.CreateCertificate(rand.Reader, caTmpl, caTmpl, &caKey.PublicKey, caKey)
if err != nil {
return err
}
ca, err := x509.ParseCertificate(caDER)
if err != nil {
return err
}
leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return err
}
if sn, err = serial(); err != nil {
return err
}
leafTmpl := &x509.Certificate{SerialNumber: sn, Subject: pkix.Name{CommonName: GuestTLSHost},
NotBefore: now.Add(-time.Minute), NotAfter: now.AddDate(0, 0, guestTLSDays), DNSNames: []string{GuestTLSHost, "*." + GuestTLSHost},
KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true}
leafDER, err := x509.CreateCertificate(rand.Reader, leafTmpl, ca, &leafKey.PublicKey, caKey)
if err != nil {
return err
}
caKeyDER, err := x509.MarshalPKCS8PrivateKey(caKey)
if err != nil {
return err
}
leafKeyDER, err := x509.MarshalPKCS8PrivateKey(leafKey)
if err != nil {
return err
}
for name, data := range map[string][]byte{guestCAKeyFile: pemBlock("PRIVATE KEY", caKeyDER), guestLeafKey: pemBlock("PRIVATE KEY", leafKeyDER)} {
if err := compose.WriteSecret(filepath.Join(dir, name), data); err != nil {
return err
}
}
for name, data := range map[string][]byte{guestCAFile: pemBlock("CERTIFICATE", caDER), guestLeafFile: pemBlock("CERTIFICATE", leafDER)} {
if err := os.WriteFile(filepath.Join(dir, name), data, 0o644); err != nil {
return err
}
}
return nil
}
func EnsureHexKey(path string) (string, error) {
if b, err := os.ReadFile(path); err == nil && len(b) == 2*hexKeyBytes {
return string(b), nil
}
buf := make([]byte, hexKeyBytes)
if _, err := rand.Read(buf); err != nil {
return "", err
}
k := hex.EncodeToString(buf)
return k, compose.WriteSecret(path, []byte(k))
}