-
Notifications
You must be signed in to change notification settings - Fork 312
/
ca.go
181 lines (163 loc) · 5.43 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
// Copyright 2020 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.
package crypto
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io/ioutil"
"math/big"
"time"
"github.com/pingcap/errors"
)
var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128)
// CertificateAuthority holds the CA of a cluster
type CertificateAuthority struct {
ClusterName string
Cert *x509.Certificate
Key PrivKey
}
// NewCA generates a new CertificateAuthority object
func NewCA(clsName string) (*CertificateAuthority, error) {
currTime := time.Now().UTC()
// generate a random serial number for the new ca
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
caTemplate := &x509.Certificate{
SerialNumber: serialNumber,
// NOTE: not adding cluster name to the cert subject to avoid potential issues
// when we implement cluster renaming feature. We may consider add this back
// if we find proper way renaming a TLS enabled cluster.
// Adding the cluster name in cert subject may be helpful to diagnose problem
// when a process is trying to connecting a component from another cluster.
Subject: pkix.Name{
Organization: []string{pkixOrganization},
OrganizationalUnit: []string{pkixOrganizationalUnit /*, clsName */},
},
NotBefore: currTime,
NotAfter: currTime.Add(time.Hour * 24 * 365 * 50), // TODO: support ca cert rotate
IsCA: true, // must be true
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
BasicConstraintsValid: true,
}
priv, err := NewKeyPair(KeyTypeRSA, KeySchemeRSASSAPSSSHA256)
if err != nil {
return nil, err
}
caBytes, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, priv.Public().Key(), priv.Signer())
if err != nil {
return nil, err
}
caCert, err := x509.ParseCertificate(caBytes)
if err != nil {
return nil, err
}
return &CertificateAuthority{
ClusterName: clsName,
Cert: caCert,
Key: priv,
}, nil
}
// Sign signs a CSR with the CA
func (ca *CertificateAuthority) Sign(csrBytes []byte) ([]byte, error) {
csr, err := x509.ParseCertificateRequest(csrBytes)
if err != nil {
return nil, err
}
if err := csr.CheckSignature(); err != nil {
return nil, err
}
currTime := time.Now().UTC()
if !currTime.Before(ca.Cert.NotAfter) {
return nil, errors.Errorf("the signer has expired: NotAfter=%v", ca.Cert.NotAfter)
}
// generate a random serial number for the new cert
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, err
}
template := &x509.Certificate{
Signature: csr.Signature,
SignatureAlgorithm: csr.SignatureAlgorithm,
PublicKey: csr.PublicKey,
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
SerialNumber: serialNumber,
Issuer: ca.Cert.Issuer,
Subject: csr.Subject,
DNSNames: csr.DNSNames,
IPAddresses: csr.IPAddresses,
EmailAddresses: csr.EmailAddresses,
URIs: csr.URIs,
NotBefore: currTime,
NotAfter: currTime.Add(time.Hour * 24 * 365 * 10),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
},
Extensions: csr.Extensions,
ExtraExtensions: csr.ExtraExtensions,
}
return x509.CreateCertificate(rand.Reader, template, ca.Cert, csr.PublicKey, ca.Key.Signer())
}
// ReadCA reads an existing CA certificate from disk
func ReadCA(clsName, certPath, keyPath string) (*CertificateAuthority, error) {
// read private key
rawKey, err := ioutil.ReadFile(keyPath)
if err != nil {
return nil, errors.Annotatef(err, "error reading CA private key for %s", clsName)
}
keyPem, _ := pem.Decode(rawKey)
if keyPem == nil {
return nil, errors.Errorf("error decoding CA private key for %s", clsName)
}
var privKey PrivKey
switch keyPem.Type {
case "RSA PRIVATE KEY":
pk, err := x509.ParsePKCS1PrivateKey(keyPem.Bytes)
if err != nil {
return nil, errors.Annotatef(err, "error decoding CA private key for %s", clsName)
}
privKey = &RSAPrivKey{key: pk}
default:
return nil, errors.Errorf("the CA private key type \"%s\" is not supported", keyPem.Type)
}
// read certificate
rawCert, err := ioutil.ReadFile(certPath)
if err != nil {
return nil, errors.Annotatef(err, "error reading CA certificate for %s", clsName)
}
certPem, _ := pem.Decode(rawCert)
if certPem == nil {
return nil, errors.Errorf("error decoding CA certificate for %s", clsName)
}
if certPem.Type != "CERTIFICATE" {
return nil, errors.Errorf("the CA certificate type \"%s\" is not valid", certPem.Type)
}
cert, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
return nil, errors.Annotatef(err, "error decoding CA certificate for %s", clsName)
}
return &CertificateAuthority{
ClusterName: clsName,
Cert: cert,
Key: privKey,
}, nil
}