mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 02:02:43 +00:00 
			
		
		
		
	 7049424c16
			
		
	
	7049424c16
	
	
	
		
			
			- We are leveraging this new feature flag to ignore the CSR's signature as we are constructing a CSR based on the information from a CMPv2 message.
		
			
				
	
	
		
			788 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			788 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) HashiCorp, Inc.
 | |
| // SPDX-License-Identifier: BUSL-1.1
 | |
| 
 | |
| package pki
 | |
| 
 | |
| import (
 | |
| 	"crypto"
 | |
| 	"crypto/x509"
 | |
| 	"crypto/x509/pkix"
 | |
| 	"encoding/asn1"
 | |
| 	"encoding/base64"
 | |
| 	"encoding/pem"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"math/big"
 | |
| 	"net"
 | |
| 	"net/url"
 | |
| 	"regexp"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/hashicorp/vault/builtin/logical/pki/issuing"
 | |
| 	"github.com/hashicorp/vault/builtin/logical/pki/parsing"
 | |
| 	"github.com/hashicorp/vault/builtin/logical/pki/pki_backend"
 | |
| 	"github.com/hashicorp/vault/sdk/framework"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/certutil"
 | |
| 	"github.com/hashicorp/vault/sdk/helper/errutil"
 | |
| 	"github.com/hashicorp/vault/sdk/logical"
 | |
| 	"golang.org/x/crypto/cryptobyte"
 | |
| 	cbbasn1 "golang.org/x/crypto/cryptobyte/asn1"
 | |
| )
 | |
| 
 | |
| type inputBundle struct {
 | |
| 	role    *issuing.RoleEntry
 | |
| 	req     *logical.Request
 | |
| 	apiData *framework.FieldData
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	// labelRegex is a single label from a valid domain name and was extracted
 | |
| 	// from hostnameRegex below for use in leftWildLabelRegex, without any
 | |
| 	// label separators (`.`).
 | |
| 	labelRegex = `([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])`
 | |
| 
 | |
| 	// A note on hostnameRegex: although we set the StrictDomainName option
 | |
| 	// when doing the idna conversion, this appears to only affect output, not
 | |
| 	// input, so it will allow e.g. host^123.example.com straight through. So
 | |
| 	// we still need to use this to check the output.
 | |
| 	hostnameRegex = regexp.MustCompile(`^(\*\.)?(` + labelRegex + `\.)*` + labelRegex + `\.?$`)
 | |
| 
 | |
| 	// Left Wildcard Label Regex is equivalent to a single domain label
 | |
| 	// component from hostnameRegex above, but with additional wildcard
 | |
| 	// characters added. There are four possibilities here:
 | |
| 	//
 | |
| 	//  1. Entire label is a wildcard,
 | |
| 	//  2. Wildcard exists at the start,
 | |
| 	//  3. Wildcard exists at the end,
 | |
| 	//  4. Wildcard exists in the middle.
 | |
| 	allWildRegex       = `\*`
 | |
| 	startWildRegex     = `\*` + labelRegex
 | |
| 	endWildRegex       = labelRegex + `\*`
 | |
| 	middleWildRegex    = labelRegex + `\*` + labelRegex
 | |
| 	leftWildLabelRegex = regexp.MustCompile(`^(` + allWildRegex + `|` + startWildRegex + `|` + endWildRegex + `|` + middleWildRegex + `)$`)
 | |
| 
 | |
| 	// Cloned from https://github.com/golang/go/blob/82c713feb05da594567631972082af2fcba0ee4f/src/crypto/x509/x509.go#L327-L379
 | |
| 	oidSignatureMD2WithRSA      = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 2}
 | |
| 	oidSignatureMD5WithRSA      = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 4}
 | |
| 	oidSignatureSHA1WithRSA     = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 5}
 | |
| 	oidSignatureSHA256WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
 | |
| 	oidSignatureSHA384WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
 | |
| 	oidSignatureSHA512WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
 | |
| 	oidSignatureRSAPSS          = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 10}
 | |
| 	oidSignatureDSAWithSHA1     = asn1.ObjectIdentifier{1, 2, 840, 10040, 4, 3}
 | |
| 	oidSignatureDSAWithSHA256   = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 3, 2}
 | |
| 	oidSignatureECDSAWithSHA1   = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 1}
 | |
| 	oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
 | |
| 	oidSignatureECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
 | |
| 	oidSignatureECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
 | |
| 	oidSignatureEd25519         = asn1.ObjectIdentifier{1, 3, 101, 112}
 | |
| 	oidISOSignatureSHA1WithRSA  = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 29}
 | |
| 
 | |
