Vault 11799 Vault CLI Re-Issue (Templating based on existing certificate) (#18499)
* The verify-sign command in it's cleanest existing form. * Working state * Updates to proper verification syntax Co-authored-by: 'Alex Scheel' <alex.scheel@hashicorp.com> * make fmt * Base functionality. * make fmt; changelog * pki issue command. * Make fmt. Changelog. * Error Handling Is Almost A Tutorial * Issue and ReIssue are Almost the Same Command * Make Fmt + Changelog. * Make some of the tests go. * make fmt * Merge fix (take 2) * Fix existing support, add support for use_pss, max_path_length, not_after, permitted_dns_domains and skid * Good Test which Fails * Test-correction. * Fix update to key_type key_bits; allow "," in OU or similar * More specific includeCNinSANs * Add tests around trying to use_pss on an ec key. * GoDoc Test Paragraph thing. --------- Co-authored-by: 'Alex Scheel' <alex.scheel@hashicorp.com>
This commit is contained in:
parent
604239a4ac
commit
674d56d9c7
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:improvement
|
||||||
|
cli/pki: Added "Reissue" command which allows extracting fields from an existing certificate to create a new certificate.
|
||||||
|
```
|
|
@ -555,6 +555,11 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) map[string]cli.Co
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
"pki reissue": func() (cli.Command, error) {
|
||||||
|
return &PKIReIssueCACommand{
|
||||||
|
BaseCommand: getBaseCommand(),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
"pki verify-sign": func() (cli.Command, error) {
|
"pki verify-sign": func() (cli.Command, error) {
|
||||||
return &PKIVerifySignCommand{
|
return &PKIVerifySignCommand{
|
||||||
BaseCommand: getBaseCommand(),
|
BaseCommand: getBaseCommand(),
|
||||||
|
|
|
@ -89,6 +89,14 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parentMountIssuer := sanitizePath(args[0]) // /pki/issuer/default
|
||||||
|
|
||||||
|
intermediateMount := sanitizePath(args[1])
|
||||||
|
|
||||||
|
return pkiIssue(c.BaseCommand, parentMountIssuer, intermediateMount, c.flagNewIssuerName, c.flagKeyStorageSource, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkiIssue(c *BaseCommand, parentMountIssuer string, intermediateMount string, flagNewIssuerName string, flagKeyStorageSource string, data map[string]interface{}) int {
|
||||||
// Check We Have a Client
|
// Check We Have a Client
|
||||||
client, err := c.Client()
|
client, err := c.Client()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -97,8 +105,6 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity Check the Parent Issuer
|
// Sanity Check the Parent Issuer
|
||||||
parentMountIssuer := sanitizePath(args[0]) // /pki/issuer/default
|
|
||||||
_, parentIssuerName := paths.Split(parentMountIssuer)
|
|
||||||
if !strings.Contains(parentMountIssuer, "/issuer/") {
|
if !strings.Contains(parentMountIssuer, "/issuer/") {
|
||||||
c.UI.Error(fmt.Sprintf("Parent Issuer %v is Not a PKI Issuer Path of the format /mount/issuer/issuer-ref", parentMountIssuer))
|
c.UI.Error(fmt.Sprintf("Parent Issuer %v is Not a PKI Issuer Path of the format /mount/issuer/issuer-ref", parentMountIssuer))
|
||||||
}
|
}
|
||||||
|
@ -108,16 +114,15 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set-up Failure State (Immediately Before First Write Call)
|
// Set-up Failure State (Immediately Before First Write Call)
|
||||||
intermediateMount := sanitizePath(args[1])
|
|
||||||
failureState := inCaseOfFailure{
|
failureState := inCaseOfFailure{
|
||||||
intermediateMount: intermediateMount,
|
intermediateMount: intermediateMount,
|
||||||
parentMount: strings.Split(parentMountIssuer, "/issuer/")[0],
|
parentMount: strings.Split(parentMountIssuer, "/issuer/")[0],
|
||||||
parentIssuer: parentMountIssuer,
|
parentIssuer: parentMountIssuer,
|
||||||
newName: c.flagNewIssuerName,
|
newName: flagNewIssuerName,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Certificate Signing Request
|
// Generate Certificate Signing Request
|
||||||
csrResp, err := client.Logical().Write(intermediateMount+"/intermediate/generate/"+c.flagKeyStorageSource, data)
|
csrResp, err := client.Logical().Write(intermediateMount+"/intermediate/generate/"+flagKeyStorageSource, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "no handler for route") { // Mount Given Does Not Exist
|
if strings.Contains(err.Error(), "no handler for route") { // Mount Given Does Not Exist
|
||||||
c.UI.Error(fmt.Sprintf("Given Intermediate Mount %v Does Not Exist: %v", intermediateMount, err))
|
c.UI.Error(fmt.Sprintf("Given Intermediate Mount %v Does Not Exist: %v", intermediateMount, err))
|
||||||
|
@ -129,21 +134,21 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
// Parse CSR Response, Also Verifies that this is a PKI Mount
|
// Parse CSR Response, Also Verifies that this is a PKI Mount
|
||||||
// (eg. calling the above call on cubbyhole/ won't return an error response)
|
// (e.g. calling the above call on cubbyhole/ won't return an error response)
|
||||||
csrPemRaw, present := csrResp.Data["csr"]
|
csrPemRaw, present := csrResp.Data["csr"]
|
||||||
if !present {
|
if !present {
|
||||||
c.UI.Error(fmt.Sprintf("Failed to Generate Intermediate CSR on %v, got response: %v", intermediateMount, csrResp))
|
c.UI.Error(fmt.Sprintf("Failed to Generate Intermediate CSR on %v, got response: %v", intermediateMount, csrResp))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
keyIdRaw, present := csrResp.Data["key_id"]
|
keyIdRaw, present := csrResp.Data["key_id"]
|
||||||
if !present && c.flagKeyStorageSource == "internal" {
|
if !present && flagKeyStorageSource == "internal" {
|
||||||
c.UI.Error(fmt.Sprintf("Failed to Generate Key on %v, got response: %v", intermediateMount, csrResp))
|
c.UI.Error(fmt.Sprintf("Failed to Generate Key on %v, got response: %v", intermediateMount, csrResp))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// If that all Parses, then we've successfully generated a CSR! Save It (and the Key-ID)
|
// If that all Parses, then we've successfully generated a CSR! Save It (and the Key-ID)
|
||||||
failureState.csrGenerated = true
|
failureState.csrGenerated = true
|
||||||
if c.flagKeyStorageSource == "internal" {
|
if flagKeyStorageSource == "internal" {
|
||||||
failureState.createdKeyId = keyIdRaw.(string)
|
failureState.createdKeyId = keyIdRaw.(string)
|
||||||
}
|
}
|
||||||
csr := csrPemRaw.(string)
|
csr := csrPemRaw.(string)
|
||||||
|
@ -171,7 +176,7 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
|
|
||||||
// Next Import Certificate
|
// Next Import Certificate
|
||||||
certificate := rootResp.Data["certificate"].(string)
|
certificate := rootResp.Data["certificate"].(string)
|
||||||
issuerId, err := importIssuerWithName(client, intermediateMount, certificate, c.flagNewIssuerName)
|
issuerId, err := importIssuerWithName(client, intermediateMount, certificate, flagNewIssuerName)
|
||||||
failureState.certIssuerId = issuerId
|
failureState.certIssuerId = issuerId
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "error naming issuer") {
|
if strings.Contains(err.Error(), "error naming issuer") {
|
||||||
|
@ -189,6 +194,7 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
|
|
||||||
// Then Import Issuing Certificate
|
// Then Import Issuing Certificate
|
||||||
issuingCa := rootResp.Data["issuing_ca"].(string)
|
issuingCa := rootResp.Data["issuing_ca"].(string)
|
||||||
|
_, parentIssuerName := paths.Split(parentMountIssuer)
|
||||||
_, err = importIssuerWithName(client, intermediateMount, issuingCa, parentIssuerName)
|
_, err = importIssuerWithName(client, intermediateMount, issuingCa, parentIssuerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "error naming issuer") {
|
if strings.Contains(err.Error(), "error naming issuer") {
|
||||||
|
@ -215,12 +221,12 @@ func (c *PKIIssueCACommand) Run(args []string) int {
|
||||||
failureState.caChainImported = true
|
failureState.caChainImported = true
|
||||||
|
|
||||||
// Finally we read our newly issued certificate in order to tell our caller about it
|
// Finally we read our newly issued certificate in order to tell our caller about it
|
||||||
c.readAndOutputNewCertificate(client, intermediateMount, issuerId)
|
readAndOutputNewCertificate(client, intermediateMount, issuerId, c)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PKIIssueCACommand) readAndOutputNewCertificate(client *api.Client, intermediateMount string, issuerId string) {
|
func readAndOutputNewCertificate(client *api.Client, intermediateMount string, issuerId string, c *BaseCommand) {
|
||||||
resp, err := client.Logical().Read(sanitizePath(intermediateMount + "/issuer/" + issuerId))
|
resp, err := client.Logical().Read(sanitizePath(intermediateMount + "/issuer/" + issuerId))
|
||||||
if err != nil || resp == nil {
|
if err != nil || resp == nil {
|
||||||
c.UI.Error(fmt.Sprintf("Error Reading Fully Imported Certificate from %v : %v",
|
c.UI.Error(fmt.Sprintf("Error Reading Fully Imported Certificate from %v : %v",
|
||||||
|
|
|
@ -0,0 +1,297 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"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
|
||||||
|
intermediateMount := sanitizePath(args[2])
|
||||||
|
|
||||||
|
templateCertificateResp, err := client.Logical().Read(sanitizePath(args[1]))
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error fetching template certificate %v : %v", sanitizePath(args[1]), err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
templateCertificateRaw, ok := templateCertificateResp.Data["certificate"]
|
||||||
|
if !ok {
|
||||||
|
c.UI.Error(fmt.Sprintf("No Certificate Field Found at %v instead found : %v", sanitizePath(args[1]), templateCertificateResp))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
certificatePemString := templateCertificateRaw.(string)
|
||||||
|
certificatePem := []byte(certificatePemString)
|
||||||
|
certificateBlock, _ := pem.Decode(certificatePem)
|
||||||
|
certificate, err := x509.ParseCertificate(certificateBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
c.UI.Error(fmt.Sprintf("Error parsing template certificate at %v : %v", sanitizePath(args[1]), err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
useExistingKey := c.flagKeyStorageSource == "existing"
|
||||||
|
keyRef := ""
|
||||||
|
if useExistingKey { // TODO: Better Error information
|
||||||
|
keyRef = templateCertificateResp.Data["key_id"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/vault/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestPKIReIssueIntermediate tests that the pki reissue command line tool accurately copies information from the
|
||||||
|
// template certificate to the newly issued certificate, by issuing and reissuing several certificates and seeing how
|
||||||
|
// they related to each other.
|
||||||
|
func TestPKIReIssueIntermediate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, closer := testVaultServer(t)
|
||||||
|
defer closer()
|
||||||
|
|
||||||
|
// Relationship Map to Create
|
||||||
|
// pki-root | pki-newroot | pki-empty
|
||||||
|
// RootX1 RootX2 RootX4 RootX3
|
||||||
|
// | |
|
||||||
|
// ----------------------------------------------
|
||||||
|
// v v
|
||||||
|
// IntX1 IntX2 pki-int
|
||||||
|
// | |
|
||||||
|
// v v
|
||||||
|
// IntX3 (-----------------------) IntX3
|
||||||
|
//
|
||||||
|
// Here X1,X2 have the same name (same mount)
|
||||||
|
// RootX4 uses the same key as RootX1 (but a different common_name/subject)
|
||||||
|
// RootX3 has the same name, and is on a different mount
|
||||||
|
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
||||||
|
createComplicatedIssuerSetUpWithReIssueIntermediate(t, client)
|
||||||
|
|
||||||
|
runPkiVerifySignTests(t, client)
|
||||||
|
|
||||||
|
runPkiListIntermediateTests(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createComplicatedIssuerSetUpWithReIssueIntermediate(t *testing.T, client *api.Client) {
|
||||||
|
// Relationship Map to Create
|
||||||
|
// pki-root | pki-newroot | pki-empty
|
||||||
|
// RootX1 RootX2 RootX4 RootX3
|
||||||
|
// | |
|
||||||
|
// ----------------------------------------------
|
||||||
|
// v v
|
||||||
|
// IntX1 IntX2 pki-int
|
||||||
|
// | |
|
||||||
|
// v v
|
||||||
|
// IntX3 (-----------------------) IntX3
|
||||||
|
//
|
||||||
|
// Here X1,X2 have the same name (same mount)
|
||||||
|
// RootX4 uses the same key as RootX1 (but a different common_name/subject)
|
||||||
|
// RootX3 has the same name, and is on a different mount
|
||||||
|
// RootX1 has issued IntX1; RootX3 has issued IntX2
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-root", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-newroot", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := client.Sys().Mount("pki-int", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{
|
||||||
|
MaxLeaseTTL: "36500d",
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to check handling empty list responses: Not Used for Any Issuers / Certificates
|
||||||
|
if err := client.Sys().Mount("pki-empty", &api.MountInput{
|
||||||
|
Type: "pki",
|
||||||
|
Config: api.MountConfigInput{},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("pki mount error: %#v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX1",
|
||||||
|
"key_name": "rootX1",
|
||||||
|
})
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = client.Logical().Write("pki-root/root/generate/internal", map[string]interface{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX2",
|
||||||
|
})
|
||||||
|
if err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp, err := client.Logical().Write("pki-newroot/root/generate/internal", map[string]interface{}{
|
||||||
|
"key_type": "ec",
|
||||||
|
"common_name": "Root X",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX3",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp, err := client.Logical().Write("pki-root/root/generate/existing", map[string]interface{}{
|
||||||
|
"common_name": "Root X4",
|
||||||
|
"ttl": "3650d",
|
||||||
|
"issuer_name": "rootX4",
|
||||||
|
"key_ref": "rootX1",
|
||||||
|
}); err != nil || resp == nil {
|
||||||
|
t.Fatalf("failed to prime CA: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout := bytes.NewBuffer(nil)
|
||||||
|
stderr := bytes.NewBuffer(nil)
|
||||||
|
runOpts := &RunOptions{
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: stderr,
|
||||||
|
Client: client,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X1
|
||||||
|
intX1CallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX1",
|
||||||
|
"pki-root/issuer/rootX1",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=rsa",
|
||||||
|
"common_name=Int X1",
|
||||||
|
"ou=thing",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut := RunCustom(intX1CallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X1, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X2 - using ReIssue
|
||||||
|
intX2CallArgs := []string{
|
||||||
|
"pki", "reissue", "-format=json", "-issuer_name=intX2",
|
||||||
|
"pki-newroot/issuer/rootX3",
|
||||||
|
"pki-int/issuer/intX1",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=ec",
|
||||||
|
"common_name=Int X2",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX2CallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X2, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intermediate X3
|
||||||
|
intX3OriginalCallArgs := []string{
|
||||||
|
"pki", "issue", "-format=json", "-issuer_name=intX3",
|
||||||
|
"pki-int/issuer/intX1",
|
||||||
|
"pki-int/",
|
||||||
|
"key_type=ec",
|
||||||
|
"use_pss=true", // This is meaningful because rootX1 is an RSA key
|
||||||
|
"signature_bits=512",
|
||||||
|
"common_name=Int X3",
|
||||||
|
"ttl=3650d",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX3OriginalCallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X3, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
intX3AdaptedCallArgs := []string{
|
||||||
|
"pki", "reissue", "-format=json", "-issuer_name=intX3also", "-type=existing",
|
||||||
|
"pki-int/issuer/intX2", // This is a EC key
|
||||||
|
"pki-int/issuer/intX3", // This template includes use_pss = true which can't be accomodated
|
||||||
|
"pki-int/",
|
||||||
|
}
|
||||||
|
codeOut = RunCustom(intX3AdaptedCallArgs, runOpts)
|
||||||
|
if codeOut != 0 {
|
||||||
|
t.Fatalf("error issuing intermediate X3also, code: %d \n stdout: %v \n stderr: %v", codeOut, stdout, stderr)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue