-
Notifications
You must be signed in to change notification settings - Fork 20
/
authority.go
216 lines (200 loc) · 6.36 KB
/
authority.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
// Copyright 2019 GRAIL, Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// Package authority provides an in-process TLS certificate authority,
// useful for creating and distributing TLS certificates for mutually authenticated
// HTTPS networking within Bigmachine.
package authority
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"io/ioutil"
"math/big"
"net"
"os"
"time"
)
// DriftMargin is the amount of acceptable clock drift during
// certificate issuing and verification.
const DriftMargin = time.Minute
// CertDuration is the duration of cert validity for the certificates
// issued by authorities.
const certDuration = 7 * 24 * time.Hour
// A T is a TLS certificate authority which can issue client and server
// certificates and provide configuration for HTTPS clients.
type T struct {
key *rsa.PrivateKey
cert *x509.Certificate
// The CA certificate and key are stored in PEM-encoded bytes
// as most of the Go APIs operate directly on these.
certPEM, keyPEM []byte
}
// New creates a new certificate authority, reading the PEM-encoded
// certificate and private key from the provided path. If the path
// does not exist, newCA instead creates a new certificate authority
// and stores it at the provided path. If path is empty, the
// authority is ephemeral.
func New(filename string) (*T, error) {
// As an extra precaution, we always exercise the read path, so if
// the CA PEM is missing, we generate it, and then read it back.
pemBlock, err := cached(filename, func() ([]byte, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "bigmachine"},
NotBefore: time.Now().Add(-DriftMargin),
// Newton says we have at least this long:
// https://newtonprojectca.files.wordpress.com/2013/06/reply-to-tom-harpur-2-page-full-version.pdf
NotAfter: time.Date(2060, 1, 1, 0, 0, 0, 0, time.UTC),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
if err != nil {
return nil, err
}
var b bytes.Buffer
// Save it also.
if err := pem.Encode(&b, &pem.Block{Type: "CERTIFICATE", Bytes: cert}); err != nil {
return nil, err
}
if err := pem.Encode(&b, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}); err != nil {
return nil, err
}
return b.Bytes(), nil
})
var certBlock, keyBlock []byte
for {
var derBlock *pem.Block
derBlock, pemBlock = pem.Decode(pemBlock)
if derBlock == nil {
break
}
switch derBlock.Type {
case "CERTIFICATE":
certBlock = derBlock.Bytes
case "RSA PRIVATE KEY":
keyBlock = derBlock.Bytes
}
}
if certBlock == nil || keyBlock == nil {
return nil, errors.New("httpsca: incomplete certificate")
}
ca := new(T)
ca.cert, err = x509.ParseCertificate(certBlock)
if err != nil {
return nil, err
}
ca.key, err = x509.ParsePKCS1PrivateKey(keyBlock)
if err != nil {
return nil, err
}
ca.certPEM, err = encodePEM(&pem.Block{Type: "CERTIFICATE", Bytes: certBlock})
if err != nil {
return nil, err
}
ca.keyPEM, err = encodePEM(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(ca.key)})
if err != nil {
return nil, err
}
return ca, nil
}
// Cert returns the authority's x509 certificate.
func (c *T) Cert() *x509.Certificate {
return c.cert
}
// Issue issues a new certificate out of this CA with the provided common name, ttl, ips, and DNSes.
func (c *T) Issue(cn string, ttl time.Duration, ips []net.IP, dnss []string) ([]byte, *rsa.PrivateKey, error) {
maxSerial := new(big.Int).Lsh(big.NewInt(1), 128)
serial, err := rand.Int(rand.Reader, maxSerial)
if err != nil {
return nil, nil, err
}
now := time.Now().Add(-DriftMargin)
template := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: cn,
},
NotBefore: now,
NotAfter: now.Add(DriftMargin + ttl),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
template.IPAddresses = append(template.IPAddresses, ips...)
template.DNSNames = append(template.DNSNames, dnss...)
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}
cert, err := x509.CreateCertificate(rand.Reader, &template, c.cert, &key.PublicKey, c.key)
if err != nil {
return nil, nil, err
}
return cert, key, nil
}
// HTTPSConfig returns a tls configs based on newly issued TLS certificates from this CA.
func (c *T) HTTPSConfig() (client, server *tls.Config, err error) {
cert, key, err := c.Issue("bigmachine", certDuration, nil, nil)
if err != nil {
return nil, nil, err
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(c.certPEM)
// Load the newly created certificate.
certPEM, err := encodePEM(&pem.Block{Type: "CERTIFICATE", Bytes: cert})
if err != nil {
return nil, nil, err
}
keyPEM, err := encodePEM(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
if err != nil {
return nil, nil, err
}
tlscert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return nil, nil, err
}
clientConfig := &tls.Config{
RootCAs: pool,
InsecureSkipVerify: true,
Certificates: []tls.Certificate{tlscert},
}
serverConfig := &tls.Config{
ClientCAs: pool,
Certificates: []tls.Certificate{tlscert},
}
return clientConfig, serverConfig, nil
}
func encodePEM(block *pem.Block) ([]byte, error) {
var w bytes.Buffer
if err := pem.Encode(&w, block); err != nil {
return nil, err
}
return w.Bytes(), nil
}
func cached(filename string, gen func() ([]byte, error)) ([]byte, error) {
if filename == "" {
return gen()
}
p, err := ioutil.ReadFile(filename)
if err == nil || !os.IsNotExist(err) {
return p, nil
}
p, err = gen()
if err != nil {
return nil, err
}
return p, ioutil.WriteFile(filename, p, 0600)
}