-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathca.go
More file actions
163 lines (139 loc) · 4.61 KB
/
ca.go
File metadata and controls
163 lines (139 loc) · 4.61 KB
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
158
159
160
161
162
163
package mtls
import (
"crypto"
"crypto/tls"
"crypto/x509"
"slices"
"github.com/cloudflare/cfssl/helpers"
cfsslSigner "github.com/cloudflare/cfssl/signer"
"github.com/cloudflare/cfssl/signer/local"
"github.com/pkg/errors"
)
// CA represents a StackRox service certificate authority.
//
//go:generate mockgen-wrapper
type CA interface {
// CheckProperties checks that the underlying CA certificate is indeed one
// generated by StackRox (e.g., that it has the expected Common Name and other fields set
// as expected).
CheckProperties() error
// Certificate returns the (public) CA certificate.
Certificate() *x509.Certificate
// PrivateKey returns the private key of the CA certificate.
PrivateKey() crypto.PrivateKey
// CertPEM returns the PEM-encoded form of the CA certificate.
CertPEM() []byte
// KeyPEM returns the PEM-encopded form of the CA key.
KeyPEM() []byte
// CertPool returns a certificate pool containing the single CA certificate.
CertPool() *x509.CertPool
// IssueCertForSubject issues a new certificate for the given subject from this CA.
IssueCertForSubject(subj Subject, opts ...IssueCertOption) (*IssuedCert, error)
// ValidateAndExtractSubject validates that the given certificate is a service certificate issued by this CA,
// and extracts the subject information.
ValidateAndExtractSubject(cert *x509.Certificate, opts ...VerifyCertOption) (Subject, error)
}
type ca struct {
certPEM, keyPEM []byte
tlsCert tls.Certificate
signer cfsslSigner.Signer
}
// LoadCAForSigning loads and instantiates a CA that can be used for signing
// from the given certificate and key.
// Note: this function does not verify that the given certificate is actually a valid
// StackRox service CA. To check for this, call `Validate()`.
func LoadCAForSigning(certPEM, keyPEM []byte) (CA, error) {
ca := &ca{
certPEM: slices.Clone(certPEM),
keyPEM: slices.Clone(keyPEM),
}
var err error
ca.tlsCert, err = tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, err
}
ca.tlsCert.Leaf, _ = x509.ParseCertificate(ca.tlsCert.Certificate[0])
priv, err := helpers.ParsePrivateKeyPEM(keyPEM)
if err != nil {
return nil, err
}
ca.signer, err = local.NewSigner(priv, ca.tlsCert.Leaf, cfsslSigner.DefaultSigAlgo(priv), createSigningPolicy())
if err != nil {
return nil, err
}
return ca, nil
}
// LoadCAForValidation loads a CA certificate to be used for certificate validation
// only. It can not be used to sign/issue new certificates, and invoking the respective
// methods will result in an error.
func LoadCAForValidation(certPEM []byte) (CA, error) {
ca := &ca{
certPEM: slices.Clone(certPEM),
}
var err error
ca.tlsCert.Leaf, err = helpers.ParseCertificatePEM(certPEM)
if err != nil {
return nil, err
}
ca.tlsCert.Certificate = [][]byte{ca.tlsCert.Leaf.Raw}
return ca, nil
}
func (c *ca) CheckProperties() error {
if !c.tlsCert.Leaf.IsCA {
return errors.New("certificate is not valid as CA")
}
if cn := c.tlsCert.Leaf.Subject.CommonName; cn != ServiceCACommonName {
return errors.Errorf("invalid certificate common name %q", cn)
}
return nil
}
func (c *ca) Certificate() *x509.Certificate {
cert, _ := x509.ParseCertificate(c.tlsCert.Certificate[0])
return cert
}
func (c *ca) PrivateKey() crypto.PrivateKey {
return c.tlsCert.PrivateKey
}
func (c *ca) CertPEM() []byte {
return slices.Clone(c.certPEM)
}
func (c *ca) KeyPEM() []byte {
return slices.Clone(c.keyPEM)
}
func (c *ca) CertPool() *x509.CertPool {
pool := x509.NewCertPool()
pool.AddCert(c.tlsCert.Leaf)
return pool
}
func (c *ca) IssueCertForSubject(subj Subject, opts ...IssueCertOption) (*IssuedCert, error) {
if c.signer == nil {
return nil, errors.New("CA is not set up for signing")
}
return issueNewCertFromSigner(subj, c.signer, opts)
}
func (c *ca) ValidateAndExtractSubject(cert *x509.Certificate, opts ...VerifyCertOption) (Subject, error) {
vo := &verificationOptions{}
vo.apply(opts)
verifyOpts := x509.VerifyOptions{
Roots: c.CertPool(),
CurrentTime: vo.currentTime,
}
if _, err := cert.Verify(verifyOpts); err != nil {
return Subject{}, err
}
return SubjectFromCommonName(cert.Subject.CommonName), nil
}
// LoadDefaultCA loads the default StackRox Service CA from the default file paths.
// This function will read the files directly from the filesystem; if this does not
// meet your performance needs, you must implement your own caching.
func LoadDefaultCA() (CA, error) {
_, caCertFileContents, _, err := readCA()
if err != nil {
return nil, err
}
caKeyFileContents, err := readCAKey()
if err != nil {
return nil, err
}
return LoadCAForSigning(caCertFileContents, caKeyFileContents)
}