-
Notifications
You must be signed in to change notification settings - Fork 81
/
ca.go
399 lines (360 loc) · 9.95 KB
/
ca.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
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
//go:build !no_cert_auth
// +build !no_cert_auth
package certificates
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"strings"
"time"
"github.com/ansible/receptor/pkg/utils"
)
// CertNames lists the subjectAltNames that can be assigned to a certificate or request.
type CertNames struct {
DNSNames []string
NodeIDs []string
IPAddresses []net.IP
}
// CertOptions are the parameters used to initialize a new certificate or request.
type CertOptions struct {
CertNames
CommonName string
Bits int
NotBefore time.Time
NotAfter time.Time
}
// LoadFromPEMFile loads certificate data from a PEM file.
func LoadFromPEMFile(filename string) ([]interface{}, error) {
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
results := make([]interface{}, 0)
var block *pem.Block
for len(content) > 0 {
block, content = pem.Decode(content)
if block == nil {
return nil, fmt.Errorf("failed to decode PEM block")
}
switch block.Type {
case "CERTIFICATE":
var cert *x509.Certificate
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
results = append(results, cert)
case "CERTIFICATE REQUEST":
var req *x509.CertificateRequest
req, err = x509.ParseCertificateRequest(block.Bytes)
if err != nil {
return nil, err
}
results = append(results, req)
case "RSA PRIVATE KEY":
var key *rsa.PrivateKey
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
results = append(results, key)
case "PUBLIC KEY":
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
results = append(results, key)
default:
return nil, fmt.Errorf("unknown block type %s", block.Type)
}
}
return results, nil
}
// SaveToPEMFile saves certificate data to a PEM file.
func SaveToPEMFile(filename string, data []interface{}) error {
var err error
var ok bool
content := make([]string, 0)
for _, elem := range data {
var cert *x509.Certificate
cert, ok = elem.(*x509.Certificate)
if ok {
certPEM := new(bytes.Buffer)
err = pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
return err
}
content = append(content, certPEM.String())
continue
}
var req *x509.CertificateRequest
req, ok = elem.(*x509.CertificateRequest)
if ok {
reqPEM := new(bytes.Buffer)
err = pem.Encode(reqPEM, &pem.Block{
Type: "CERTIFICATE REQUEST",
Bytes: req.Raw,
})
if err != nil {
return err
}
content = append(content, reqPEM.String())
continue
}
var keyPrivate *rsa.PrivateKey
keyPrivate, ok = elem.(*rsa.PrivateKey)
if ok {
keyPEM := new(bytes.Buffer)
err = pem.Encode(keyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(keyPrivate),
})
if err != nil {
return err
}
content = append(content, keyPEM.String())
continue
}
var keyPublic *rsa.PublicKey
keyPublic, ok = elem.(*rsa.PublicKey)
if ok {
keyPEM := new(bytes.Buffer)
keyPublicBytes, err := x509.MarshalPKIXPublicKey(keyPublic)
if err != nil {
return err
}
err = pem.Encode(keyPEM, &pem.Block{
Type: "PUBLIC KEY",
Bytes: keyPublicBytes,
})
if err != nil {
return err
}
content = append(content, keyPEM.String())
continue
}
}
return ioutil.WriteFile(filename, []byte(strings.Join(content, "\n")), 0o600)
}
// LoadCertificate loads a single certificate from a file.
func LoadCertificate(filename string) (*x509.Certificate, error) {
data, err := LoadFromPEMFile(filename)
if err != nil {
return nil, err
}
if len(data) != 1 {
return nil, fmt.Errorf("certificate file should contain exactly one item")
}
cert, ok := data[0].(*x509.Certificate)
if !ok {
return nil, fmt.Errorf("certificate file does not contain certificate data")
}
return cert, nil
}
// LoadRequest loads a single certificate request from a file.
func LoadRequest(filename string) (*x509.CertificateRequest, error) {
data, err := LoadFromPEMFile(filename)
if err != nil {
return nil, err
}
if len(data) != 1 {
return nil, fmt.Errorf("certificate request file should contain exactly one item")
}
req, ok := data[0].(*x509.CertificateRequest)
if !ok {
return nil, fmt.Errorf("certificate request file does not contain certificate request data")
}
return req, nil
}
// LoadPrivateKey loads a single RSA private key from a file.
func LoadPrivateKey(filename string) (*rsa.PrivateKey, error) {
data, err := LoadFromPEMFile(filename)
if err != nil {
return nil, err
}
if len(data) != 1 {
return nil, fmt.Errorf("private key file should contain exactly one item")
}
key, ok := data[0].(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("private key file does not contain private key data")
}
return key, nil
}
// LoadPublicKey loads a single RSA public key from a file.
func LoadPublicKey(filename string) (*rsa.PublicKey, error) {
data, err := LoadFromPEMFile(filename)
if err != nil {
return nil, err
}
if len(data) != 1 {
return nil, fmt.Errorf("public key file should contain exactly one item")
}
key, ok := data[0].(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("public key file does not contain public key data")
}
return key, nil
}
// CA contains internal data for a certificate authority.
type CA struct {
Certificate *x509.Certificate
PrivateKey *rsa.PrivateKey
}
// CreateCA initializes a new CertKeyPair from given parameters.
func CreateCA(opts *CertOptions) (*CA, error) {
if opts.CommonName == "" {
return nil, fmt.Errorf("must provide CommonName")
}
if opts.Bits == 0 {
opts.Bits = 2048
}
if opts.NotBefore.IsZero() {
opts.NotBefore = time.Now()
}
if opts.NotAfter.IsZero() {
opts.NotAfter = time.Now().AddDate(10, 0, 0)
}
if opts.DNSNames != nil || opts.NodeIDs != nil || opts.IPAddresses != nil {
return nil, fmt.Errorf("CertKeyPair certificate cannot have DNSNames, NodeIDs or IPAddresses")
}
var err error
ca := &CA{}
ca.PrivateKey, err = rsa.GenerateKey(rand.Reader, opts.Bits)
if err != nil {
return nil, err
}
caTemplate := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: opts.CommonName,
},
NotBefore: opts.NotBefore,
NotAfter: opts.NotAfter,
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &ca.PrivateKey.PublicKey, ca.PrivateKey)
if err != nil {
return nil, err
}
ca.Certificate, err = x509.ParseCertificate(caBytes)
if err != nil {
return nil, err
}
return ca, nil
}
// CreateCertReqWithKey creates a new x.509 certificate request with a newly generated private key.
func CreateCertReqWithKey(opts *CertOptions) (*x509.CertificateRequest, *rsa.PrivateKey, error) {
key, err := rsa.GenerateKey(rand.Reader, opts.Bits)
if err != nil {
return nil, nil, err
}
req, err := CreateCertReq(opts, key)
if err != nil {
return nil, nil, err
}
return req, key, nil
}
// CreateCertReq creates a new x.509 certificate request for an existing private key.
func CreateCertReq(opts *CertOptions, privateKey *rsa.PrivateKey) (*x509.CertificateRequest, error) {
if opts.CommonName == "" {
return nil, fmt.Errorf("must provide CommonName")
}
if opts.Bits == 0 {
opts.Bits = 2048
}
var err error
var san *pkix.Extension
san, err = utils.MakeReceptorSAN(opts.DNSNames, opts.IPAddresses, opts.NodeIDs)
if err != nil {
return nil, err
}
reqTemplate := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: opts.CommonName,
},
ExtraExtensions: []pkix.Extension{*san},
}
var reqBytes []byte
reqBytes, err = x509.CreateCertificateRequest(rand.Reader, reqTemplate, privateKey)
if err != nil {
return nil, err
}
var req *x509.CertificateRequest
req, err = x509.ParseCertificateRequest(reqBytes)
if err != nil {
return nil, err
}
return req, nil
}
// GetReqNames returns the names coded into a certificate request, including Receptor node IDs.
func GetReqNames(request *x509.CertificateRequest) (*CertNames, error) {
nodeIDs, err := utils.ReceptorNames(request.Extensions)
if err != nil {
return nil, err
}
cn := &CertNames{
DNSNames: request.DNSNames,
NodeIDs: nodeIDs,
IPAddresses: request.IPAddresses,
}
return cn, nil
}
// SignCertReq signs a certificate request using a CA key.
func SignCertReq(req *x509.CertificateRequest, ca *CA, opts *CertOptions) (*x509.Certificate, error) {
if opts.NotBefore.IsZero() {
opts.NotBefore = time.Now()
}
if opts.NotAfter.IsZero() {
opts.NotAfter = time.Now().AddDate(1, 0, 0)
}
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Signature: req.Signature,
SignatureAlgorithm: req.SignatureAlgorithm,
PublicKey: req.PublicKey,
PublicKeyAlgorithm: req.PublicKeyAlgorithm,
Issuer: ca.Certificate.Subject,
Subject: req.Subject,
NotBefore: opts.NotBefore,
NotAfter: opts.NotAfter,
IsCA: false,
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
}
found := false
for _, ext := range req.Extensions {
if ext.Id.Equal(utils.OIDSubjectAltName) {
certTemplate.ExtraExtensions = []pkix.Extension{ext}
found = true
break
}
}
if !found {
certTemplate.DNSNames = req.DNSNames
certTemplate.IPAddresses = req.IPAddresses
}
certBytes, err := x509.CreateCertificate(rand.Reader, certTemplate, ca.Certificate, req.PublicKey, ca.PrivateKey)
if err != nil {
return nil, err
}
var cert *x509.Certificate
cert, err = x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
return cert, nil
}