Force NotBefore=now when common_criteria_mode feature flag is enabled (#10739) (#10849)

This commit is contained in:
Vault Automation 2025-11-20 14:38:39 -05:00 committed by GitHub
parent cb88546ad2
commit 399a10f22d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 105 additions and 44 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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()

View file

@ -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())

View file

@ -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 {

View file

@ -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) {

View file

@ -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) {

3
changelog/_10739.txt Normal file
View file

@ -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.
```

View file

@ -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 {

View file

@ -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 {