Josh Black fa13dbd381
add gosimport to make fmt and run it (#25383)
* add gosimport to make fmt and run it

* move installation to tools.sh

* correct weird spacing issue

* Update Makefile

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>

* fix a weird issue

---------

Co-authored-by: Nick Cabatoff <ncabatoff@hashicorp.com>
2024-02-13 14:07:02 -08:00

495 lines
16 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package issuing
import (
"context"
"crypto/x509"
"fmt"
"sort"
"strings"
"time"
"github.com/hashicorp/vault/builtin/logical/pki/managed_key"
"github.com/hashicorp/vault/builtin/logical/pki/parsing"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/helper/errutil"
"github.com/hashicorp/vault/sdk/logical"
)
const (
ReadOnlyUsage IssuerUsage = iota
IssuanceUsage IssuerUsage = 1 << iota
CRLSigningUsage IssuerUsage = 1 << iota
OCSPSigningUsage IssuerUsage = 1 << iota
)
const (
// When adding a new usage in the future, we'll need to create a usage
// mask field on the IssuerEntry and handle migrations to a newer mask,
// inferring a value for the new bits.
AllIssuerUsages = ReadOnlyUsage | IssuanceUsage | CRLSigningUsage | OCSPSigningUsage
DefaultRef = "default"
IssuerPrefix = "config/issuer/"
// Used as a quick sanity check for a reference id lookups...
uuidLength = 36
IssuerRefNotFound = IssuerID("not-found")
LatestIssuerVersion = 1
LegacyCertBundlePath = "config/ca_bundle"
LegacyBundleShimID = IssuerID("legacy-entry-shim-id")
LegacyBundleShimKeyID = KeyID("legacy-entry-shim-key-id")
)
type IssuerID string
func (p IssuerID) String() string {
return string(p)
}
type IssuerUsage uint
var namedIssuerUsages = map[string]IssuerUsage{
"read-only": ReadOnlyUsage,
"issuing-certificates": IssuanceUsage,
"crl-signing": CRLSigningUsage,
"ocsp-signing": OCSPSigningUsage,
}
func (i *IssuerUsage) ToggleUsage(usages ...IssuerUsage) {
for _, usage := range usages {
*i ^= usage
}
}
func (i IssuerUsage) HasUsage(usage IssuerUsage) bool {
return (i & usage) == usage
}
func (i IssuerUsage) Names() string {
var names []string
var builtUsage IssuerUsage
// Return the known set of usages in a sorted order to not have Terraform state files flipping
// saying values are different when it's the same list in a different order.
keys := make([]string, 0, len(namedIssuerUsages))
for k := range namedIssuerUsages {
keys = append(keys, k)
}
sort.Strings(keys)
for _, name := range keys {
usage := namedIssuerUsages[name]
if i.HasUsage(usage) {
names = append(names, name)
builtUsage.ToggleUsage(usage)
}
}
if i != builtUsage {
// Found some unknown usage, we should indicate this in the names.
names = append(names, fmt.Sprintf("unknown:%v", i^builtUsage))
}
return strings.Join(names, ",")
}
func NewIssuerUsageFromNames(names []string) (IssuerUsage, error) {
var result IssuerUsage
for index, name := range names {
usage, ok := namedIssuerUsages[name]
if !ok {
return ReadOnlyUsage, fmt.Errorf("unknown name for usage at index %v: %v", index, name)
}
result.ToggleUsage(usage)
}
return result, nil
}
type IssuerEntry struct {
ID IssuerID `json:"id"`
Name string `json:"name"`
KeyID KeyID `json:"key_id"`
Certificate string `json:"certificate"`
CAChain []string `json:"ca_chain"`
ManualChain []IssuerID `json:"manual_chain"`
SerialNumber string `json:"serial_number"`
LeafNotAfterBehavior certutil.NotAfterBehavior `json:"not_after_behavior"`
Usage IssuerUsage `json:"usage"`
RevocationSigAlg x509.SignatureAlgorithm `json:"revocation_signature_algorithm"`
Revoked bool `json:"revoked"`
RevocationTime int64 `json:"revocation_time"`
RevocationTimeUTC time.Time `json:"revocation_time_utc"`
AIAURIs *AiaConfigEntry `json:"aia_uris,omitempty"`
LastModified time.Time `json:"last_modified"`
Version uint `json:"version"`
}
func (i IssuerEntry) GetCertificate() (*x509.Certificate, error) {
cert, err := parsing.ParseCertificateFromBytes([]byte(i.Certificate))
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to parse certificate from issuer: %s: %v", err.Error(), i.ID)}
}
return cert, nil
}
func (i IssuerEntry) EnsureUsage(usage IssuerUsage) error {
// We want to spit out a nice error message about missing usages.
if i.Usage.HasUsage(usage) {
return nil
}
issuerRef := fmt.Sprintf("id:%v", i.ID)
if len(i.Name) > 0 {
issuerRef = fmt.Sprintf("%v / name:%v", issuerRef, i.Name)
}
// These usages differ at some point in time. We've gotta find the first
// usage that differs and return a logical-sounding error message around
// that difference.
for name, candidate := range namedIssuerUsages {
if usage.HasUsage(candidate) && !i.Usage.HasUsage(candidate) {
return fmt.Errorf("requested usage %v for issuer [%v] but only had usage %v", name, issuerRef, i.Usage.Names())
}
}
// Maybe we have an unnamed usage that's requested.
return fmt.Errorf("unknown delta between usages: %v -> %v / for issuer [%v]", usage.Names(), i.Usage.Names(), issuerRef)
}
func (i IssuerEntry) CanMaybeSignWithAlgo(algo x509.SignatureAlgorithm) error {
// Hack: Go isn't kind enough expose its lovely signatureAlgorithmDetails
// informational struct for our usage. However, we don't want to actually
// fetch the private key and attempt a signature with this algo (as we'll
// mint new, previously unsigned material in the process that could maybe
// be potentially abused if it leaks).
//
// So...
//
// ...we maintain our own mapping of cert.PKI<->sigAlgos. Notably, we
// exclude DSA support as the PKI engine has never supported DSA keys.
if algo == x509.UnknownSignatureAlgorithm {
// Special cased to indicate upgrade and letting Go automatically
// chose the correct value.
return nil
}
cert, err := i.GetCertificate()
if err != nil {
return fmt.Errorf("unable to parse issuer's potential signature algorithm types: %w", err)
}
switch cert.PublicKeyAlgorithm {
case x509.RSA:
switch algo {
case x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA,
x509.SHA256WithRSAPSS, x509.SHA384WithRSAPSS,
x509.SHA512WithRSAPSS:
return nil
}
case x509.ECDSA:
switch algo {
case x509.ECDSAWithSHA256, x509.ECDSAWithSHA384, x509.ECDSAWithSHA512:
return nil
}
case x509.Ed25519:
switch algo {
case x509.PureEd25519:
return nil
}
}
return fmt.Errorf("unable to use issuer of type %v to sign with %v key type", cert.PublicKeyAlgorithm.String(), algo.String())
}
func ResolveIssuerReference(ctx context.Context, s logical.Storage, reference string) (IssuerID, error) {
if reference == DefaultRef {
// Handle fetching the default issuer.
config, err := GetIssuersConfig(ctx, s)
if err != nil {
return IssuerID("config-error"), err
}
if len(config.DefaultIssuerId) == 0 {
return IssuerRefNotFound, fmt.Errorf("no default issuer currently configured")
}
return config.DefaultIssuerId, nil
}
// Lookup by a direct get first to see if our reference is an ID, this is quick and cached.
if len(reference) == uuidLength {
entry, err := s.Get(ctx, IssuerPrefix+reference)
if err != nil {
return IssuerID("issuer-read"), err
}
if entry != nil {
return IssuerID(reference), nil
}
}
// ... than to pull all issuers from storage.
issuers, err := ListIssuers(ctx, s)
if err != nil {
return IssuerID("list-error"), err
}
for _, issuerId := range issuers {
issuer, err := FetchIssuerById(ctx, s, issuerId)
if err != nil {
return IssuerID("issuer-read"), err
}
if issuer.Name == reference {
return issuer.ID, nil
}
}
// Otherwise, we must not have found the issuer.
return IssuerRefNotFound, errutil.UserError{Err: fmt.Sprintf("unable to find PKI issuer for reference: %v", reference)}
}
func ListIssuers(ctx context.Context, s logical.Storage) ([]IssuerID, error) {
strList, err := s.List(ctx, IssuerPrefix)
if err != nil {
return nil, err
}
issuerIds := make([]IssuerID, 0, len(strList))
for _, entry := range strList {
issuerIds = append(issuerIds, IssuerID(entry))
}
return issuerIds, nil
}
// FetchIssuerById returns an IssuerEntry based on issuerId, if none found an error is returned.
func FetchIssuerById(ctx context.Context, s logical.Storage, issuerId IssuerID) (*IssuerEntry, error) {
if len(issuerId) == 0 {
return nil, errutil.InternalError{Err: "unable to fetch pki issuer: empty issuer identifier"}
}
entry, err := s.Get(ctx, IssuerPrefix+issuerId.String())
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch pki issuer: %v", err)}
}
if entry == nil {
return nil, errutil.UserError{Err: fmt.Sprintf("pki issuer id %s does not exist", issuerId.String())}
}
var issuer IssuerEntry
if err := entry.DecodeJSON(&issuer); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode pki issuer with id %s: %v", issuerId.String(), err)}
}
return upgradeIssuerIfRequired(&issuer), nil
}
func WriteIssuer(ctx context.Context, s logical.Storage, issuer *IssuerEntry) error {
issuerId := issuer.ID
if issuer.LastModified.IsZero() {
issuer.LastModified = time.Now().UTC()
}
json, err := logical.StorageEntryJSON(IssuerPrefix+issuerId.String(), issuer)
if err != nil {
return err
}
return s.Put(ctx, json)
}
func DeleteIssuer(ctx context.Context, s logical.Storage, id IssuerID) (bool, error) {
config, err := GetIssuersConfig(ctx, s)
if err != nil {
return false, err
}
wasDefault := false
if config.DefaultIssuerId == id {
wasDefault = true
// Overwrite the fetched default issuer as we're going to remove this
// entry.
config.fetchedDefault = IssuerID("")
config.DefaultIssuerId = IssuerID("")
if err := SetIssuersConfig(ctx, s, config); err != nil {
return wasDefault, err
}
}
return wasDefault, s.Delete(ctx, IssuerPrefix+id.String())
}
func upgradeIssuerIfRequired(issuer *IssuerEntry) *IssuerEntry {
// *NOTE*: Don't attempt to write out the issuer here as it may cause ErrReadOnly that will direct the
// request all the way up to the primary cluster which would be horrible for local cluster operations such
// as generating a leaf cert or a revoke.
// Also even though we could tell if we are the primary cluster's active node, we can't tell if we have the
// a full rw issuer lock, so it might not be safe to write.
if issuer.Version == LatestIssuerVersion {
return issuer
}
if issuer.Version == 0 {
// Upgrade at this step requires interrogating the certificate itself;
// if this decode fails, it indicates internal problems and the
// request will subsequently fail elsewhere. However, decoding this
// certificate is mildly expensive, so we only do it in the event of
// a Version 0 certificate.
cert, err := issuer.GetCertificate()
if err != nil {
return issuer
}
hadCRL := issuer.Usage.HasUsage(CRLSigningUsage)
// Remove CRL signing usage if it exists on the issuer but doesn't
// exist in the KU of the x509 certificate.
if hadCRL && (cert.KeyUsage&x509.KeyUsageCRLSign) == 0 {
issuer.Usage.ToggleUsage(CRLSigningUsage)
}
// Handle our new OCSPSigning usage flag for earlier versions. If we
// had it (prior to removing it in this upgrade), we'll add the OCSP
// flag since EKUs don't matter.
if hadCRL && !issuer.Usage.HasUsage(OCSPSigningUsage) {
issuer.Usage.ToggleUsage(OCSPSigningUsage)
}
}
issuer.Version = LatestIssuerVersion
return issuer
}
// 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 FetchCAInfoByIssuerId(ctx context.Context, s logical.Storage, mkv managed_key.PkiManagedKeyView, issuerId IssuerID, usage IssuerUsage) (*certutil.CAInfoBundle, error) {
entry, bundle, err := FetchCertBundleByIssuerId(ctx, s, issuerId, true)
if err != nil {
switch err.(type) {
case errutil.UserError:
return nil, err
case errutil.InternalError:
return nil, err
default:
return nil, errutil.InternalError{Err: fmt.Sprintf("error fetching CA info: %v", err)}
}
}
if err = entry.EnsureUsage(usage); err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("error while attempting to use issuer %v: %v", issuerId, err)}
}
parsedBundle, err := ParseCABundle(ctx, mkv, bundle)
if err != nil {
return nil, errutil.InternalError{Err: err.Error()}
}
if parsedBundle.Certificate == nil {
return nil, errutil.InternalError{Err: "stored CA information not able to be parsed"}
}
if parsedBundle.PrivateKey == nil {
return nil, errutil.UserError{Err: fmt.Sprintf("unable to fetch corresponding key for issuer %v; unable to use this issuer for signing", issuerId)}
}
caInfo := &certutil.CAInfoBundle{
ParsedCertBundle: *parsedBundle,
URLs: nil,
LeafNotAfterBehavior: entry.LeafNotAfterBehavior,
RevocationSigAlg: entry.RevocationSigAlg,
}
entries, err := GetAIAURLs(ctx, s, entry)
if err != nil {
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch AIA URL information: %v", err)}
}
caInfo.URLs = entries
return caInfo, nil
}
func ParseCABundle(ctx context.Context, mkv managed_key.PkiManagedKeyView, bundle *certutil.CertBundle) (*certutil.ParsedCertBundle, error) {
if bundle.PrivateKeyType == certutil.ManagedPrivateKey {
return managed_key.ParseManagedKeyCABundle(ctx, mkv, bundle)
}
return bundle.ToParsedCertBundle()
}
// FetchCertBundleByIssuerId builds a certutil.CertBundle from the specified issuer identifier,
// optionally loading the key or not. This method supports loading legacy
// bundles using the legacyBundleShimID issuerId, and if no entry is found will return an error.
func FetchCertBundleByIssuerId(ctx context.Context, s logical.Storage, id IssuerID, loadKey bool) (*IssuerEntry, *certutil.CertBundle, error) {
if id == LegacyBundleShimID {
// We have not completed the migration, or started a request in legacy mode, so
// attempt to load the bundle from the legacy location
issuer, bundle, err := GetLegacyCertBundle(ctx, s)
if err != nil {
return nil, nil, err
}
if issuer == nil || bundle == nil {
return nil, nil, errutil.UserError{Err: "no legacy cert bundle exists"}
}
return issuer, bundle, err
}
issuer, err := FetchIssuerById(ctx, s, id)
if err != nil {
return nil, nil, err
}
var bundle certutil.CertBundle
bundle.Certificate = issuer.Certificate
bundle.CAChain = issuer.CAChain
bundle.SerialNumber = issuer.SerialNumber
// Fetch the key if it exists. Sometimes we don't need the key immediately.
if loadKey && issuer.KeyID != KeyID("") {
key, err := FetchKeyById(ctx, s, issuer.KeyID)
if err != nil {
return nil, nil, err
}
bundle.PrivateKeyType = key.PrivateKeyType
bundle.PrivateKey = key.PrivateKey
}
return issuer, &bundle, nil
}
func GetLegacyCertBundle(ctx context.Context, s logical.Storage) (*IssuerEntry, *certutil.CertBundle, error) {
entry, err := s.Get(ctx, LegacyCertBundlePath)
if err != nil {
return nil, nil, err
}
if entry == nil {
return nil, nil, nil
}
cb := &certutil.CertBundle{}
err = entry.DecodeJSON(cb)
if err != nil {
return nil, nil, err
}
// Fake a storage entry with backwards compatibility in mind.
issuer := &IssuerEntry{
ID: LegacyBundleShimID,
KeyID: LegacyBundleShimKeyID,
Name: "legacy-entry-shim",
Certificate: cb.Certificate,
CAChain: cb.CAChain,
SerialNumber: cb.SerialNumber,
LeafNotAfterBehavior: certutil.ErrNotAfterBehavior,
}
issuer.Usage.ToggleUsage(AllIssuerUsages)
return issuer, cb, nil
}