-
Notifications
You must be signed in to change notification settings - Fork 4
/
keyStore.go
351 lines (319 loc) · 10.9 KB
/
keyStore.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package keyStore
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"github.com/echocat/caretakerd/errors"
"github.com/echocat/caretakerd/panics"
"io/ioutil"
"math/big"
"os"
"strconv"
"strings"
"time"
)
// KeyStore represents a keystore that holds certificates, CAs and private keys.
type KeyStore struct {
enabled bool
config Config
pem []byte
ca []*x509.Certificate
cert *x509.Certificate
privateKey interface{}
}
// NewKeyStore create an new instance of KeyStore.
func NewKeyStore(enabled bool, conf Config) (*KeyStore, error) {
err := conf.Validate()
if err != nil {
return nil, err
}
if !enabled {
return &KeyStore{
enabled: false,
config: conf,
}, nil
}
switch conf.Type {
case FromFile:
return newFomFile(conf)
case FromEnvironment:
return newFromEnvironment(conf)
case Generated:
return newGenerated(conf)
}
return nil, errors.New("Unknown keyStore type %v.", conf.Type)
}
func generatePrivateKey(conf Config) (privateKey interface{}, privateKeyBytes []byte, publicKey interface{}, err error) {
plainAlgorithm := conf.GetHintsArgument("algorithm")
if len(plainAlgorithm) > 0 && strings.ToLower(plainAlgorithm) != "rsa" {
return nil, []byte{}, nil, errors.New("Unsupported algorithm: %s", plainAlgorithm)
}
bits := 1024
plainBits := conf.GetHintsArgument("bits")
if len(plainBits) > 0 {
if bits, err = strconv.Atoi(plainBits); err != nil || bits <= 0 {
return nil, []byte{}, nil, errors.New("Unsupported algorithm bits: %s", plainBits)
}
}
plainPrivateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, []byte{}, nil, errors.New("Could not generate private key.").CausedBy(err)
}
privateKeyBytes = x509.MarshalPKCS1PrivateKey(plainPrivateKey)
return plainPrivateKey, privateKeyBytes, &plainPrivateKey.PublicKey, nil
}
func generateCertificate(conf Config, privateKey interface{}, publicKey interface{}) ([]byte, error) {
notBefore := time.Now()
notAfter := notBefore.Add(8760 * time.Hour)
template := x509.Certificate{
SerialNumber: newSerialNumber(),
Subject: pkix.Name{
CommonName: "caretakerd",
},
IsCA: true,
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
BasicConstraintsValid: true,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey, privateKey)
if err != nil {
return nil, errors.New("Failed to create certificate.").CausedBy(err)
}
return derBytes, nil
}
func generatePem(conf Config) ([]byte, *x509.Certificate, interface{}, error) {
privateKey, privateKeyBytes, publicKey, err := generatePrivateKey(conf)
if err != nil {
return []byte{}, nil, nil, errors.New("Could not generate private key.").CausedBy(err)
}
certificateDerBytes, err := generateCertificate(conf, privateKey, publicKey)
if err != nil {
return []byte{}, nil, nil, errors.New("Could not generate certificate.").CausedBy(err)
}
cert, err := x509.ParseCertificate(certificateDerBytes)
if err != nil {
return []byte{}, nil, nil, errors.New("Wow! Could not parse right now created certificate?").CausedBy(err)
}
pemBytes := []byte{}
pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateDerBytes})...)
pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privateKeyBytes})...)
return pemBytes, cert, privateKey, nil
}
func newFomFile(conf Config) (*KeyStore, error) {
pem, err := ioutil.ReadFile(conf.PemFile.String())
if err != nil {
return nil, errors.New("Could not read pem from '%v'.", conf.PemFile).CausedBy(err)
}
return newPemFromBytes(conf, pem)
}
func newFromEnvironment(conf Config) (*KeyStore, error) {
pem := os.Getenv("CTD_PEM")
if len(strings.TrimSpace(pem)) <= 0 {
return nil, errors.New("There is an %v keyStore confgiured but the CTD_PEM environment varaible is empty.", conf.Type)
}
return newPemFromBytes(conf, []byte(pem))
}
func newPemFromBytes(conf Config, pem []byte) (*KeyStore, error) {
ca, err := buildWholeCAsBy(conf, pem)
if err != nil {
return nil, errors.New("Could not build ca for keyStore config.").CausedBy(err)
}
certs, err := loadCertificatesFrom(pem)
if err != nil {
return nil, errors.New("Could not load certs from PEM.").CausedBy(err)
}
if len(certs) <= 0 {
return nil, errors.New("The provieded PEM does not contain a certificate.")
}
privateKey, err := loadPrivateKeyFrom(pem)
if err != nil {
return nil, err
}
return &KeyStore{
enabled: true,
config: conf,
pem: pem,
ca: ca,
cert: certs[0],
privateKey: privateKey,
}, nil
}
func newGenerated(conf Config) (*KeyStore, error) {
pem, cert, privateKey, err := generatePem(conf)
if err != nil {
return nil, errors.New("Could not generate pem for keyStore config.").CausedBy(err)
}
ca, err := buildWholeCAsBy(conf, pem)
if err != nil {
return nil, errors.New("Could not build CA bundle for keyStore config.").CausedBy(err)
}
return &KeyStore{
enabled: true,
config: conf,
pem: pem,
ca: ca,
cert: cert,
privateKey: privateKey,
}, nil
}
func buildWholeCAsBy(conf Config, p []byte) ([]*x509.Certificate, error) {
result := []*x509.Certificate{}
if !conf.CaFile.IsTrimmedEmpty() {
fileContent, err := ioutil.ReadFile(conf.CaFile.String())
if err != nil {
return nil, errors.New("Could not read certificates from %v.", conf.CaFile).CausedBy(err)
}
casFromFile, err := loadCertificatesFrom(fileContent)
if err != nil {
return nil, errors.New("Could not parse certificates from %v.", conf.CaFile).CausedBy(err)
}
for _, candidate := range casFromFile {
if candidate.IsCA {
result = append(result, candidate)
}
}
}
casFromP, err := loadCertificatesFrom(p)
if err != nil {
return nil, err
}
return append(result, casFromP...), nil
}
func loadCertificatesFrom(p []byte) ([]*x509.Certificate, error) {
result := []*x509.Certificate{}
if len(p) > 0 {
rp := p
block := new(pem.Block)
for block != nil && len(rp) > 0 {
block, rp = pem.Decode(rp)
if block != nil && block.Type == "CERTIFICATE" {
candidates, err := x509.ParseCertificates(block.Bytes)
if err != nil {
return nil, errors.New("Could not parse certificates.").CausedBy(err)
}
for _, candidate := range candidates {
if candidate.IsCA {
result = append(result, candidate)
}
}
}
}
}
return result, nil
}
func loadPrivateKeyFrom(p []byte) (interface{}, error) {
if len(p) > 0 {
rp := p
block := new(pem.Block)
for block != nil && len(rp) > 0 {
block, rp = pem.Decode(rp)
if block != nil && block.Type == "RSA PRIVATE KEY" {
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, errors.New("Could not parse privateKey.").CausedBy(err)
}
return privateKey, nil
}
}
}
return nil, errors.New("The PEM does not contain a valid private key.")
}
func newSerialNumber() *big.Int {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
panics.New("Could not generate serial number.").CausedBy(err).Throw()
}
return serialNumber
}
// LoadCertificateFromFile loads a certificate from given filename and return it.
func LoadCertificateFromFile(filename string) (*x509.Certificate, error) {
fileContent, err := ioutil.ReadFile(filename)
if err != nil {
return nil, errors.New("Could not read certificate from %v.", filename).CausedBy(err)
}
certificates, err := loadCertificatesFrom(fileContent)
if err != nil {
return nil, errors.New("Could not read certificate from %v.", filename).CausedBy(err)
}
if len(certificates) <= 0 {
return nil, errors.New("File %v does not contain a valid certificate.", filename)
}
return certificates[0], nil
}
func (instance KeyStore) generateClientCertificate(name string, publicKey interface{}, privateKey interface{}) ([]byte, error) {
notBefore := time.Now()
notAfter := notBefore.Add(8760 * time.Hour)
template := x509.Certificate{
SerialNumber: newSerialNumber(),
Issuer: instance.cert.Subject,
Subject: pkix.Name{
CommonName: name,
},
IsCA: true,
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: false,
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, instance.cert, publicKey, instance.privateKey)
if err != nil {
return []byte{}, errors.New("Failed to create certificate for '%v'.", name).CausedBy(err)
}
return derBytes, nil
}
// GeneratePem generates a new PEM with config of current KeyStore instance and return it.
// This PEM will be stored at the KeyStore instance by this method.
func (instance KeyStore) GeneratePem(name string) ([]byte, *x509.Certificate, error) {
if !instance.enabled {
return []byte{}, nil, errors.New("KeyStore is not enabled.")
}
privateKey, privateKeyBytes, publicKey, err := generatePrivateKey(instance.Config())
if err != nil {
return []byte{}, nil, errors.New("Could not generate pem for '%v'.", name).CausedBy(err)
}
certificateDerBytes, err := instance.generateClientCertificate(name, publicKey, privateKey)
if err != nil {
return []byte{}, nil, err
}
cert, err := x509.ParseCertificate(certificateDerBytes)
if err != nil || cert == nil {
return []byte{}, nil, errors.New("Wow! Could not parse right now created certificate for '%v'?", name).CausedBy(err)
}
pemBytes := []byte{}
pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateDerBytes})...)
pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: instance.cert.Raw})...)
pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privateKeyBytes})...)
return pemBytes, cert, nil
}
// PEM returns the containing PEM instance of this KeyStore.
// If there is no PEM the result is empty.
func (instance KeyStore) PEM() []byte {
return instance.pem
}
// CA returns all containing CAs of this KeyStore.
func (instance KeyStore) CA() []*x509.Certificate {
return instance.ca
}
// Type returns the Type of this KeyStore.
func (instance KeyStore) Type() Type {
return instance.config.Type
}
// Config returns the Config instance this KeyStore was created with.
func (instance KeyStore) Config() Config {
return instance.config
}
// IsCA returns true if the containing certificate could use to create new certificates.
func (instance KeyStore) IsCA() bool {
cert := instance.cert
return cert != nil && cert.IsCA
}
// IsEnabled returns true if this KeyStore is configured and usable.
func (instance KeyStore) IsEnabled() bool {
return instance.enabled
}