-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
issue.go
157 lines (135 loc) · 4.86 KB
/
issue.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
/*
Copyright 2020 The Kubernetes 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.
*/
package pki
import (
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
"net"
"strings"
"time"
)
var wellKnownCertificateTypes = map[string]string{
"ca": "CA,KeyUsageCRLSign,KeyUsageCertSign",
"client": "ExtKeyUsageClientAuth,KeyUsageDigitalSignature",
"clientServer": "ExtKeyUsageClientAuth,ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
"server": "ExtKeyUsageServerAuth,KeyUsageDigitalSignature,KeyUsageKeyEncipherment",
}
type IssueCertRequest struct {
// Signer is the keypair to use to sign. Ignored if Type is "CA", in which case the cert will be self-signed.
Signer string
// Type is the type of certificate i.e. CA, server, client etc.
Type string
// Subject is the certificate subject.
Subject pkix.Name
// AlternateNames is a list of alternative names for this certificate.
AlternateNames []string
// PublicKey is the public key for this certificate. If nil, it will be calculated from PrivateKey.
PublicKey crypto.PublicKey
// PrivateKey is the private key for this certificate. If both this and PublicKey are nil, a new private key will be generated.
PrivateKey *PrivateKey
// Validity is the certificate validity. The default is 10 years.
Validity time.Duration
// Serial is the certificate serial number. If nil, a random number will be generated.
Serial *big.Int
}
type Keystore interface {
// FindPrimaryKeypair finds a cert & private key, returning nil where either is not found
// (if the certificate is found but not keypair, that is not an error: only the cert will be returned).
FindPrimaryKeypair(name string) (*Certificate, *PrivateKey, error)
}
// IssueCert issues a certificate, either a self-signed CA or from a CA in a keystore.
func IssueCert(request *IssueCertRequest, keystore Keystore) (issuedCertificate *Certificate, issuedKey *PrivateKey, caCertificate *Certificate, err error) {
certificateType := request.Type
if expanded, found := wellKnownCertificateTypes[certificateType]; found {
certificateType = expanded
}
template := &x509.Certificate{
BasicConstraintsValid: true,
IsCA: false,
SerialNumber: request.Serial,
}
tokens := strings.Split(certificateType, ",")
for _, t := range tokens {
if strings.HasPrefix(t, "KeyUsage") {
ku, found := parseKeyUsage(t)
if !found {
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
}
template.KeyUsage |= ku
} else if strings.HasPrefix(t, "ExtKeyUsage") {
ku, found := parseExtKeyUsage(t)
if !found {
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %v", t)
}
template.ExtKeyUsage = append(template.ExtKeyUsage, ku)
} else if t == "CA" {
template.IsCA = true
} else {
return nil, nil, nil, fmt.Errorf("unrecognized certificate option: %q", t)
}
}
template.Subject = request.Subject
var alternateNames []string
alternateNames = append(alternateNames, request.AlternateNames...)
for _, san := range alternateNames {
san = strings.TrimSpace(san)
if san == "" {
continue
}
if ip := net.ParseIP(san); ip != nil {
template.IPAddresses = append(template.IPAddresses, ip)
} else {
template.DNSNames = append(template.DNSNames, san)
}
}
var caPrivateKey *PrivateKey
var signer *x509.Certificate
if !template.IsCA {
var err error
caCertificate, caPrivateKey, err = keystore.FindPrimaryKeypair(request.Signer)
if err != nil {
return nil, nil, nil, err
}
if caPrivateKey == nil {
return nil, nil, nil, fmt.Errorf("ca key for %q was not found; cannot issue certificates", request.Signer)
}
if caCertificate == nil {
return nil, nil, nil, fmt.Errorf("ca certificate for %q was not found; cannot issue certificates", request.Signer)
}
signer = caCertificate.Certificate
}
privateKey := request.PrivateKey
if request.PublicKey != nil {
template.PublicKey = request.PublicKey
} else if privateKey == nil {
var err error
privateKey, err = GeneratePrivateKey()
if err != nil {
return nil, nil, nil, err
}
}
if request.Validity != 0 {
template.NotAfter = time.Now().Add(request.Validity).UTC()
}
certificate, err := signNewCertificate(privateKey, template, signer, caPrivateKey)
if err != nil {
return nil, nil, nil, err
}
if signer == nil {
caCertificate = certificate
}
return certificate, privateKey, caCertificate, err
}