package pki import ( "bytes" "context" "crypto" "crypto/x509" "fmt" "sort" "strings" "time" "github.com/hashicorp/go-uuid" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/errutil" "github.com/hashicorp/vault/sdk/logical" ) const ( storageKeyConfig = "config/keys" storageIssuerConfig = "config/issuers" keyPrefix = "config/key/" issuerPrefix = "config/issuer/" storageLocalCRLConfig = "crls/config" legacyMigrationBundleLogKey = "config/legacyMigrationBundleLog" legacyCertBundlePath = "config/ca_bundle" legacyCRLPath = "crl" deltaCRLPath = "delta-crl" deltaCRLPathSuffix = "-delta" autoTidyConfigPath = "config/auto-tidy" clusterConfigPath = "config/cluster" // Used as a quick sanity check for a reference id lookups... uuidLength = 36 maxRolesToScanOnIssuerChange = 100 maxRolesToFindOnIssuerChange = 10 latestIssuerVersion = 1 ) type keyID string func (p keyID) String() string { return string(p) } type issuerID string func (p issuerID) String() string { return string(p) } type crlID string func (p crlID) String() string { return string(p) } const ( IssuerRefNotFound = issuerID("not-found") KeyRefNotFound = keyID("not-found") ) type keyEntry struct { ID keyID `json:"id"` Name string `json:"name"` PrivateKeyType certutil.PrivateKeyType `json:"private_key_type"` PrivateKey string `json:"private_key"` } func (e keyEntry) getManagedKeyUUID() (UUIDKey, error) { if !e.isManagedPrivateKey() { return "", errutil.InternalError{Err: "getManagedKeyId called on a key id %s (%s) "} } return extractManagedKeyId([]byte(e.PrivateKey)) } func (e keyEntry) isManagedPrivateKey() bool { return e.PrivateKeyType == certutil.ManagedPrivateKey } type issuerUsage uint const ( ReadOnlyUsage issuerUsage = iota IssuanceUsage issuerUsage = 1 << iota CRLSigningUsage issuerUsage = 1 << iota OCSPSigningUsage issuerUsage = 1 << iota // 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 ) 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"` } type localCRLConfigEntry struct { IssuerIDCRLMap map[issuerID]crlID `json:"issuer_id_crl_map"` CRLNumberMap map[crlID]int64 `json:"crl_number_map"` LastCompleteNumberMap map[crlID]int64 `json:"last_complete_number_map"` CRLExpirationMap map[crlID]time.Time `json:"crl_expiration_map"` LastModified time.Time `json:"last_modified"` DeltaLastModified time.Time `json:"delta_last_modified"` } type keyConfigEntry struct { DefaultKeyId keyID `json:"default"` } type issuerConfigEntry struct { // This new fetchedDefault field allows us to detect if the default // issuer was modified, in turn dispatching the timestamp updater // if necessary. fetchedDefault issuerID `json:"-"` DefaultIssuerId issuerID `json:"default"` DefaultFollowsLatestIssuer bool `json:"default_follows_latest_issuer"` } type clusterConfigEntry struct { Path string `json:"path"` AIAPath string `json:"aia_path"` } type aiaConfigEntry struct { IssuingCertificates []string `json:"issuing_certificates"` CRLDistributionPoints []string `json:"crl_distribution_points"` OCSPServers []string `json:"ocsp_servers"` EnableTemplating bool `json:"enable_templating"` } func (c *aiaConfigEntry) toURLEntries(sc *storageContext, issuer issuerID) (*certutil.URLEntries, error) { if len(c.IssuingCertificates) == 0 && len(c.CRLDistributionPoints) == 0 && len(c.OCSPServers) == 0 { return &certutil.URLEntries{}, nil } result := certutil.URLEntries{ IssuingCertificates: c.IssuingCertificates[:], CRLDistributionPoints: c.CRLDistributionPoints[:], OCSPServers: c.OCSPServers[:], } if c.EnableTemplating { cfg, err := sc.getClusterConfig() if err != nil { return nil, fmt.Errorf("error fetching cluster-local address config: %w", err) } for name, source := range map[string]*[]string{ "issuing_certificates": &result.IssuingCertificates, "crl_distribution_points": &result.CRLDistributionPoints, "ocsp_servers": &result.OCSPServers, } { templated := make([]string, len(*source)) for index, uri := range *source { if strings.Contains(uri, "{{cluster_path}}") && len(cfg.Path) == 0 { return nil, fmt.Errorf("unable to template AIA URLs as we lack local cluster address information (path)") } if strings.Contains(uri, "{{cluster_aia_path}}") && len(cfg.AIAPath) == 0 { return nil, fmt.Errorf("unable to template AIA URLs as we lack local cluster address information (aia_path)") } if strings.Contains(uri, "{{issuer_id}}") && len(issuer) == 0 { // Elide issuer AIA info as we lack an issuer_id. return nil, fmt.Errorf("unable to template AIA URLs as we lack an issuer_id for this operation") } uri = strings.ReplaceAll(uri, "{{cluster_path}}", cfg.Path) uri = strings.ReplaceAll(uri, "{{cluster_aia_path}}", cfg.AIAPath) uri = strings.ReplaceAll(uri, "{{issuer_id}}", issuer.String()) templated[index] = uri } if uri := validateURLs(templated); uri != "" { return nil, fmt.Errorf("error validating templated %v; invalid URI: %v", name, uri) } *source = templated } } return &result, nil } type storageContext struct { Context context.Context Storage logical.Storage Backend *backend } func (b *backend) makeStorageContext(ctx context.Context, s logical.Storage) *storageContext { return &storageContext{ Context: ctx, Storage: s, Backend: b, } } func (sc *storageContext) listKeys() ([]keyID, error) { strList, err := sc.Storage.List(sc.Context, keyPrefix) if err != nil { return nil, err } keyIds := make([]keyID, 0, len(strList)) for _, entry := range strList { keyIds = append(keyIds, keyID(entry)) } return keyIds, nil } func (sc *storageContext) fetchKeyById(keyId keyID) (*keyEntry, error) { if len(keyId) == 0 { return nil, errutil.InternalError{Err: "unable to fetch pki key: empty key identifier"} } entry, err := sc.Storage.Get(sc.Context, keyPrefix+keyId.String()) if err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch pki key: %v", err)} } if entry == nil { return nil, errutil.UserError{Err: fmt.Sprintf("pki key id %s does not exist", keyId.String())} } var key keyEntry if err := entry.DecodeJSON(&key); err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode pki key with id %s: %v", keyId.String(), err)} } return &key, nil } func (sc *storageContext) writeKey(key keyEntry) error { keyId := key.ID json, err := logical.StorageEntryJSON(keyPrefix+keyId.String(), key) if err != nil { return err } return sc.Storage.Put(sc.Context, json) } func (sc *storageContext) deleteKey(id keyID) (bool, error) { config, err := sc.getKeysConfig() if err != nil { return false, err } wasDefault := false if config.DefaultKeyId == id { wasDefault = true config.DefaultKeyId = keyID("") if err := sc.setKeysConfig(config); err != nil { return wasDefault, err } } return wasDefault, sc.Storage.Delete(sc.Context, keyPrefix+id.String()) } func (sc *storageContext) importKey(keyValue string, keyName string, keyType certutil.PrivateKeyType) (*keyEntry, bool, error) { // importKey imports the specified PEM-format key (from keyValue) into // the new PKI storage format. The first return field is a reference to // the new key; the second is whether or not the key already existed // during import (in which case, *key points to the existing key reference // and identifier); the last return field is whether or not an error // occurred. // // Normalize whitespace before beginning. See note in importIssuer as to // why we do this. keyValue = strings.TrimSpace(keyValue) + "\n" // // Before we can import a known key, we first need to know if the key // exists in storage already. This means iterating through all known // keys and comparing their private value against this value. knownKeys, err := sc.listKeys() if err != nil { return nil, false, err } // Get our public key from the current inbound key, to compare against all the other keys. var pkForImportingKey crypto.PublicKey if keyType == certutil.ManagedPrivateKey { managedKeyUUID, err := extractManagedKeyId([]byte(keyValue)) if err != nil { return nil, false, errutil.InternalError{Err: fmt.Sprintf("failed extracting managed key uuid from key: %v", err)} } pkForImportingKey, err = getManagedKeyPublicKey(sc.Context, sc.Backend, managedKeyUUID) if err != nil { return nil, false, err } } else { pkForImportingKey, err = getPublicKeyFromBytes([]byte(keyValue)) if err != nil { return nil, false, err } } foundExistingKeyWithName := false for _, identifier := range knownKeys { existingKey, err := sc.fetchKeyById(identifier) if err != nil { return nil, false, err } areEqual, err := comparePublicKey(sc, existingKey, pkForImportingKey) if err != nil { return nil, false, err } if areEqual { // Here, we don't need to stitch together the issuer entries, // because the last run should've done that for us (or, when // importing an issuer). return existingKey, true, nil } // Allow us to find an existing matching key with a different name before erroring out if keyName != "" && existingKey.Name == keyName { foundExistingKeyWithName = true } } // Another key with a different value is using the keyName so reject this request. if foundExistingKeyWithName { return nil, false, errutil.UserError{Err: fmt.Sprintf("an existing key is using the requested key name value: %s", keyName)} } // Haven't found a key, so we've gotta create it and write it into storage. var result keyEntry result.ID = genKeyId() result.Name = keyName result.PrivateKey = keyValue result.PrivateKeyType = keyType // Finally, we can write the key to storage. if err := sc.writeKey(result); err != nil { return nil, false, err } // Before we return below, we need to iterate over _all_ issuers and see if // one of them has a missing KeyId link, and if so, point it back to // ourselves. We fetch the list of issuers up front, even when don't need // it, to give ourselves a better chance of succeeding below. knownIssuers, err := sc.listIssuers() if err != nil { return nil, false, err } issuerDefaultSet, err := sc.isDefaultIssuerSet() if err != nil { return nil, false, err } // Now, for each issuer, try and compute the issuer<->key link if missing. for _, identifier := range knownIssuers { existingIssuer, err := sc.fetchIssuerById(identifier) if err != nil { return nil, false, err } // If the KeyID value is already present, we can skip it. if len(existingIssuer.KeyID) > 0 { continue } // Otherwise, compare public values. Note that there might be multiple // certificates (e.g., cross-signed) with the same key. cert, err := existingIssuer.GetCertificate() if err != nil { // Malformed issuer. return nil, false, err } equal, err := certutil.ComparePublicKeysAndType(cert.PublicKey, pkForImportingKey) if err != nil { return nil, false, err } if equal { // These public keys are equal, so this key entry must be the // corresponding private key to this issuer; update it accordingly. existingIssuer.KeyID = result.ID if err := sc.writeIssuer(existingIssuer); err != nil { return nil, false, err } // If there was no prior default value set and/or we had no known // issuers when we started, set this issuer as default. if !issuerDefaultSet { err = sc.updateDefaultIssuerId(existingIssuer.ID) if err != nil { return nil, false, err } issuerDefaultSet = true } } } // If there was no prior default value set and/or we had no known // keys when we started, set this key as default. keyDefaultSet, err := sc.isDefaultKeySet() if err != nil { return nil, false, err } if len(knownKeys) == 0 || !keyDefaultSet { if err = sc.updateDefaultKeyId(result.ID); err != nil { return nil, false, err } } // All done; return our new key reference. return &result, false, nil } func (i issuerEntry) GetCertificate() (*x509.Certificate, error) { cert, err := 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 (i issuerEntry) GetAIAURLs(sc *storageContext) (*certutil.URLEntries, error) { // Default to the per-issuer AIA URLs. entries := i.AIAURIs // If none are set (either due to a nil entry or because no URLs have // been provided), fall back to the global AIA URL config. if entries == nil || (len(entries.IssuingCertificates) == 0 && len(entries.CRLDistributionPoints) == 0 && len(entries.OCSPServers) == 0) { var err error entries, err = getGlobalAIAURLs(sc.Context, sc.Storage) if err != nil { return nil, err } } if entries == nil { return &certutil.URLEntries{}, nil } return entries.toURLEntries(sc, i.ID) } func (sc *storageContext) listIssuers() ([]issuerID, error) { strList, err := sc.Storage.List(sc.Context, 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 } func (sc *storageContext) resolveKeyReference(reference string) (keyID, error) { if reference == defaultRef { // Handle fetching the default key. config, err := sc.getKeysConfig() if err != nil { return keyID("config-error"), err } if len(config.DefaultKeyId) == 0 { return KeyRefNotFound, fmt.Errorf("no default key currently configured") } return config.DefaultKeyId, 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 := sc.Storage.Get(sc.Context, keyPrefix+reference) if err != nil { return keyID("key-read"), err } if entry != nil { return keyID(reference), nil } } // ... than to pull all keys from storage. keys, err := sc.listKeys() if err != nil { return keyID("list-error"), err } for _, keyId := range keys { key, err := sc.fetchKeyById(keyId) if err != nil { return keyID("key-read"), err } if key.Name == reference { return key.ID, nil } } // Otherwise, we must not have found the key. return KeyRefNotFound, errutil.UserError{Err: fmt.Sprintf("unable to find PKI key for reference: %v", reference)} } // fetchIssuerById returns an issuerEntry based on issuerId, if none found an error is returned. func (sc *storageContext) fetchIssuerById(issuerId issuerID) (*issuerEntry, error) { if len(issuerId) == 0 { return nil, errutil.InternalError{Err: "unable to fetch pki issuer: empty issuer identifier"} } entry, err := sc.Storage.Get(sc.Context, 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 sc.upgradeIssuerIfRequired(&issuer), nil } func (sc *storageContext) 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(OCSPSigningUsage) } // 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 } func (sc *storageContext) writeIssuer(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 sc.Storage.Put(sc.Context, json) } func (sc *storageContext) deleteIssuer(id issuerID) (bool, error) { config, err := sc.getIssuersConfig() 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 := sc.setIssuersConfig(config); err != nil { return wasDefault, err } } return wasDefault, sc.Storage.Delete(sc.Context, issuerPrefix+id.String()) } func (sc *storageContext) importIssuer(certValue string, issuerName string) (*issuerEntry, bool, error) { // importIssuers imports the specified PEM-format certificate (from // certValue) into the new PKI storage format. The first return field is a // reference to the new issuer; the second is whether or not the issuer // already existed during import (in which case, *issuer points to the // existing issuer reference and identifier); the last return field is // whether or not an error occurred. // Before we begin, we need to ensure the PEM formatted certificate looks // good. Restricting to "just" `CERTIFICATE` entries is a little // restrictive, as it could be a `X509 CERTIFICATE` entry or a custom // value wrapping an actual DER cert. So validating the contents of the // PEM header is out of the question (and validating the contents of the // PEM block is left to our GetCertificate call below). // // However, we should trim all leading and trailing spaces and add a // single new line. This allows callers to blindly concatenate PEM // blobs from the API and get roughly what they'd expect. // // Discussed further in #11960 and RFC 7468. certValue = strings.TrimSpace(certValue) + "\n" // Extracting the certificate is necessary for two reasons: first, it lets // us fetch the serial number; second, for the public key comparison with // known keys. issuerCert, err := parseCertificateFromBytes([]byte(certValue)) if err != nil { return nil, false, err } // Ensure this certificate is a usable as a CA certificate. if !issuerCert.BasicConstraintsValid || !issuerCert.IsCA { return nil, false, errutil.UserError{Err: "Refusing to import non-CA certificate"} } // Ensure this certificate has a parsed public key. Otherwise, we've // likely been given a bad certificate. if issuerCert.PublicKeyAlgorithm == x509.UnknownPublicKeyAlgorithm || issuerCert.PublicKey == nil { return nil, false, errutil.UserError{Err: "Refusing to import CA certificate with empty PublicKey. This usually means the SubjectPublicKeyInfo field has an OID not recognized by Go, such as 1.2.840.113549.1.1.10 for rsaPSS."} } // Before we can import a known issuer, we first need to know if the issuer // exists in storage already. This means iterating through all known // issuers and comparing their private value against this value. knownIssuers, err := sc.listIssuers() if err != nil { return nil, false, err } foundExistingIssuerWithName := false for _, identifier := range knownIssuers { existingIssuer, err := sc.fetchIssuerById(identifier) if err != nil { return nil, false, err } existingIssuerCert, err := existingIssuer.GetCertificate() if err != nil { return nil, false, err } if areCertificatesEqual(existingIssuerCert, issuerCert) { // Here, we don't need to stitch together the key entries, // because the last run should've done that for us (or, when // importing a key). return existingIssuer, true, nil } // Allow us to find an existing matching issuer with a different name before erroring out if issuerName != "" && existingIssuer.Name == issuerName { foundExistingIssuerWithName = true } } if foundExistingIssuerWithName { return nil, false, errutil.UserError{Err: fmt.Sprintf("another issuer is using the requested name: %s", issuerName)} } // Haven't found an issuer, so we've gotta create it and write it into // storage. var result issuerEntry result.ID = genIssuerId() result.Name = issuerName result.Certificate = certValue result.LeafNotAfterBehavior = certutil.ErrNotAfterBehavior result.Usage.ToggleUsage(AllIssuerUsages) result.Version = latestIssuerVersion // If we lack relevant bits for CRL, prohibit it from being set // on the usage side. if (issuerCert.KeyUsage&x509.KeyUsageCRLSign) == 0 && result.Usage.HasUsage(CRLSigningUsage) { result.Usage.ToggleUsage(CRLSigningUsage) } // We shouldn't add CSRs or multiple certificates in this countCertificates := strings.Count(result.Certificate, "-BEGIN ") if countCertificates != 1 { return nil, false, fmt.Errorf("bad issuer: potentially multiple PEM blobs in one certificate storage entry:\n%v", result.Certificate) } result.SerialNumber = serialFromCert(issuerCert) // Before we return below, we need to iterate over _all_ keys and see if // one of them a public key matching this certificate, and if so, update our // link accordingly. We fetch the list of keys up front, even may not need // it, to give ourselves a better chance of succeeding below. knownKeys, err := sc.listKeys() if err != nil { return nil, false, err } // Now, for each key, try and compute the issuer<->key link. We delay // writing issuer to storage as we won't need to update the key, only // the issuer. for _, identifier := range knownKeys { existingKey, err := sc.fetchKeyById(identifier) if err != nil { return nil, false, err } equal, err := comparePublicKey(sc, existingKey, issuerCert.PublicKey) if err != nil { return nil, false, err } if equal { result.KeyID = existingKey.ID // Here, there's exactly one stored key with the same public key // as us, per guarantees in importKey; as we're importing an // issuer, there's no other keys or issuers we'd need to read or // update, so exit. break } } // Finally, rebuild the chains. In this process, because the provided // reference issuer is non-nil, we'll save this issuer to storage. if err := sc.rebuildIssuersChains(&result); err != nil { return nil, false, err } // If there was no prior default value set and/or we had no known // issuers when we started, set this issuer as default. issuerDefaultSet, err := sc.isDefaultIssuerSet() if err != nil { return nil, false, err } if (len(knownIssuers) == 0 || !issuerDefaultSet) && len(result.KeyID) != 0 { if err = sc.updateDefaultIssuerId(result.ID); err != nil { return nil, false, err } } // All done; return our new key reference. return &result, false, nil } func areCertificatesEqual(cert1 *x509.Certificate, cert2 *x509.Certificate) bool { return bytes.Equal(cert1.Raw, cert2.Raw) } func (sc *storageContext) setLocalCRLConfig(mapping *localCRLConfigEntry) error { json, err := logical.StorageEntryJSON(storageLocalCRLConfig, mapping) if err != nil { return err } return sc.Storage.Put(sc.Context, json) } func (sc *storageContext) getLocalCRLConfig() (*localCRLConfigEntry, error) { entry, err := sc.Storage.Get(sc.Context, storageLocalCRLConfig) if err != nil { return nil, err } mapping := &localCRLConfigEntry{} if entry != nil { if err := entry.DecodeJSON(mapping); err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode cluster-local CRL configuration: %v", err)} } } if len(mapping.IssuerIDCRLMap) == 0 { mapping.IssuerIDCRLMap = make(map[issuerID]crlID) } if len(mapping.CRLNumberMap) == 0 { mapping.CRLNumberMap = make(map[crlID]int64) } if len(mapping.LastCompleteNumberMap) == 0 { mapping.LastCompleteNumberMap = make(map[crlID]int64) // Since this might not exist on migration, we want to guess as // to the last full CRL number was. This was likely the last // value from CRLNumberMap if it existed, since we're just adding // the mapping here in this block. // // After the next full CRL build, we will have set this value // correctly, so it doesn't really matter in the long term if // we're off here. for id, number := range mapping.CRLNumberMap { // Decrement by one, since CRLNumberMap is the future number, // not the last built number. mapping.LastCompleteNumberMap[id] = number - 1 } } if len(mapping.CRLExpirationMap) == 0 { mapping.CRLExpirationMap = make(map[crlID]time.Time) } return mapping, nil } func (sc *storageContext) setKeysConfig(config *keyConfigEntry) error { json, err := logical.StorageEntryJSON(storageKeyConfig, config) if err != nil { return err } return sc.Storage.Put(sc.Context, json) } func (sc *storageContext) getKeysConfig() (*keyConfigEntry, error) { entry, err := sc.Storage.Get(sc.Context, storageKeyConfig) if err != nil { return nil, err } keyConfig := &keyConfigEntry{} if entry != nil { if err := entry.DecodeJSON(keyConfig); err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode key configuration: %v", err)} } } return keyConfig, nil } func (sc *storageContext) setIssuersConfig(config *issuerConfigEntry) error { json, err := logical.StorageEntryJSON(storageIssuerConfig, config) if err != nil { return err } if err := sc.Storage.Put(sc.Context, json); err != nil { return err } if err := sc.changeDefaultIssuerTimestamps(config.fetchedDefault, config.DefaultIssuerId); err != nil { return err } return nil } func (sc *storageContext) getIssuersConfig() (*issuerConfigEntry, error) { entry, err := sc.Storage.Get(sc.Context, storageIssuerConfig) if err != nil { return nil, err } issuerConfig := &issuerConfigEntry{} if entry != nil { if err := entry.DecodeJSON(issuerConfig); err != nil { return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode issuer configuration: %v", err)} } } issuerConfig.fetchedDefault = issuerConfig.DefaultIssuerId return issuerConfig, nil } // Lookup within storage the value of reference, assuming the string is a reference to an issuer entry, // returning the converted issuerID or an error if not found. This method will not properly resolve the // special legacyBundleShimID value as we do not want to confuse our special value and a user-provided name of the // same value. func (sc *storageContext) resolveIssuerReference(reference string) (issuerID, error) { if reference == defaultRef { // Handle fetching the default issuer. config, err := sc.getIssuersConfig() 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 := sc.Storage.Get(sc.Context, 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 := sc.listIssuers() if err != nil { return issuerID("list-error"), err } for _, issuerId := range issuers { issuer, err := sc.fetchIssuerById(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 (sc *storageContext) resolveIssuerCRLPath(reference string) (string, error) { if sc.Backend.useLegacyBundleCaStorage() { return legacyCRLPath, nil } issuer, err := sc.resolveIssuerReference(reference) if err != nil { return legacyCRLPath, err } crlConfig, err := sc.getLocalCRLConfig() if err != nil { return legacyCRLPath, err } if crlId, ok := crlConfig.IssuerIDCRLMap[issuer]; ok && len(crlId) > 0 { return fmt.Sprintf("crls/%v", crlId), nil } return legacyCRLPath, fmt.Errorf("unable to find CRL for issuer: id:%v/ref:%v", issuer, reference) } // 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 (sc *storageContext) fetchCertBundleByIssuerId(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(sc.Context, sc.Storage) 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 := sc.fetchIssuerById(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 := sc.fetchKeyById(issuer.KeyID) if err != nil { return nil, nil, err } bundle.PrivateKeyType = key.PrivateKeyType bundle.PrivateKey = key.PrivateKey } return issuer, &bundle, nil } func (sc *storageContext) writeCaBundle(caBundle *certutil.CertBundle, issuerName string, keyName string) (*issuerEntry, *keyEntry, error) { myKey, _, err := sc.importKey(caBundle.PrivateKey, keyName, caBundle.PrivateKeyType) if err != nil { return nil, nil, err } // We may have existing mounts that only contained a key with no certificate yet as a signed CSR // was never setup within the mount. if caBundle.Certificate == "" { return &issuerEntry{}, myKey, nil } myIssuer, _, err := sc.importIssuer(caBundle.Certificate, issuerName) if err != nil { return nil, nil, err } for _, cert := range caBundle.CAChain { if _, _, err = sc.importIssuer(cert, ""); err != nil { return nil, nil, err } } return myIssuer, myKey, nil } func genIssuerId() issuerID { return issuerID(genUuid()) } func genKeyId() keyID { return keyID(genUuid()) } func genCRLId() crlID { return crlID(genUuid()) } func genUuid() string { aUuid, err := uuid.GenerateUUID() if err != nil { panic(err) } return aUuid } func (sc *storageContext) isKeyInUse(keyId string) (inUse bool, issuerId string, err error) { knownIssuers, err := sc.listIssuers() if err != nil { return true, "", err } for _, issuerId := range knownIssuers { issuerEntry, err := sc.fetchIssuerById(issuerId) if err != nil { return true, issuerId.String(), errutil.InternalError{Err: fmt.Sprintf("unable to fetch pki issuer: %v", err)} } if issuerEntry == nil { return true, issuerId.String(), errutil.InternalError{Err: fmt.Sprintf("Issuer listed: %s does not exist", issuerId.String())} } if issuerEntry.KeyID.String() == keyId { return true, issuerId.String(), nil } } return false, "", nil } func (sc *storageContext) checkForRolesReferencing(issuerId string) (timeout bool, inUseBy int32, err error) { roleEntries, err := sc.Storage.List(sc.Context, "role/") if err != nil { return false, 0, err } inUseBy = 0 checkedRoles := 0 for _, roleName := range roleEntries { entry, err := sc.Storage.Get(sc.Context, "role/"+roleName) if err != nil { return false, 0, err } if entry != nil { // If nil, someone deleted an entry since we haven't taken a lock here so just continue var role roleEntry err = entry.DecodeJSON(&role) if err != nil { return false, inUseBy, err } if role.Issuer == issuerId { inUseBy = inUseBy + 1 if inUseBy >= maxRolesToFindOnIssuerChange { return true, inUseBy, nil } } } checkedRoles = checkedRoles + 1 if checkedRoles >= maxRolesToScanOnIssuerChange { return true, inUseBy, nil } } return false, inUseBy, nil } func (sc *storageContext) getRevocationConfig() (*crlConfig, error) { entry, err := sc.Storage.Get(sc.Context, "config/crl") if err != nil { return nil, err } var result crlConfig if entry == nil { result = defaultCrlConfig return &result, nil } if err = entry.DecodeJSON(&result); err != nil { return nil, err } if result.Version == 0 { // Automatically update existing configurations. result.OcspDisable = defaultCrlConfig.OcspDisable result.OcspExpiry = defaultCrlConfig.OcspExpiry result.AutoRebuild = defaultCrlConfig.AutoRebuild result.AutoRebuildGracePeriod = defaultCrlConfig.AutoRebuildGracePeriod result.Version = 1 } if result.Version == 1 { if result.DeltaRebuildInterval == "" { result.DeltaRebuildInterval = defaultCrlConfig.DeltaRebuildInterval } result.Version = 2 } // Depending on client version, it's possible that the expiry is unset. // This sets the default value to prevent issues in downstream code. if result.Expiry == "" { result.Expiry = defaultCrlConfig.Expiry } return &result, nil } func (sc *storageContext) getAutoTidyConfig() (*tidyConfig, error) { entry, err := sc.Storage.Get(sc.Context, autoTidyConfigPath) if err != nil { return nil, err } var result tidyConfig if entry == nil { result = defaultTidyConfig return &result, nil } if err = entry.DecodeJSON(&result); err != nil { return nil, err } if result.IssuerSafetyBuffer == 0 { result.IssuerSafetyBuffer = defaultTidyConfig.IssuerSafetyBuffer } return &result, nil } func (sc *storageContext) writeAutoTidyConfig(config *tidyConfig) error { entry, err := logical.StorageEntryJSON(autoTidyConfigPath, config) if err != nil { return err } return sc.Storage.Put(sc.Context, entry) } func (sc *storageContext) listRevokedCerts() ([]string, error) { list, err := sc.Storage.List(sc.Context, revokedPath) if err != nil { return nil, fmt.Errorf("failed listing revoked certs: %w", err) } return list, err } func (sc *storageContext) getClusterConfig() (*clusterConfigEntry, error) { entry, err := sc.Storage.Get(sc.Context, clusterConfigPath) if err != nil { return nil, err } var result clusterConfigEntry if entry == nil { return &result, nil } if err = entry.DecodeJSON(&result); err != nil { return nil, err } return &result, nil } func (sc *storageContext) writeClusterConfig(config *clusterConfigEntry) error { entry, err := logical.StorageEntryJSON(clusterConfigPath, config) if err != nil { return err } return sc.Storage.Put(sc.Context, entry) }