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
package vaultcert
import (
"bytes"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"path/filepath"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/internal/rootvault"
)
const (
OpenBaoClientsCACommonName = "socialweb-openbao-clients-ca"
OpenBaoClientCommonNamePrefix = "socialweb-openbao-client-"
openBaoClientDirMode = 0o700
openBaoClientMaterialFileMode = 0o600
)
type OpenBaoClientsCA struct {
CAPEM []byte
BundlePath string
key *ecdsa.PrivateKey
cert *x509.Certificate
}
type OpenBaoClientTLS struct {
CommonName string
CertFile string
KeyFile string
}
func OpenBaoClientCommonName(name string) string { return OpenBaoClientCommonNamePrefix + name }
func EnsureOpenBaoClientsCA(stateDir string, rootCAPEM []byte) (OpenBaoClientsCA, error) {
if stateDir == "" {
return OpenBaoClientsCA{}, ErrNoRootVaultTLSStateDir
}
caPath, keyPath := rootvault.ClientsCAPath(stateDir), rootvault.ClientsCAKeyPath(stateDir)
ca := OpenBaoClientsCA{BundlePath: rootvault.ClientCABundlePath(stateDir)}
caPEM, cert, ok := vaultTLSCAOnDisk(VaultTLS{CAPath: caPath})
var key *ecdsa.PrivateKey
if ok {
key, ok = vaultTLSCAKeyOnDisk(cert, VaultTLS{CAKeyPath: keyPath})
}
if !ok {
var der []byte
var err error
key, cert, der, err = newNamedCA(time.Now(), OpenBaoClientsCACommonName)
if err != nil {
return OpenBaoClientsCA{}, err
}
caPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
keyDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return OpenBaoClientsCA{}, fmt.Errorf("vaultcert: marshalling the OpenBao clients CA key: %w", err)
}
if err := os.MkdirAll(filepath.Dir(caPath), openBaoClientDirMode); err != nil {
return OpenBaoClientsCA{}, err
}
if err := os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), openBaoClientMaterialFileMode); err != nil {
return OpenBaoClientsCA{}, fmt.Errorf("vaultcert: writing %s: %w", keyPath, err)
}
if err := os.WriteFile(caPath, caPEM, openBaoClientMaterialFileMode); err != nil {
return OpenBaoClientsCA{}, fmt.Errorf("vaultcert: writing %s: %w", caPath, err)
}
}
bundle := append(append(append([]byte{}, bytes.TrimSpace(rootCAPEM)...), '\n'), caPEM...)
if cur, err := os.ReadFile(ca.BundlePath); err != nil || !bytes.Equal(cur, bundle) {
if err := os.WriteFile(ca.BundlePath, bundle, openBaoClientMaterialFileMode); err != nil {
return OpenBaoClientsCA{}, fmt.Errorf("vaultcert: writing %s: %w", ca.BundlePath, err)
}
}
ca.CAPEM, ca.key, ca.cert = caPEM, key, cert
return ca, nil
}
func (ca OpenBaoClientsCA) EnsureClient(stateDir, name string) (OpenBaoClientTLS, error) {
out := OpenBaoClientTLS{CommonName: OpenBaoClientCommonName(name), CertFile: rootvault.AppClientCertPath(stateDir, name),
KeyFile: rootvault.AppClientKeyPath(stateDir, name)}
pool := x509.NewCertPool()
pool.AddCert(ca.cert)
if leaf, ok := usableLeaf(out.CertFile, out.KeyFile, pool, x509.ExtKeyUsageClientAuth); ok && leaf.Subject.CommonName == out.CommonName {
return out, nil
}
certPEM, keyPEM, err := signLeaf(time.Now(), ca.key, ca.cert, out.CommonName, x509.ExtKeyUsageClientAuth, nil, nil)
if err != nil {
return OpenBaoClientTLS{}, err
}
if err := os.MkdirAll(filepath.Dir(out.CertFile), openBaoClientDirMode); err != nil {
return OpenBaoClientTLS{}, err
}
if err := os.WriteFile(out.KeyFile, keyPEM, openBaoClientMaterialFileMode); err != nil {
return OpenBaoClientTLS{}, fmt.Errorf("vaultcert: writing %s: %w", out.KeyFile, err)
}
if err := os.WriteFile(out.CertFile, append(append([]byte{}, certPEM...), ca.CAPEM...), openBaoClientMaterialFileMode); err != nil {
return OpenBaoClientTLS{}, fmt.Errorf("vaultcert: writing %s: %w", out.CertFile, err)
}
if _, err := tls.LoadX509KeyPair(out.CertFile, out.KeyFile); err != nil {
return OpenBaoClientTLS{}, fmt.Errorf("vaultcert: the client pair just written at %s: %w", out.CertFile, err)
}
return out, nil
}