forked from cockroachdb/cockroach
-
Notifications
You must be signed in to change notification settings - Fork 0
/
certs.go
290 lines (243 loc) · 9.24 KB
/
certs.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
// Copyright 2015 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Marc Berhault (marc@cockroachlabs.com)
package security
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"time"
"golang.org/x/net/context"
"github.com/cockroachdb/cockroach/pkg/util/log"
"github.com/pkg/errors"
)
const (
keyFileMode = 0600
certFileMode = 0644
)
// loadCACertAndKey loads the certificate and key files,parses them,
// and returns the x509 certificate and private key.
func loadCACertAndKey(sslCA, sslCAKey string) (*x509.Certificate, crypto.PrivateKey, error) {
// LoadX509KeyPair does a bunch of validation, including len(Certificates) != 0.
caCert, err := tls.LoadX509KeyPair(sslCA, sslCAKey)
if err != nil {
return nil, nil, errors.Errorf("error loading CA certificate %s and key %s: %s",
sslCA, sslCAKey, err)
}
// Extract x509 certificate from tls cert.
x509Cert, err := x509.ParseCertificate(caCert.Certificate[0])
if err != nil {
return nil, nil, errors.Errorf("error parsing CA certificate %s: %s", sslCA, err)
}
return x509Cert, caCert.PrivateKey, nil
}
func writeCertificateToFile(certFilePath string, certificate []byte, overwrite bool) error {
certBlock := &pem.Block{Type: "CERTIFICATE", Bytes: certificate}
return WritePEMToFile(certFilePath, certFileMode, overwrite, certBlock)
}
func writeKeyToFile(keyFilePath string, key crypto.PrivateKey, overwrite bool) error {
keyBlock, err := PrivateKeyToPEM(key)
if err != nil {
return err
}
return WritePEMToFile(keyFilePath, keyFileMode, overwrite, keyBlock)
}
// CreateCAPair creates a CA key and a CA certificate.
// If the certs directory does not exist, it is created.
// If the key does not exist, it is created.
// The certificate is written to the certs directory. If the file already exists,
// we append the original certificates to the new certificate.
func CreateCAPair(
certsDir, caKeyPath string,
keySize int,
lifetime time.Duration,
allowKeyReuse bool,
overwrite bool,
) error {
if len(caKeyPath) == 0 {
return errors.New("the path to the CA key is required")
}
if len(certsDir) == 0 {
return errors.New("the path to the certs directory is required")
}
// The certificate manager expands the env for the certs directory.
// For consistency, we need to do this for the key as well.
caKeyPath = os.ExpandEnv(caKeyPath)
// Create a certificate manager with "create dir if not exist".
cm, err := NewCertificateManagerFirstRun(certsDir)
if err != nil {
return err
}
var key crypto.PrivateKey
if _, err := os.Stat(caKeyPath); err != nil {
if !os.IsNotExist(err) {
return errors.Errorf("could not stat CA key file %s: %v", caKeyPath, err)
}
// The key does not exist: create it.
key, err = rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return errors.Errorf("could not generate new CA key: %v", err)
}
// overwrite is not technically needed here, but use it in case something else created it.
if err := writeKeyToFile(caKeyPath, key, overwrite); err != nil {
return errors.Errorf("could not write CA key to file %s: %v", caKeyPath, err)
}
log.Infof(context.Background(), "Generated CA key %s", caKeyPath)
} else {
if !allowKeyReuse {
return errors.Errorf("CA key %s exists, but key reuse is disabled", caKeyPath)
}
// The key exists, parse it.
contents, err := ioutil.ReadFile(caKeyPath)
if err != nil {
return errors.Errorf("could not read CA key file %s: %v", caKeyPath, err)
}
key, err = PEMToPrivateKey(contents)
if err != nil {
return errors.Errorf("could not parse CA key file %s: %v", caKeyPath, err)
}
log.Infof(context.Background(), "Using CA key from file %s", caKeyPath)
}
// Generate certificate.
certContents, err := GenerateCA(key.(crypto.Signer), lifetime)
if err != nil {
return errors.Errorf("could not generate CA certificate: %v", err)
}
certPath := cm.CACertPath()
var existingCertificates []*pem.Block
if _, err := os.Stat(certPath); err == nil {
// The cert file already exists, load certificates.
contents, err := ioutil.ReadFile(certPath)
if err != nil {
return errors.Errorf("could not read existing CA cert file %s: %v", certPath, err)
}
existingCertificates, err = PEMToCertificates(contents)
if err != nil {
return errors.Errorf("could not parse existing CA cert file %s: %v", certPath, err)
}
log.Infof(context.Background(), "Found %d certificates in %s",
len(existingCertificates), certPath)
} else if !os.IsNotExist(err) {
return errors.Errorf("could not stat CA cert file %s: %v", certPath, err)
}
// Always place the new certificate first.
certificates := []*pem.Block{{Type: "CERTIFICATE", Bytes: certContents}}
certificates = append(certificates, existingCertificates...)
if err := WritePEMToFile(certPath, certFileMode, overwrite, certificates...); err != nil {
return errors.Errorf("could not write CA certificate file %s: %v", certPath, err)
}
log.Infof(context.Background(), "Wrote %d certificates to %s", len(certificates), certPath)
return nil
}
// CreateNodePair creates a node key and certificate.
// The CA cert and key must load properly. If multiple certificates
// exist in the CA cert, the first one is used.
func CreateNodePair(
certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, hosts []string,
) error {
if len(caKeyPath) == 0 {
return errors.New("the path to the CA key is required")
}
if len(certsDir) == 0 {
return errors.New("the path to the certs directory is required")
}
if len(hosts) == 0 {
return errors.Errorf("no hosts specified. Need at least one")
}
// The certificate manager expands the env for the certs directory.
// For consistency, we need to do this for the key as well.
caKeyPath = os.ExpandEnv(caKeyPath)
// Create a certificate manager with "create dir if not exist".
cm, err := NewCertificateManagerFirstRun(certsDir)
if err != nil {
return err
}
// Load the CA pair.
caCert, caPrivateKey, err := loadCACertAndKey(cm.CACertPath(), caKeyPath)
if err != nil {
return err
}
// Generate certificates and keys.
nodeKey, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return errors.Errorf("could not generate new node key: %v", err)
}
nodeCert, err := GenerateServerCert(caCert, caPrivateKey, nodeKey.Public(), lifetime, hosts)
if err != nil {
return errors.Errorf("error creating node server certificate and key: %s", err)
}
certPath := cm.NodeCertPath()
if err := writeCertificateToFile(certPath, nodeCert, overwrite); err != nil {
return errors.Errorf("error writing node server certificate to %s: %v", certPath, err)
}
log.Infof(context.Background(), "Generated node certificate: %s", certPath)
keyPath := cm.NodeKeyPath()
if err := writeKeyToFile(keyPath, nodeKey, overwrite); err != nil {
return errors.Errorf("error writing node server key to %s: %v", keyPath, err)
}
log.Infof(context.Background(), "Generated node key: %s", keyPath)
return nil
}
// CreateClientPair creates a node key and certificate.
// The CA cert and key must load properly. If multiple certificates
// exist in the CA cert, the first one is used.
func CreateClientPair(
certsDir, caKeyPath string, keySize int, lifetime time.Duration, overwrite bool, user string,
) error {
if len(caKeyPath) == 0 {
return errors.New("the path to the CA key is required")
}
if len(certsDir) == 0 {
return errors.New("the path to the certs directory is required")
}
// The certificate manager expands the env for the certs directory.
// For consistency, we need to do this for the key as well.
caKeyPath = os.ExpandEnv(caKeyPath)
// Create a certificate manager with "create dir if not exist".
cm, err := NewCertificateManagerFirstRun(certsDir)
if err != nil {
return err
}
// Load the CA pair.
caCert, caPrivateKey, err := loadCACertAndKey(cm.CACertPath(), caKeyPath)
if err != nil {
return err
}
// Generate certificates and keys.
clientKey, err := rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return errors.Errorf("could not generate new node key: %v", err)
}
clientCert, err := GenerateClientCert(caCert, caPrivateKey, clientKey.Public(), lifetime, user)
if err != nil {
return errors.Errorf("error creating node server certificate and key: %s", err)
}
certPath := cm.ClientCertPath(user)
if err := writeCertificateToFile(certPath, clientCert, overwrite); err != nil {
return errors.Errorf("error writing node server certificate to %s: %v", certPath, err)
}
log.Infof(context.Background(), "Generated client certificate: %s", certPath)
keyPath := cm.ClientKeyPath(user)
if err := writeKeyToFile(keyPath, clientKey, overwrite); err != nil {
return errors.Errorf("error writing node server key to %s: %v", keyPath, err)
}
log.Infof(context.Background(), "Generated client key: %s", keyPath)
return nil
}