-
Notifications
You must be signed in to change notification settings - Fork 47
/
cert_bundle.go
207 lines (180 loc) · 5.27 KB
/
cert_bundle.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/*
Copyright (c) 2021 - Present. Blend Labs, Inc. All rights reserved
Use of this source code is governed by a MIT license that can be found in the LICENSE file.
*/
package certutil
import (
"bytes"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io"
"github.com/blend/go-sdk/ex"
)
// NewCertBundle returns a new cert bundle from a given key pair, which can denote the raw PEM encoded
// contents of the public and private key portions of the cert, or paths to files.
// The CertBundle itself is the parsed public key, private key, and individual certificates for the pair.
func NewCertBundle(keyPair KeyPair) (*CertBundle, error) {
certPEM, err := keyPair.CertBytes()
if err != nil {
return nil, ex.New(err)
}
if len(certPEM) == 0 {
return nil, ex.New("empty cert contents")
}
keyPEM, err := keyPair.KeyBytes()
if err != nil {
return nil, ex.New(err)
}
if len(keyPEM) == 0 {
return nil, ex.New("empty key contents")
}
certData, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, ex.New(err)
}
if len(certData.Certificate) == 0 {
return nil, ex.New("no certificates")
}
var certs []x509.Certificate
var ders [][]byte
for _, certDataPortion := range certData.Certificate {
cert, err := x509.ParseCertificate(certDataPortion)
if err != nil {
return nil, ex.New(err)
}
certs = append(certs, *cert)
ders = append(ders, cert.Raw)
}
var privateKey *rsa.PrivateKey
if typed, ok := certData.PrivateKey.(*rsa.PrivateKey); ok {
privateKey = typed
} else {
return nil, ex.New("invalid private key type", ex.OptMessagef("%T", certData.PrivateKey))
}
return &CertBundle{
PrivateKey: privateKey,
PublicKey: &privateKey.PublicKey,
Certificates: certs,
CertificateDERs: ders,
}, nil
}
// CertBundle is the packet of information for a certificate.
type CertBundle struct {
PrivateKey *rsa.PrivateKey
PublicKey *rsa.PublicKey
Certificates []x509.Certificate
CertificateDERs [][]byte
}
// MustGenerateKeyPair returns a serialized version of the bundle as a key pair
// and panics if there is an error.
func (cb *CertBundle) MustGenerateKeyPair() KeyPair {
pair, err := cb.GenerateKeyPair()
if err != nil {
panic(err)
}
return pair
}
// GenerateKeyPair returns a serialized key pair for the cert bundle.
func (cb *CertBundle) GenerateKeyPair() (output KeyPair, err error) {
private := bytes.NewBuffer(nil)
if err = cb.WriteKeyPem(private); err != nil {
return
}
public := bytes.NewBuffer(nil)
if err = cb.WriteCertPem(public); err != nil {
return
}
output = KeyPair{
Cert: public.String(),
Key: private.String(),
}
return
}
// WithParent adds a parent certificate to the certificate chain.
// It is used typically to add the certificate authority.
func (cb *CertBundle) WithParent(parent *CertBundle) {
cb.Certificates = append(cb.Certificates, parent.Certificates...)
cb.CertificateDERs = append(cb.CertificateDERs, parent.CertificateDERs...)
}
// WriteCertPem writes the public key portion of the cert to a given writer.
func (cb CertBundle) WriteCertPem(w io.Writer) error {
for _, der := range cb.CertificateDERs {
if err := pem.Encode(w, &pem.Block{Type: BlockTypeCertificate, Bytes: der}); err != nil {
return ex.New(err)
}
}
return nil
}
// CertPEM returns the cert portion of the certificate DERs as a byte array.
func (cb CertBundle) CertPEM() ([]byte, error) {
buffer := new(bytes.Buffer)
if err := cb.WriteCertPem(buffer); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// WriteKeyPem writes the certificate key as a pem.
func (cb CertBundle) WriteKeyPem(w io.Writer) error {
return pem.Encode(w, &pem.Block{Type: BlockTypeRSAPrivateKey, Bytes: x509.MarshalPKCS1PrivateKey(cb.PrivateKey)})
}
// KeyPEM returns the cert portion of the certificate DERs as a byte array.
func (cb CertBundle) KeyPEM() ([]byte, error) {
buffer := new(bytes.Buffer)
if err := cb.WriteKeyPem(buffer); err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
// CommonNames returns the cert bundle common name(s).
func (cb CertBundle) CommonNames() ([]string, error) {
if len(cb.Certificates) == 0 {
return nil, ex.New("no certificates returned")
}
var output []string
for _, cert := range cb.Certificates {
if cert.Subject.CommonName != "" {
output = append(output, cert.Subject.CommonName)
}
}
return output, nil
}
// CertPool returns the bundle as a cert pool.
func (cb CertBundle) CertPool() (*x509.CertPool, error) {
systemPool, err := x509.SystemCertPool()
if err != nil {
return nil, ex.New(err)
}
for index := range cb.Certificates {
systemPool.AddCert(&cb.Certificates[index])
}
return systemPool, nil
}
// ServerConfig returns a tls.Config for this bundle as a server certificate.
func (cb CertBundle) ServerConfig() (*tls.Config, error) {
keyPair, err := cb.GenerateKeyPair()
if err != nil {
return nil, err
}
serverCert, err := keyPair.CertBytes()
if err != nil {
return nil, err
}
serverKey, err := keyPair.KeyBytes()
if err != nil {
return nil, err
}
serverCertificate, err := tls.X509KeyPair(serverCert, serverKey)
if err != nil {
return nil, err
}
certPool, err := cb.CertPool()
if err != nil {
return nil, err
}
config := new(tls.Config)
config.Certificates = []tls.Certificate{serverCertificate}
config.RootCAs = certPool
return config, nil
}