mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-08 07:37:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
294 lines
8.2 KiB
Go
294 lines
8.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
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 ""
|
|
}
|
|
}
|