| 	signatureAlgorithmDetails = []struct {
 | |
| 		algo       x509.SignatureAlgorithm
 | |
| 		name       string
 | |
| 		oid        asn1.ObjectIdentifier
 | |
| 		pubKeyAlgo x509.PublicKeyAlgorithm
 | |
| 		hash       crypto.Hash
 | |
| 	}{
 | |
| 		{x509.MD2WithRSA, "MD2-RSA", oidSignatureMD2WithRSA, x509.RSA, crypto.Hash(0) /* no value for MD2 */},
 | |
| 		{x509.MD5WithRSA, "MD5-RSA", oidSignatureMD5WithRSA, x509.RSA, crypto.MD5},
 | |
| 		{x509.SHA1WithRSA, "SHA1-RSA", oidSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
 | |
| 		{x509.SHA1WithRSA, "SHA1-RSA", oidISOSignatureSHA1WithRSA, x509.RSA, crypto.SHA1},
 | |
| 		{x509.SHA256WithRSA, "SHA256-RSA", oidSignatureSHA256WithRSA, x509.RSA, crypto.SHA256},
 | |
| 		{x509.SHA384WithRSA, "SHA384-RSA", oidSignatureSHA384WithRSA, x509.RSA, crypto.SHA384},
 | |
| 		{x509.SHA512WithRSA, "SHA512-RSA", oidSignatureSHA512WithRSA, x509.RSA, crypto.SHA512},
 | |
| 		{x509.SHA256WithRSAPSS, "SHA256-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA256},
 | |
| 		{x509.SHA384WithRSAPSS, "SHA384-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA384},
 | |
| 		{x509.SHA512WithRSAPSS, "SHA512-RSAPSS", oidSignatureRSAPSS, x509.RSA, crypto.SHA512},
 | |
| 		{x509.DSAWithSHA1, "DSA-SHA1", oidSignatureDSAWithSHA1, x509.DSA, crypto.SHA1},
 | |
| 		{x509.DSAWithSHA256, "DSA-SHA256", oidSignatureDSAWithSHA256, x509.DSA, crypto.SHA256},
 | |
| 		{x509.ECDSAWithSHA1, "ECDSA-SHA1", oidSignatureECDSAWithSHA1, x509.ECDSA, crypto.SHA1},
 | |
| 		{x509.ECDSAWithSHA256, "ECDSA-SHA256", oidSignatureECDSAWithSHA256, x509.ECDSA, crypto.SHA256},
 | |
| 		{x509.ECDSAWithSHA384, "ECDSA-SHA384", oidSignatureECDSAWithSHA384, x509.ECDSA, crypto.SHA384},
 | |
| 		{x509.ECDSAWithSHA512, "ECDSA-SHA512", oidSignatureECDSAWithSHA512, x509.ECDSA, crypto.SHA512},
 | |
| 		{x509.PureEd25519, "Ed25519", oidSignatureEd25519, x509.Ed25519, crypto.Hash(0) /* no pre-hashing */},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func doesPublicKeyAlgoMatchSignatureAlgo(pubKey x509.PublicKeyAlgorithm, algo x509.SignatureAlgorithm) bool {
 | |
| 	for _, detail := range signatureAlgorithmDetails {
 | |
| 		if detail.algo == algo {
 | |
| 			return pubKey == detail.pubKeyAlgo
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func getFormat(data *framework.FieldData) string {
 | |
| 	format := data.Get("format").(string)
 | |
| 	switch format {
 | |
| 	case "pem":
 | |
| 	case "der":
 | |
| 	case "pem_bundle":
 | |
| 	default:
 | |
| 		format = ""
 | |
| 	}
 | |
| 	return format
 | |
| }
 | |
| 
 | |
| // fetchCAInfo will fetch the CA info, will return an error if no ca info exists, this does NOT support
 | |
| // loading using the legacyBundleShimID and should be used with care. This should be called only once
 | |
| // within the request path otherwise you run the risk of a race condition with the issuer migration on perf-secondaries.
 | |
| func (sc *storageContext) fetchCAInfo(issuerRef string, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, error) {
 | |
| 	bundle, _, err := sc.fetchCAInfoWithIssuer(issuerRef, usage)
 | |
| 	return bundle, err
 | |
| }
 | |
| 
 | |
| func (sc *storageContext) fetchCAInfoWithIssuer(issuerRef string, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, issuing.IssuerID, error) {
 | |
| 	var issuerId issuing.IssuerID
 | |
| 
 | |
| 	if sc.UseLegacyBundleCaStorage() {
 | |
| 		// We have not completed the migration so attempt to load the bundle from the legacy location
 | |
| 		sc.Logger().Info("Using legacy CA bundle as PKI migration has not completed.")
 | |
| 		issuerId = legacyBundleShimID
 | |
| 	} else {
 | |
| 		var err error
 | |
| 		issuerId, err = sc.resolveIssuerReference(issuerRef)
 | |
| 		if err != nil {
 | |
| 			// Usually a bad label from the user or mis-configured default.
 | |
| 			return nil, issuing.IssuerRefNotFound, errutil.UserError{Err: err.Error()}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	bundle, err := sc.fetchCAInfoByIssuerId(issuerId, usage)
 | |
| 	if err != nil {
 | |
| 		return nil, issuing.IssuerRefNotFound, err
 | |
| 	}
 | |
| 
 | |
| 	return bundle, issuerId, nil
 | |
| }
 | |
| 
 | |
| // fetchCAInfoByIssuerId will fetch the CA info, will return an error if no ca info exists for the given issuerId.
 | |
| // This does support the loading using the legacyBundleShimID
 | |
| func (sc *storageContext) fetchCAInfoByIssuerId(issuerId issuing.IssuerID, usage issuing.IssuerUsage) (*certutil.CAInfoBundle, error) {
 | |
| 	return issuing.FetchCAInfoByIssuerId(sc.Context, sc.Storage, sc.GetPkiManagedView(), issuerId, usage)
 | |
| }
 | |
| 
 | |
| func fetchCertBySerialBigInt(sc *storageContext, prefix string, serial *big.Int) (*logical.StorageEntry, error) {
 | |
| 	return fetchCertBySerial(sc, prefix, serialFromBigInt(serial))
 | |
| }
 | |
| 
 | |
| // fetchCertBySerial allows fetching certificates from the backend; it handles the slightly
 | |
| // separate pathing for CRL, and revoked certificates.
 | |
| //
 | |
| // Support for fetching CA certificates was removed, due to the new issuers
 | |
| // changes.
 | |
| func fetchCertBySerial(sc pki_backend.StorageContext, prefix, serial string) (*logical.StorageEntry, error) {
 | |
| 	var path, legacyPath string
 | |
| 	var err error
 | |
| 	var certEntry *logical.StorageEntry
 | |
| 
 | |
| 	hyphenSerial := parsing.NormalizeSerialForStorage(serial)
 | |
| 	colonSerial := strings.ReplaceAll(strings.ToLower(serial), "-", ":")
 | |
| 
 | |
| 	switch {
 | |
| 	// Revoked goes first as otherwise crl get hardcoded paths which fail if
 | |
| 	// we actually want revocation info
 | |
| 	case strings.HasPrefix(prefix, "revoked/"):
 | |
| 		legacyPath = "revoked/" + colonSerial
 | |
| 		path = "revoked/" + hyphenSerial
 | |
| 	case serial == issuing.LegacyCRLPath || serial == issuing.DeltaCRLPath || serial == issuing.UnifiedCRLPath || serial == issuing.UnifiedDeltaCRLPath:
 | |
| 		warnings, err := sc.CrlBuilder().RebuildIfForced(sc)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		if len(warnings) > 0 {
 | |
| 			msg := "During rebuild of CRL for cert fetch, got the following warnings:"
 | |
| 			for index, warning := range warnings {
 | |
| 				msg = fmt.Sprintf("%v\n %d. %v", msg, index+1, warning)
 | |
| 			}
 | |
| 			sc.Logger().Warn(msg)
 | |
| 		}
 | |
| 
 | |
| 		unified := serial == issuing.UnifiedCRLPath || serial == issuing.UnifiedDeltaCRLPath
 | |
| 		path, err = issuing.ResolveIssuerCRLPath(sc.GetContext(), sc.GetStorage(), sc.UseLegacyBundleCaStorage(), issuing.DefaultRef, unified)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if serial == issuing.DeltaCRLPath || serial == issuing.UnifiedDeltaCRLPath {
 | |
| 			if sc.UseLegacyBundleCaStorage() {
 | |
| 				return nil, fmt.Errorf("refusing to serve delta CRL with legacy CA bundle")
 | |
| 			}
 | |
| 
 | |
| 			path += issuing.DeltaCRLPathSuffix
 | |
| 		}
 | |
| 	default:
 | |
| 		legacyPath = issuing.PathCerts + colonSerial
 | |
| 		path = issuing.PathCerts + hyphenSerial
 | |
| 	}
 | |
| 
 | |
| 	certEntry, err = sc.GetStorage().Get(sc.GetContext(), path)
 | |
| 	if err != nil {
 | |
| 		return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching certificate %s: %s", serial, err)}
 | |
| 	}
 | |
| 	if certEntry != nil {
 | |
| 		if certEntry.Value == nil || len(certEntry.Value) == 0 {
 | |
| 			return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
 | |
| 		}
 | |
| 		return certEntry, nil
 | |
| 	}
 | |
| 
 | |
| 	// If legacyPath is unset, it's going to be a CA or CRL; return immediately
 | |
| 	if legacyPath == "" {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 
 | |
| 	// Retrieve the old-style path.  We disregard errors here because they
 | |
| 	// always manifest on Windows, and thus the initial check for a revoked
 | |
| 	// cert fails would return an error when the cert isn't revoked, preventing
 | |
| 	// the happy path from working.
 | |
| 	certEntry, _ = sc.GetStorage().Get(sc.GetContext(), legacyPath)
 | |
| 	if certEntry == nil {
 | |
| 		return nil, nil
 | |
| 	}
 | |
| 	if certEntry.Value == nil || len(certEntry.Value) == 0 {
 | |
| 		return nil, errutil.InternalError{Err: fmt.Sprintf("returned certificate bytes for serial %s were empty", serial)}
 | |
| 	}
 | |
| 
 | |
| 	// Update old-style paths to new-style paths
 | |
| 	certEntry.Key = path
 | |
| 	certCounter := sc.GetCertificateCounter()
 | |
| 	certsCounted := certCounter.IsInitialized()
 | |
| 	if err = sc.GetStorage().Put(sc.GetContext(), certEntry); err != nil {
 | |
| 		return nil, errutil.InternalError{Err: fmt.Sprintf("error saving certificate with serial %s to new location: %s", serial, err)}
 | |
| 	}
 | |
| 	if err = sc.GetStorage().Delete(sc.GetContext(), legacyPath); err != nil {
 | |
| 		// If we fail here, we have an extra (copy) of a cert in storage, add to metrics:
 | |
| 		switch {
 | |
| 		case strings.HasPrefix(prefix, "revoked/"):
 | |
| 			certCounter.IncrementTotalRevokedCertificatesCount(certsCounted, path)
 | |
| 		default:
 | |
| 			certCounter.IncrementTotalCertificatesCount(certsCounted, path)
 | |
| 		}
 | |
| 		return nil, errutil.InternalError{Err: fmt.Sprintf("error deleting certificate with serial %s from old location", serial)}
 | |
| 	}
 | |
| 
 | |
| 	return certEntry, nil
 | |
| }
 | |
| 
 | |
| // Given a URI SAN, verify that it is allowed.
 | |
| func validateURISAN(b *backend, data *inputBundle, uri string) bool {
 | |
| 	entityInfo := issuing.NewEntityInfoFromReq(data.req)
 | |
| 	return issuing.ValidateURISAN(b.System(), data.role, entityInfo, uri)
 | |
| }
 | |
| 
 | |
| // Validates a given common name, ensuring it's either an email or a hostname
 | |
| // after validating it according to the role parameters, or disables
 | |
| // validation altogether.
 | |
| func validateCommonName(b *backend, data *inputBundle, name string) string {
 | |
| 	entityInfo := issuing.NewEntityInfoFromReq(data.req)
 | |
| 	return issuing.ValidateCommonName(b.System(), data.role, entityInfo, name)
 | |
| }
 | |
| 
 | |
| func isWildcardDomain(name string) bool {
 | |
| 	return issuing.IsWildcardDomain(name)
 | |
| }
 | |
| 
 | |
| func validateWildcardDomain(name string) (string, string, error) {
 | |
| 	return issuing.ValidateWildcardDomain(name)
 | |
| }
 | |
| 
 | |
| // Given a set of requested names for a certificate, verifies that all of them
 | |
| // match the various toggles set in the role for controlling issuance.
 | |
| // If one does not pass, it is returned in the string argument.
 | |
| func validateNames(b *backend, data *inputBundle, names []string) string {
 | |
| 	entityInfo := issuing.NewEntityInfoFromReq(data.req)
 | |
| 	return issuing.ValidateNames(b.System(), data.role, entityInfo, names)
 | |
| }
 | |
| 
 | |
| // validateOtherSANs checks if the values requested are allowed. If an OID
 | |
| // isn't allowed, it will be returned as the first string. If a value isn't
 | |
| // allowed, it will be returned as the second string. Empty strings + error
 | |
| // means everything is okay.
 | |
| func validateOtherSANs(data *inputBundle, requested map[string][]string) (string, string, error) {
 | |
| 	return issuing.ValidateOtherSANs(data.role, requested)
 | |
| }
 | |
| 
 | |
| func parseOtherSANs(others []string) (map[string][]string, error) {
 | |
| 	return issuing.ParseOtherSANs(others)
 | |
| }
 | |
| 
 | |
| // Returns bool stating whether the given UserId is Valid
 | |
| func validateUserId(data *inputBundle, userId string) bool {
 | |
| 	return issuing.ValidateUserId(data.role, userId)
 | |
| }
 | |
| 
 | |
| func validateSerialNumber(data *inputBundle, serialNumber string) string {
 | |
| 	return issuing.ValidateSerialNumber(data.role, serialNumber)
 | |
| }
 | |
| 
 | |
| func generateCert(sc *storageContext,
 | |
| 	input *inputBundle,
 | |
| 	caSign *certutil.CAInfoBundle,
 | |
| 	isCA bool,
 | |
| 	randomSource io.Reader) (*certutil.ParsedCertBundle, []string, error,
 | |
| ) {
 | |
| 	ctx := sc.Context
 | |
| 
 | |
| 	if input.role == nil {
 | |
| 		return nil, nil, errutil.InternalError{Err: "no role found in data bundle"}
 | |
| 	}
 | |
| 
 | |
| 	if input.role.KeyType == "rsa" && input.role.KeyBits < 2048 {
 | |
| 		return nil, nil, errutil.UserError{Err: "RSA keys < 2048 bits are unsafe and not supported"}
 | |
| 	}
 | |
| 
 | |
| 	data, warnings, err := generateCreationBundle(sc.System(), input, caSign, nil)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	if data.Params == nil {
 | |
| 		return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"}
 | |
| 	}
 | |
| 
 | |
| 	if isCA {
 | |
| 		data.Params.IsCA = isCA
 | |
| 		data.Params.PermittedDNSDomains = input.apiData.Get("permitted_dns_domains").([]string)
 | |
| 
 | |
| 		if data.SigningBundle == nil {
 | |
| 			// Generating a self-signed root certificate. Since we have no
 | |
| 			// issuer entry yet, we default to the global URLs.
 | |
| 			entries, err := getGlobalAIAURLs(ctx, sc.Storage)
 | |
| 			if err != nil {
 | |
| 				return nil, nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch AIA URL information: %v", err)}
 | |
| 			}
 | |
| 
 | |
| 			uris, err := ToURLEntries(sc, issuing.IssuerID(""), entries)
 | |
| 			if err != nil {
 | |
| 				// When generating root issuers, don't err on missing issuer
 | |
| 				// ID; there is little value in including AIA info on a root,
 | |
| 				// as this info would point back to itself; though RFC 5280 is
 | |
| 				// a touch vague on this point, this seems to be consensus
 | |
| 				// from public CAs such as DigiCert Global Root G3, ISRG Root
 | |
| 				// X1, and others.
 | |
| 				//
 | |
| 				// This is a UX bug if we do err here, as it requires AIA
 | |
| 				// templating to not include issuer id (a best practice for
 | |
| 				// child certs issued from root and intermediate mounts
 | |
| 				// however), and setting this before root generation (or, on
 | |
| 				// root renewal) could cause problems.
 | |
| 				if _, nonEmptyIssuerErr := ToURLEntries(sc, issuing.IssuerID("empty-issuer-id"), entries); nonEmptyIssuerErr != nil {
 | |
| 					return nil, nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse AIA URL information: %v\nUsing templated AIA URL's {{issuer_id}} field when generating root certificates is not supported.", err)}
 | |
| 				}
 | |
| 
 | |
| 				uris = &certutil.URLEntries{}
 | |
| 
 | |
| 				msg := "When generating root CA, found global AIA configuration with issuer_id template unsuitable for root generation. This AIA configuration has been ignored. To include AIA on this root CA, set the global AIA configuration to not include issuer_id and instead to refer to a static issuer name."
 | |
| 				warnings = append(warnings, msg)
 | |
| 			}
 | |
| 
 | |
| 			data.Params.URLs = uris
 | |
| 
 | |
| 			if input.role.MaxPathLength == nil {
 | |
| 				data.Params.MaxPathLength = -1
 | |
| 			} else {
 | |
| 				data.Params.MaxPathLength = *input.role.MaxPathLength
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	parsedBundle, err := generateCABundle(sc, input, data, randomSource)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	return parsedBundle, warnings, nil
 | |
| }
 | |
| 
 | |
| // N.B.: This is only meant to be used for generating intermediate CAs.
 | |
| // It skips some sanity checks.
 | |
| func generateIntermediateCSR(sc *storageContext, input *inputBundle, randomSource io.Reader) (*certutil.ParsedCSRBundle, []string, error) {
 | |
| 	creation, warnings, err := generateCreationBundle(sc.System(), input, nil, nil)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 	if creation.Params == nil {
 | |
| 		return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"}
 | |
| 	}
 | |
| 
 | |
| 	addBasicConstraints := input.apiData != nil && input.apiData.Get("add_basic_constraints").(bool)
 | |
| 	parsedBundle, err := generateCSRBundle(sc, input, creation, addBasicConstraints, randomSource)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	return parsedBundle, warnings, nil
 | |
| }
 | |
| 
 | |
| func NewSignCertInputFromDataFields(data *framework.FieldData, isCA bool, useCSRValues bool) SignCertInputFromDataFields {
 | |
| 	certBundle := NewCreationBundleInputFromFieldData(data)
 | |
| 	return SignCertInputFromDataFields{
 | |
| 		CreationBundleInputFromFieldData: certBundle,
 | |
| 		data:                             data,
 | |
| 		isCA:                             isCA,
 | |
| 		useCSRValues:                     useCSRValues,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type SignCertInputFromDataFields struct {
 | |
| 	CreationBundleInputFromFieldData
 | |
| 	data         *framework.FieldData
 | |
| 	isCA         bool
 | |
| 	useCSRValues bool
 | |
| }
 | |
| 
 | |
| var _ issuing.SignCertInput = SignCertInputFromDataFields{}
 | |
| 
 | |
| func (i SignCertInputFromDataFields) GetCSR() (*x509.CertificateRequest, error) {
 | |
| 	csrString := i.data.Get("csr").(string)
 | |
| 	if csrString == "" {
 | |
| 		return nil, errutil.UserError{Err: "\"csr\" is empty"}
 | |
| 	}
 | |
| 
 | |
| 	pemBlock, _ := pem.Decode([]byte(csrString))
 | |
| 	if pemBlock == nil {
 | |
| 		return nil, errutil.UserError{Err: "csr contains no data"}
 | |
| 	}
 | |
| 	csr, err := x509.ParseCertificateRequest(pemBlock.Bytes)
 | |
| 	if err != nil {
 | |
| 		return nil, errutil.UserError{Err: fmt.Sprintf("certificate request could not be parsed: %v", err)}
 | |
| 	}
 | |
| 
 | |
| 	return csr, nil
 | |
| }
 | |
| 
 | |
| func (i SignCertInputFromDataFields) IsCA() bool {
 | |
| 	return i.isCA
 | |
| }
 | |
| 
 | |
| func (i SignCertInputFromDataFields) UseCSRValues() bool {
 | |
| 	return i.useCSRValues
 | |
| }
 | |
| 
 | |
| func (i SignCertInputFromDataFields) GetPermittedDomains() []string {
 | |
| 	return i.data.Get("permitted_dns_domains").([]string)
 | |
| }
 | |
| 
 | |
| func (i SignCertInputFromDataFields) IgnoreCSRSignature() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func signCert(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle, isCA bool, useCSRValues bool) (*certutil.ParsedCertBundle, []string, error) {
 | |
| 	if data.role == nil {
 | |
| 		return nil, nil, errutil.InternalError{Err: "no role found in data bundle"}
 | |
| 	}
 | |
| 
 | |
| 	entityInfo := issuing.NewEntityInfoFromReq(data.req)
 | |
| 	signCertInput := NewSignCertInputFromDataFields(data.apiData, isCA, useCSRValues)
 | |
| 
 | |
| 	return issuing.SignCert(sysView, data.role, entityInfo, caSign, signCertInput)
 | |
| }
 | |
| 
 | |
| func getOtherSANsFromX509Extensions(exts []pkix.Extension) ([]certutil.OtherNameUtf8, error) {
 | |
| 	return certutil.GetOtherSANsFromX509Extensions(exts)
 | |
| }
 | |
| 
 | |
| var _ issuing.CreationBundleInput = CreationBundleInputFromFieldData{}
 | |
| 
 | |
| func NewCreationBundleInputFromFieldData(data *framework.FieldData) CreationBundleInputFromFieldData {
 | |
| 	certNotAfter := NewCertNotAfterInputFromFieldData(data)
 | |
| 	return CreationBundleInputFromFieldData{
 | |
| 		CertNotAfterInputFromFieldData: certNotAfter,
 | |
| 		data:                           data,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type CreationBundleInputFromFieldData struct {
 | |
| 	CertNotAfterInputFromFieldData
 | |
| 	data *framework.FieldData
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) IgnoreCSRSignature() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetCommonName() string {
 | |
| 	return cb.data.Get("common_name").(string)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetSerialNumber() string {
 | |
| 	return cb.data.Get("serial_number").(string)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetExcludeCnFromSans() bool {
 | |
| 	return cb.data.Get("exclude_cn_from_sans").(bool)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetOptionalAltNames() (interface{}, bool) {
 | |
| 	return cb.data.GetOk("alt_names")
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetOtherSans() []string {
 | |
| 	return cb.data.Get("other_sans").([]string)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetIpSans() []string {
 | |
| 	return cb.data.Get("ip_sans").([]string)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetURISans() []string {
 | |
| 	return cb.data.Get("uri_sans").([]string)
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetOptionalSkid() (interface{}, bool) {
 | |
| 	return cb.data.GetOk("skid")
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) IsUserIdInSchema() (interface{}, bool) {
 | |
| 	val, present := cb.data.Schema["user_ids"]
 | |
| 	return val, present
 | |
| }
 | |
| 
 | |
| func (cb CreationBundleInputFromFieldData) GetUserIds() []string {
 | |
| 	return cb.data.Get("user_ids").([]string)
 | |
| }
 | |
| 
 | |
| // generateCreationBundle is a shared function that reads parameters supplied
 | |
| // from the various endpoints and generates a CreationParameters with the
 | |
| // parameters that can be used to issue or sign
 | |
| func generateCreationBundle(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle, csr *x509.CertificateRequest) (*certutil.CreationBundle, []string, error) {
 | |
| 	entityInfo := issuing.NewEntityInfoFromReq(data.req)
 | |
| 	creationBundleInput := NewCreationBundleInputFromFieldData(data.apiData)
 | |
| 
 | |
| 	return issuing.GenerateCreationBundle(sysView, data.role, entityInfo, creationBundleInput, caSign, csr)
 | |
| }
 | |
| 
 | |
| // getCertificateNotAfter compute a certificate's NotAfter date based on the mount ttl, role, signing bundle and input
 | |
| // api data being sent. Returns a NotAfter time, a set of warnings or an error.
 | |
| func getCertificateNotAfter(sysView logical.SystemView, data *inputBundle, caSign *certutil.CAInfoBundle) (time.Time, []string, error) {
 | |
| 	input := NewCertNotAfterInputFromFieldData(data.apiData)
 | |
| 	return issuing.GetCertificateNotAfter(sysView, data.role, input, caSign)
 | |
| }
 | |
| 
 | |
| // applyIssuerLeafNotAfterBehavior resets a certificate's notAfter time or errors out based on the
 | |
| // issuer's notAfter date along with the LeafNotAfterBehavior configuration
 | |
| func applyIssuerLeafNotAfterBehavior(caSign *certutil.CAInfoBundle, notAfter time.Time) (time.Time, error) {
 | |
| 	return issuing.ApplyIssuerLeafNotAfterBehavior(caSign, notAfter)
 | |
| }
 | |
| 
 | |
| func convertRespToPKCS8(resp *logical.Response) error {
 | |
| 	privRaw, ok := resp.Data["private_key"]
 | |
| 	if !ok {
 | |
| 		return nil
 | |
| 	}
 | |
| 	priv, ok := privRaw.(string)
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("error converting response to pkcs8: could not parse original value as string")
 | |
| 	}
 | |
| 
 | |
| 	privKeyTypeRaw, ok := resp.Data["private_key_type"]
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("error converting response to pkcs8: %q not found in response", "private_key_type")
 | |
| 	}
 | |
| 	privKeyType, ok := privKeyTypeRaw.(certutil.PrivateKeyType)
 | |
| 	if !ok {
 | |
| 		return fmt.Errorf("error converting response to pkcs8: could not parse original type value as string")
 | |
| 	}
 | |
| 
 | |
| 	var keyData []byte
 | |
| 	var pemUsed bool
 | |
| 	var err error
 | |
| 	var signer crypto.Signer
 | |
| 
 | |
| 	block, _ := pem.Decode([]byte(priv))
 | |
| 	if block == nil {
 | |
| 		keyData, err = base64.StdEncoding.DecodeString(priv)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error converting response to pkcs8: error decoding original value: %w", err)
 | |
| 		}
 | |
| 	} else {
 | |
| 		keyData = block.Bytes
 | |
| 		pemUsed = true
 | |
| 	}
 | |
| 
 | |
| 	switch privKeyType {
 | |
| 	case certutil.RSAPrivateKey:
 | |
| 		signer, err = x509.ParsePKCS1PrivateKey(keyData)
 | |
| 	case certutil.ECPrivateKey:
 | |
| 		signer, err = x509.ParseECPrivateKey(keyData)
 | |
| 	case certutil.Ed25519PrivateKey:
 | |
| 		k, err := x509.ParsePKCS8PrivateKey(keyData)
 | |
| 		if err != nil {
 | |
| 			return fmt.Errorf("error converting response to pkcs8: error parsing previous key: %w", err)
 | |
| 		}
 | |
| 		signer = k.(crypto.Signer)
 | |
| 	default:
 | |
| 		return fmt.Errorf("unknown private key type %q", privKeyType)
 | |
| 	}
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error converting response to pkcs8: error parsing previous key: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	keyData, err = x509.MarshalPKCS8PrivateKey(signer)
 | |
| 	if err != nil {
 | |
| 		return fmt.Errorf("error converting response to pkcs8: error marshaling pkcs8 key: %w", err)
 | |
| 	}
 | |
| 
 | |
| 	if pemUsed {
 | |
| 		block.Type = "PRIVATE KEY"
 | |
| 		block.Bytes = keyData
 | |
| 		resp.Data["private_key"] = strings.TrimSpace(string(pem.EncodeToMemory(block)))
 | |
| 	} else {
 | |
| 		resp.Data["private_key"] = base64.StdEncoding.EncodeToString(keyData)
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func handleOtherCSRSANs(in *x509.CertificateRequest, sans map[string][]string) error {
 | |
| 	certTemplate := &x509.Certificate{
 | |
| 		DNSNames:       in.DNSNames,
 | |
| 		IPAddresses:    in.IPAddresses,
 | |
| 		EmailAddresses: in.EmailAddresses,
 | |
| 		URIs:           in.URIs,
 | |
| 	}
 | |
| 	if err := handleOtherSANs(certTemplate, sans); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if len(certTemplate.ExtraExtensions) > 0 {
 | |
| 		in.ExtraExtensions = append(in.ExtraExtensions, certTemplate.ExtraExtensions...)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func handleOtherSANs(in *x509.Certificate, sans map[string][]string) error {
 | |
| 	// If other SANs is empty we return which causes normal Go stdlib parsing
 | |
| 	// of the other SAN types
 | |
| 	if len(sans) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	var rawValues []asn1.RawValue
 | |
| 
 | |
| 	// We need to generate an IMPLICIT sequence for compatibility with OpenSSL
 | |
| 	// -- it's an open question what the default for RFC 5280 actually is, see
 | |
| 	// https://github.com/openssl/openssl/issues/5091 -- so we have to use
 | |
| 	// cryptobyte because using the asn1 package's marshaling always produces
 | |
| 	// an EXPLICIT sequence. Note that asn1 is way too magical according to
 | |
| 	// agl, and cryptobyte is modeled after the CBB/CBS bits that agl put into
 | |
| 	// boringssl.
 | |
| 	for oid, vals := range sans {
 | |
| 		for _, val := range vals {
 | |
| 			var b cryptobyte.Builder
 | |
| 			oidStr, err := stringToOid(oid)
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			b.AddASN1ObjectIdentifier(oidStr)
 | |
| 			b.AddASN1(cbbasn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) {
 | |
| 				b.AddASN1(cbbasn1.UTF8String, func(b *cryptobyte.Builder) {
 | |
| 					b.AddBytes([]byte(val))
 | |
| 				})
 | |
| 			})
 | |
| 			m, err := b.Bytes()
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 			rawValues = append(rawValues, asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: m})
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// If other SANs is empty we return which causes normal Go stdlib parsing
 | |
| 	// of the other SAN types
 | |
| 	if len(rawValues) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Append any existing SANs, sans marshalling
 | |
| 	rawValues = append(rawValues, marshalSANs(in.DNSNames, in.EmailAddresses, in.IPAddresses, in.URIs)...)
 | |
| 
 | |
| 	// Marshal and add to ExtraExtensions
 | |
| 	ext := pkix.Extension{
 | |
| 		// This is the defined OID for subjectAltName
 | |
| 		Id: certutil.OidExtensionSubjectAltName,
 | |
| 	}
 | |
| 	var err error
 | |
| 	ext.Value, err = asn1.Marshal(rawValues)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	in.ExtraExtensions = append(in.ExtraExtensions, ext)
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Note: Taken from the Go source code since it's not public, and used in the
 | |
| // modified function below (which also uses these consts upstream)
 | |
| const (
 | |
| 	nameTypeOther = 0
 | |
| 	nameTypeEmail = 1
 | |
| 	nameTypeDNS   = 2
 | |
| 	nameTypeURI   = 6
 | |
| 	nameTypeIP    = 7
 | |
| )
 | |
| 
 | |
| // Note: Taken from the Go source code since it's not public, plus changed to not marshal
 | |
| // marshalSANs marshals a list of addresses into the contents of an X.509
 | |
| // SubjectAlternativeName extension.
 | |
| func marshalSANs(dnsNames, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL) []asn1.RawValue {
 | |
| 	var rawValues []asn1.RawValue
 | |
| 	for _, name := range dnsNames {
 | |
| 		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeDNS, Class: 2, Bytes: []byte(name)})
 | |
| 	}
 | |
| 	for _, email := range emailAddresses {
 | |
| 		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeEmail, Class: 2, Bytes: []byte(email)})
 | |
| 	}
 | |
| 	for _, rawIP := range ipAddresses {
 | |
| 		// If possible, we always want to encode IPv4 addresses in 4 bytes.
 | |
| 		ip := rawIP.To4()
 | |
| 		if ip == nil {
 | |
| 			ip = rawIP
 | |
| 		}
 | |
| 		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIP, Class: 2, Bytes: ip})
 | |
| 	}
 | |
| 	for _, uri := range uris {
 | |
| 		rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeURI, Class: 2, Bytes: []byte(uri.String())})
 | |
| 	}
 | |
| 	return rawValues
 | |
| }
 | |
| 
 | |
| func stringToOid(in string) (asn1.ObjectIdentifier, error) {
 | |
| 	split := strings.Split(in, ".")
 | |
| 	ret := make(asn1.ObjectIdentifier, 0, len(split))
 | |
| 	for _, v := range split {
 | |
| 		i, err := strconv.Atoi(v)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		ret = append(ret, i)
 | |
| 	}
 | |
| 	return ret, nil
 | |
| }
 | |
| 
 | |
| func parseCertificateFromBytes(certBytes []byte) (*x509.Certificate, error) {
 | |
| 	return parsing.ParseCertificateFromBytes(certBytes)
 | |
| }
 | |
| 
 | |
| func NewCertNotAfterInputFromFieldData(data *framework.FieldData) CertNotAfterInputFromFieldData {
 | |
| 	return CertNotAfterInputFromFieldData{data: data}
 | |
| }
 | |
| 
 | |
| var _ issuing.CertNotAfterInput = CertNotAfterInputFromFieldData{}
 | |
| 
 | |
| type CertNotAfterInputFromFieldData struct {
 | |
| 	data *framework.FieldData
 | |
| }
 | |
| 
 | |
| func (i CertNotAfterInputFromFieldData) GetTTL() int {
 | |
| 	return i.data.Get("ttl").(int)
 | |
| }
 | |
| 
 | |
| func (i CertNotAfterInputFromFieldData) GetOptionalNotAfter() (interface{}, bool) {
 | |
| 	return i.data.GetOk("not_after")
 | |
| }
 |