diff --git a/builtin/logical/pki/cert_util.go b/builtin/logical/pki/cert_util.go index d9bbb5670c..a86e65e878 100644 --- a/builtin/logical/pki/cert_util.go +++ b/builtin/logical/pki/cert_util.go @@ -295,6 +295,7 @@ func generateCert(sc *storageContext, if data.Params == nil { return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } + issuing.EntAdjustCreationBundle(sc.System(), data) if isCA { data.Params.IsCA = isCA diff --git a/builtin/logical/pki/common_criteria_stubs_oss.go b/builtin/logical/pki/common_criteria_stubs_oss.go new file mode 100644 index 0000000000..3e4f792c06 --- /dev/null +++ b/builtin/logical/pki/common_criteria_stubs_oss.go @@ -0,0 +1,17 @@ +// Copyright IBM Corp. 2016, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !enterprise + +package pki + +import ( + "github.com/hashicorp/vault/builtin/logical/pki/issuing" +) + +//go:generate go run github.com/hashicorp/vault/tools/stubmaker +func (b *backend) adjustInputBundle(input *inputBundle) {} + +func entValidateRole(b *backend, entry *issuing.RoleEntry) ([]string, error) { + return nil, nil +} diff --git a/builtin/logical/pki/issuing/issuing_stubs_oss.go b/builtin/logical/pki/issuing/issuing_stubs_oss.go index ebbb550cb7..365786099b 100644 --- a/builtin/logical/pki/issuing/issuing_stubs_oss.go +++ b/builtin/logical/pki/issuing/issuing_stubs_oss.go @@ -9,6 +9,7 @@ import ( "context" ctx509 "github.com/google/certificate-transparency-go/x509" + "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/logical" ) @@ -17,3 +18,7 @@ import ( func entSetCertVerifyOptions(ctx context.Context, storage logical.Storage, issuerId IssuerID, options *ctx509.VerifyOptions) error { return nil } + +func EntAdjustCreationBundle(view logical.SystemView, bundle *certutil.CreationBundle) { + return +} diff --git a/builtin/logical/pki/issuing/sign_cert.go b/builtin/logical/pki/issuing/sign_cert.go index e446e1a1fc..6d88e07445 100644 --- a/builtin/logical/pki/issuing/sign_cert.go +++ b/builtin/logical/pki/issuing/sign_cert.go @@ -318,6 +318,7 @@ func SignCert(b logical.SystemView, role *RoleEntry, entityInfo EntityInfo, caSi if creation.Params == nil { return nil, nil, errutil.InternalError{Err: "nil parameters received from parameter bundle generation"} } + EntAdjustCreationBundle(b, creation) creation.Params.IsCA = signInput.IsCA() creation.Params.UseCSRValues = signInput.UseCSRValues() diff --git a/builtin/logical/pki/path_acme_order.go b/builtin/logical/pki/path_acme_order.go index 23f8a422ed..cf0ed9dc14 100644 --- a/builtin/logical/pki/path_acme_order.go +++ b/builtin/logical/pki/path_acme_order.go @@ -288,7 +288,7 @@ func (b *backend) acmeFinalizeOrderHandler(ac *acmeContext, r *logical.Request, return nil, err } } else { - signedCertBundle, issuerId, err = issueCertFromCsr(ac, csr) + signedCertBundle, issuerId, err = issueCertFromCsr(b, ac, csr) if err != nil { return nil, err } @@ -553,7 +553,7 @@ func maybeAugmentReqDataWithSuitableCN(ac *acmeContext, csr *x509.CertificateReq } } -func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuing.IssuerID, error) { +func issueCertFromCsr(b *backend, ac *acmeContext, csr *x509.CertificateRequest) (*certutil.ParsedCertBundle, issuing.IssuerID, error) { pemBlock := &pem.Block{ Type: "CERTIFICATE REQUEST", Headers: nil, @@ -625,6 +625,7 @@ func issueCertFromCsr(ac *acmeContext, csr *x509.CertificateRequest) (*certutil. // unit, we have no way of validating this (via ACME here, without perhaps // an external policy engine), and thus should not be setting it on our // final issued certificate. + b.adjustInputBundle(input) parsedBundle, _, err := signCert(ac.sc.System(), input, signingBundle, false /* is_ca=false */, false /* use_csr_values */) if err != nil { return nil, "", fmt.Errorf("%w: refusing to sign CSR: %s", ErrBadCSR, err.Error()) diff --git a/builtin/logical/pki/path_issue_sign.go b/builtin/logical/pki/path_issue_sign.go index 6ef06d908f..bef2c3dcba 100644 --- a/builtin/logical/pki/path_issue_sign.go +++ b/builtin/logical/pki/path_issue_sign.go @@ -434,6 +434,7 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d apiData: data, role: role, } + b.adjustInputBundle(input) var parsedBundle *certutil.ParsedCertBundle var warnings []string if useCSR { diff --git a/builtin/logical/pki/path_roles.go b/builtin/logical/pki/path_roles.go index 0749cdb0ae..410367a8ef 100644 --- a/builtin/logical/pki/path_roles.go +++ b/builtin/logical/pki/path_roles.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/consts" - "github.com/hashicorp/vault/sdk/helper/errutil" "github.com/hashicorp/vault/sdk/logical" ) @@ -898,7 +897,7 @@ func (b *backend) GetRole(ctx context.Context, s logical.Storage, n string) (*is } // Ensure the role is valid after updating. - _, err = validateRole(b, result, ctx, s) + _, _, err = validateRole(b, result, ctx, s) if err != nil { return nil, err } @@ -948,8 +947,9 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data * return nil, nil } - resp := &logical.Response{ - Data: role.ToResponseData(), + warnings, err := entValidateRole(b, role) + if err != nil { + return nil, err } b.pkiObserver.RecordPKIObservation(ctx, req, observe.ObservationTypePKIRoleRead, @@ -957,7 +957,10 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data * observe.NewAdditionalPKIMetadata("role_name", role.Name), ) - return resp, nil + return &logical.Response{ + Data: role.ToResponseData(), + Warnings: warnings, + }, nil } func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) { @@ -1059,15 +1062,21 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data } } - resp, err := validateRole(b, entry, ctx, req.Storage) + userError, warnings, err := validateRole(b, entry, ctx, req.Storage) if err != nil { return nil, err } if warning != "" { - resp.AddWarning(warning) + warnings = append(warnings, warning) } - if resp.IsError() { - return resp, nil + if userError != "" { + return logical.ErrorResponse(userError), nil + } + + if entWarnings, err := entValidateRole(b, entry); err != nil { + return nil, err + } else { + warnings = append(warnings, entWarnings...) } // Store it @@ -1089,42 +1098,44 @@ func (b *backend) pathRoleCreate(ctx context.Context, req *logical.Request, data observe.NewAdditionalPKIMetadata("not_before_duration", entry.NotBeforeDuration.String()), ) - return resp, nil + return &logical.Response{ + Warnings: warnings, + Data: entry.ToResponseData(), + }, nil } -func validateRole(b *backend, entry *issuing.RoleEntry, ctx context.Context, s logical.Storage) (*logical.Response, error) { - resp := &logical.Response{} - var err error - +// validateRole takes a role and performs various checks and mutations against it. +// If it returns an error, the calling handler should also return an error as +// opposed to a logical response. If instead it returns a nonempty string, +// that should be returned as a logical.ErrorResponse to the client. Otherwise, +// the warnings should be included in any response to the client. +func validateRole(b *backend, entry *issuing.RoleEntry, ctx context.Context, s logical.Storage) (string, []string, error) { if entry.MaxTTL > 0 && entry.TTL > entry.MaxTTL { - return logical.ErrorResponse( - `"ttl" value must be less than "max_ttl" value`, - ), nil + return `"ttl" value must be less than "max_ttl" value`, nil, nil } - if entry.KeyBits, entry.SignatureBits, err = certutil.ValidateDefaultOrValueKeyTypeSignatureLength(entry.KeyType, entry.KeyBits, entry.SignatureBits); err != nil { - return logical.ErrorResponse(err.Error()), nil + keyBits, signatureBits, err := certutil.ValidateDefaultOrValueKeyTypeSignatureLength(entry.KeyType, entry.KeyBits, entry.SignatureBits) + if err != nil { + return err.Error(), nil, nil } if entry.SerialNumberSource != "" && entry.SerialNumberSource != "json-csr" && entry.SerialNumberSource != "json" { - return logical.ErrorResponse("unknown serial_number_source %s", entry.SerialNumberSource), nil + return fmt.Sprintf("unknown serial_number_source %s", entry.SerialNumberSource), nil, nil } if len(entry.ExtKeyUsageOIDs) > 0 { for _, oidstr := range entry.ExtKeyUsageOIDs { - _, err := certutil.StringToOid(oidstr) - if err != nil { - return logical.ErrorResponse(fmt.Sprintf("%q could not be parsed as a valid oid for an extended key usage", oidstr)), nil + if _, err := certutil.StringToOid(oidstr); err != nil { + return fmt.Sprintf("%q could not be parsed as a valid oid for an extended key usage", oidstr), nil, nil } } } if len(entry.PolicyIdentifiers) > 0 { - _, err := certutil.CreatePolicyInformationExtensionFromStorageStrings(entry.PolicyIdentifiers) - if err != nil { - return nil, err + if _, err := certutil.CreatePolicyInformationExtensionFromStorageStrings(entry.PolicyIdentifiers); err != nil { + return "", nil, err } } @@ -1132,36 +1143,38 @@ func validateRole(b *backend, entry *issuing.RoleEntry, ctx context.Context, s l // resolve the reference (to an issuerId) at role creation time; instead, // resolve it at use time. This allows values such as `default` or other // user-assigned names to "float" and change over time. - if len(entry.Issuer) == 0 { - entry.Issuer = defaultRef + issuer := entry.Issuer + if len(issuer) == 0 { + issuer = defaultRef } + + var warnings []string // Check that the issuers reference set resolves to something if !b.UseLegacyBundleCaStorage() { sc := b.makeStorageContext(ctx, s) - issuerId, err := sc.resolveIssuerReference(entry.Issuer) + issuerId, err := sc.resolveIssuerReference(issuer) if err != nil { if issuerId == issuing.IssuerRefNotFound { - resp = &logical.Response{} if entry.Issuer == defaultRef { - resp.AddWarning("Issuing Certificate was set to default, but no default issuing certificate (configurable at /config/issuers) is currently set") + warnings = append(warnings, "Issuing Certificate was set to default, but no default issuing certificate (configurable at /config/issuers) is currently set") } else { - resp.AddWarning(fmt.Sprintf("Issuing Certificate was set to %s but no issuing certificate currently has that name", entry.Issuer)) + warnings = append(warnings, fmt.Sprintf("Issuing Certificate was set to %s but no issuing certificate currently has that name", issuer)) } } else { - return nil, err + return "", nil, err } } } // Ensures CNValidations are alright - entry.CNValidations, err = checkCNValidations(entry.CNValidations) + cnValidations, err := checkCNValidations(entry.CNValidations) if err != nil { - return nil, errutil.UserError{Err: err.Error()} + return err.Error(), nil, nil } - resp.Data = entry.ToResponseData() - return resp, nil + entry.CNValidations, entry.Issuer, entry.KeyBits, entry.SignatureBits = cnValidations, issuer, keyBits, signatureBits + return "", warnings, nil } func getWithExplicitDefault(data *framework.FieldData, field string, defaultValue interface{}) interface{} { @@ -1284,15 +1297,21 @@ func (b *backend) pathRolePatch(ctx context.Context, req *logical.Request, data } } - resp, err := validateRole(b, entry, ctx, req.Storage) + userError, warnings, err := validateRole(b, entry, ctx, req.Storage) if err != nil { return nil, err } if warning != "" { - resp.AddWarning(warning) + warnings = append(warnings, warning) } - if resp.IsError() { - return resp, nil + if userError != "" { + return logical.ErrorResponse(userError), nil + } + + if entWarnings, err := entValidateRole(b, entry); err != nil { + return nil, err + } else { + warnings = append(warnings, entWarnings...) } // Store it @@ -1314,7 +1333,10 @@ func (b *backend) pathRolePatch(ctx context.Context, req *logical.Request, data observe.NewAdditionalPKIMetadata("not_before_duration", entry.NotBeforeDuration.String()), ) - return resp, nil + return &logical.Response{ + Warnings: warnings, + Data: entry.ToResponseData(), + }, nil } func checkCNValidations(validations []string) ([]string, error) { diff --git a/builtin/logical/pki/path_root.go b/builtin/logical/pki/path_root.go index 77a1fefaf2..e57c7aabbd 100644 --- a/builtin/logical/pki/path_root.go +++ b/builtin/logical/pki/path_root.go @@ -176,6 +176,7 @@ func (b *backend) pathCAGenerateRoot(ctx context.Context, req *logical.Request, apiData: data, role: role, } + b.adjustInputBundle(input) parsedBundle, warnings, err := generateCert(sc, input, nil, true, b.Backend.GetRandomReader()) if err != nil { switch err.(type) { @@ -435,6 +436,7 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R apiData: data, role: role, } + b.adjustInputBundle(input) parsedBundle, warnings, err := signCert(b.System(), input, signingBundle, true, useCSRValues) if err != nil { switch err.(type) { diff --git a/changelog/_10739.txt b/changelog/_10739.txt new file mode 100644 index 0000000000..fe40cd9f45 --- /dev/null +++ b/changelog/_10739.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/pki (enterprise): When the common_criteria_mode feature flag is enabled, NotBefore will always be treated as zero. +``` diff --git a/sdk/helper/certutil/helpers.go b/sdk/helper/certutil/helpers.go index 7de849cdb2..afca868d4b 100644 --- a/sdk/helper/certutil/helpers.go +++ b/sdk/helper/certutil/helpers.go @@ -933,6 +933,9 @@ func createCertificate(data *CreationBundle, randReader io.Reader, privateKeyGen if data.Params.NotBeforeDuration > 0 { certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration) } + if data.Params.ZeroNotBefore { + certTemplate.NotBefore = time.Now() + } if err := HandleOtherSANs(certTemplate, data.Params.OtherSANs); err != nil { return nil, errutil.InternalError{Err: errwrap.Wrapf("error marshaling other SANs: {{err}}", err).Error()} @@ -1296,6 +1299,9 @@ func signCertificate(data *CreationBundle, randReader io.Reader) (*ParsedCertBun if data.Params.NotBeforeDuration > 0 { certTemplate.NotBefore = time.Now().Add(-1 * data.Params.NotBeforeDuration) } + if data.Params.ZeroNotBefore { + certTemplate.NotBefore = time.Now() + } privateKeyType := data.SigningBundle.PrivateKeyType if privateKeyType == ManagedPrivateKey { diff --git a/sdk/helper/certutil/types.go b/sdk/helper/certutil/types.go index 5c1983bd75..46dff9c596 100644 --- a/sdk/helper/certutil/types.go +++ b/sdk/helper/certutil/types.go @@ -810,6 +810,8 @@ type CreationParameters struct { // sender of the CSR has proven proof of possession of the associated // private key by some other means, otherwise keep this set to false. IgnoreCSRSignature bool + + ZeroNotBefore bool } type CreationBundle struct {