-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
pki_reissue_intermediate.go
290 lines (249 loc) · 8.14 KB
/
pki_reissue_intermediate.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
package command
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"fmt"
"io"
"net"
"net/url"
"os"
"strings"
"github.com/posener/complete"
)
type PKIReIssueCACommand struct {
*BaseCommand
flagConfig string
flagReturnIndicator string
flagDefaultDisabled bool
flagList bool
flagKeyStorageSource string
flagNewIssuerName string
}
func (c *PKIReIssueCACommand) Synopsis() string {
return "Uses a parent certificate and a template certificate to create a new issuer on a child mount"
}
func (c *PKIReIssueCACommand) Help() string {
helpText := `
Usage: vault pki reissue PARENT TEMPLATE CHILD_MOUNT options
`
return strings.TrimSpace(helpText)
}
func (c *PKIReIssueCACommand) Flags() *FlagSets {
set := c.flagSet(FlagSetHTTP | FlagSetOutputFormat)
f := set.NewFlagSet("Command Options")
f.StringVar(&StringVar{
Name: "type",
Target: &c.flagKeyStorageSource,
Default: "internal",
EnvVar: "",
Usage: `Options are “existing” - to use an existing key inside vault, “internal” - to generate a new key inside vault, or “kms” - to link to an external key. Exported keys are not available through this API.`,
Completion: complete.PredictSet("internal", "existing", "kms"),
})
f.StringVar(&StringVar{
Name: "issuer_name",
Target: &c.flagNewIssuerName,
Default: "",
EnvVar: "",
Usage: `If present, the newly created issuer will be given this name`,
})
return set
}
func (c *PKIReIssueCACommand) Run(args []string) int {
// Parse Args
f := c.Flags()
if err := f.Parse(args); err != nil {
c.UI.Error(err.Error())
return 1
}
args = f.Args()
if len(args) < 3 {
c.UI.Error("Not enough arguments: expected parent issuer and child-mount location and some key_value argument")
return 1
}
stdin := (io.Reader)(os.Stdin)
userData, err := parseArgsData(stdin, args[3:])
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to parse K=V data: %s", err))
return 1
}
// Check We Have a Client
client, err := c.Client()
if err != nil {
c.UI.Error(fmt.Sprintf("Failed to obtain client: %v", err))
return 1
}
parentIssuer := sanitizePath(args[0]) // /pki/issuer/default
templateIssuer := sanitizePath(args[1])
intermediateMount := sanitizePath(args[2])
templateIssuerBundle, err := readIssuer(client, templateIssuer)
if err != nil {
c.UI.Error(fmt.Sprintf("Error fetching template certificate %v : %v", templateIssuer, err))
return 1
}
certificate := templateIssuerBundle.certificate
useExistingKey := c.flagKeyStorageSource == "existing"
keyRef := ""
if useExistingKey {
keyRef = templateIssuerBundle.keyId
if keyRef == "" {
c.UI.Error(fmt.Sprintf("Template issuer %s did not have a key id field set in response which is required", templateIssuer))
return 1
}
}
templateData, err := parseTemplateCertificate(*certificate, useExistingKey, keyRef)
data := updateTemplateWithData(templateData, userData)
return pkiIssue(c.BaseCommand, parentIssuer, intermediateMount, c.flagNewIssuerName, c.flagKeyStorageSource, data)
}
func updateTemplateWithData(template map[string]interface{}, changes map[string]interface{}) map[string]interface{} {
data := map[string]interface{}{}
for key, value := range template {
data[key] = value
}
// ttl and not_after set the same thing. Delete template ttl if using not_after:
if _, ok := changes["not_after"]; ok {
delete(data, "ttl")
}
// If we are updating the key_type, do not set key_bits
if _, ok := changes["key_type"]; ok && changes["key_type"] != template["key_type"] {
delete(data, "key_bits")
}
for key, value := range changes {
data[key] = value
}
return data
}
func parseTemplateCertificate(certificate x509.Certificate, useExistingKey bool, keyRef string) (templateData map[string]interface{}, err error) {
// Generate Certificate Signing Parameters
templateData = map[string]interface{}{
"common_name": certificate.Subject.CommonName,
"alt_names": makeAltNamesCommaSeparatedString(certificate.DNSNames, certificate.EmailAddresses),
"ip_sans": makeIpAddressCommaSeparatedString(certificate.IPAddresses),
"uri_sans": makeUriCommaSeparatedString(certificate.URIs),
// other_sans (string: "") - Specifies custom OID/UTF8-string SANs. These must match values specified on the role in allowed_other_sans (see role creation for allowed_other_sans globbing rules). The format is the same as OpenSSL: <oid>;<type>:<value> where the only current valid type is UTF8. This can be a comma-delimited list or a JSON string slice.
// Punting on Other_SANs, shouldn't really be on CAs
"signature_bits": findSignatureBits(certificate.SignatureAlgorithm),
"exclude_cn_from_sans": determineExcludeCnFromSans(certificate),
"ou": certificate.Subject.OrganizationalUnit,
"organization": certificate.Subject.Organization,
"country": certificate.Subject.Country,
"locality": certificate.Subject.Locality,
"province": certificate.Subject.Province,
"street_address": certificate.Subject.StreetAddress,
"postal_code": certificate.Subject.PostalCode,
"serial_number": certificate.Subject.SerialNumber,
"ttl": (certificate.NotAfter.Sub(certificate.NotBefore)).String(),
"max_path_length": certificate.MaxPathLen,
"permitted_dns_domains": strings.Join(certificate.PermittedDNSDomains, ","),
"use_pss": isPSS(certificate.SignatureAlgorithm),
}
if useExistingKey {
templateData["skid"] = hex.EncodeToString(certificate.SubjectKeyId) // TODO: Double Check this with someone
if keyRef == "" {
return nil, fmt.Errorf("unable to create certificate template for existing key without a key_id")
}
templateData["key_ref"] = keyRef
} else {
templateData["key_type"] = getKeyType(certificate.PublicKeyAlgorithm.String())
templateData["key_bits"] = findBitLength(certificate.PublicKey)
}
return templateData, nil
}
func isPSS(algorithm x509.SignatureAlgorithm) bool {
switch algorithm {
case x509.SHA384WithRSAPSS, x509.SHA512WithRSAPSS, x509.SHA256WithRSAPSS:
return true
default:
return false
}
}
func makeAltNamesCommaSeparatedString(names []string, emails []string) string {
return strings.Join(names, ",") + "," + strings.Join(emails, ",")
}
func makeUriCommaSeparatedString(uris []*url.URL) string {
stringAddresses := make([]string, len(uris))
for i, uri := range uris {
stringAddresses[i] = uri.String()
}
return strings.Join(stringAddresses, ",")
}
func makeIpAddressCommaSeparatedString(addresses []net.IP) string {
stringAddresses := make([]string, len(addresses))
for i, address := range addresses {
stringAddresses[i] = address.String()
}
return strings.Join(stringAddresses, ",")
}
func determineExcludeCnFromSans(certificate x509.Certificate) bool {
cn := certificate.Subject.CommonName
if cn == "" {
return false
}
emails := certificate.EmailAddresses
for _, email := range emails {
if email == cn {
return false
}
}
dnses := certificate.DNSNames
for _, dns := range dnses {
if dns == cn {
return false
}
}
return true
}
func findBitLength(publicKey any) int {
if publicKey == nil {
return 0
}
switch pub := publicKey.(type) {
case *rsa.PublicKey:
return pub.N.BitLen()
case *ecdsa.PublicKey:
switch pub.Curve {
case elliptic.P224():
return 224
case elliptic.P256():
return 256
case elliptic.P384():
return 384
case elliptic.P521():
return 521
default:
return 0
}
default:
return 0
}
}
func findSignatureBits(algo x509.SignatureAlgorithm) int {
switch algo {
case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.DSAWithSHA1, x509.ECDSAWithSHA1:
return -1
case x509.SHA256WithRSA, x509.DSAWithSHA256, x509.ECDSAWithSHA256, x509.SHA256WithRSAPSS:
return 256
case x509.SHA384WithRSA, x509.ECDSAWithSHA384, x509.SHA384WithRSAPSS:
return 384
case x509.SHA512WithRSA, x509.SHA512WithRSAPSS, x509.ECDSAWithSHA512:
return 512
case x509.PureEd25519:
return 0
default:
return -1
}
}
func getKeyType(goKeyType string) string {
switch goKeyType {
case "RSA":
return "rsa"
case "ECDSA":
return "ec"
case "Ed25519":
return "ed25519"
default:
return ""
}
}