Enforce Checks on Chain in Common Criteria Mode (#10915) (#11251)

* Add Disable-Time-Check flag, and also respect common criteria when doing so.

* Switch to EnableTimeChecks to not change default behavior.

* Check Common Criteria Flag Before Disabling Verification.

* Add Changelog.

* Update builtin/logical/pki/issuing/cert_verify_ent.go



* Update changelog/_10915.txt



* PR feedback.

* Merge-fix

* Test case requested by PR review.

---------

Co-authored-by: Kit Haines <khaines@mit.edu>
Co-authored-by: Steven Clark <steven.clark@hashicorp.com>
This commit is contained in:
Vault Automation 2025-12-15 14:08:43 -05:00 committed by GitHub
parent e78aea2ec1
commit f9bb8aa7d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 29 additions and 12 deletions

View file

@ -10,6 +10,7 @@ import (
ctx509 "github.com/google/certificate-transparency-go/x509"
"github.com/hashicorp/vault/sdk/helper/certutil"
"github.com/hashicorp/vault/sdk/logical"
)
// disableVerifyCertificateEnvVar is an environment variable that can be used to disable the
@ -32,7 +33,7 @@ func isCertificateVerificationDisabled() (bool, error) {
return disable, nil
}
func VerifyCertificate(issuer *IssuerEntry, parsedBundle *certutil.ParsedCertBundle) error {
func VerifyCertificate(issuer *IssuerEntry, system logical.SystemView, parsedBundle *certutil.ParsedCertBundle) error {
if verificationDisabled, err := isCertificateVerificationDisabled(); err != nil {
return err
} else if verificationDisabled {
@ -51,9 +52,11 @@ func VerifyCertificate(issuer *IssuerEntry, parsedBundle *certutil.ParsedCertBun
DisablePathLenChecks: false,
DisableNameConstraintChecks: false,
}
if err := entSetCertVerifyOptions(issuer, &options); err != nil {
isCommonCriteria, err := entSetCertVerifyOptions(issuer, system, &options)
if err != nil {
return err
}
return certutil.VerifyCertificate(parsedBundle, options)
return certutil.VerifyCertificateChain(parsedBundle, options, isCommonCriteria)
}

View file

@ -13,8 +13,8 @@ import (
//go:generate go run github.com/hashicorp/vault/tools/stubmaker
func entSetCertVerifyOptions(issuer *IssuerEntry, options *ctx509.VerifyOptions) error {
return nil
func entSetCertVerifyOptions(issuer *IssuerEntry, view logical.SystemView, options *ctx509.VerifyOptions) (bool, error) {
return false, nil
}
func EntAdjustCreationBundle(view logical.SystemView, bundle *certutil.CreationBundle) {

View file

@ -633,7 +633,7 @@ func issueCertFromCsr(b *backend, ac *acmeContext, csr *x509.CertificateRequest)
return nil, "", fmt.Errorf("%w: refusing to sign CSR: %s", ErrBadCSR, err.Error())
}
if err = issuing.VerifyCertificate(ac.Issuer, parsedBundle); err != nil {
if err = issuing.VerifyCertificate(ac.Issuer, ac.sc.System(), parsedBundle); err != nil {
return nil, "", fmt.Errorf("verification of parsed bundle failed: %w", err)
}

View file

@ -460,7 +460,7 @@ func (b *backend) pathIssueSignCert(ctx context.Context, req *logical.Request, d
return nil, err
}
if err = issuing.VerifyCertificate(issuer, parsedBundle); err != nil {
if err = issuing.VerifyCertificate(issuer, sc.System(), parsedBundle); err != nil {
return nil, err
}

View file

@ -449,7 +449,7 @@ func (b *backend) pathIssuerSignIntermediate(ctx context.Context, req *logical.R
}
}
if err := issuing.VerifyCertificate(issuer, parsedBundle); err != nil {
if err := issuing.VerifyCertificate(issuer, sc.System(), parsedBundle); err != nil {
return nil, fmt.Errorf("verification of parsed bundle failed: %w", err)
}

3
changelog/_10915.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
secrets/pki (enterprise): Validate entire chain in common criteria mode; add field to enable time checks on validation
```

View file

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/errwrap"
)
func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOptions) error {
func VerifyCertificateChain(parsedBundle *ParsedCertBundle, options ctx509.VerifyOptions, rootChainOnly bool) error {
// If private key exists, check if it matches the public key of cert
if parsedBundle.PrivateKey != nil && parsedBundle.Certificate != nil {
equal, err := ComparePublicKeys(parsedBundle.Certificate.PublicKey, parsedBundle.PrivateKey.Public())
@ -45,9 +45,16 @@ func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOpti
}
}
if len(rootCertPool.Subjects()) < 1 {
// Alright, this is weird, since we don't have the root CA, we'll treat the intermediate as
// the root, otherwise we'll get a "x509: certificate signed by unknown authority" error.
if !rootChainOnly && len(rootCertPool.Subjects()) < 1 {
// In this case, we don't have the root CA. In some cases systems do trust an intermediate
// directly, and this will work. To accommodate those cases, we'll treat the intermediate
// as the root.
//
// In other cases, such as in Common Criteria mode, we are required to return an error if
// no root certificate is present.
//
// If there's no root, and we don't treat the intermediates as root certificates, we'd get
// a "x509: certificate signed by unknown authority" error.
rootCertPool, intermediateCertPool = intermediateCertPool, rootCertPool
}
@ -67,6 +74,10 @@ func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOpti
return err
}
func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOptions) error {
return VerifyCertificateChain(parsedBundle, options, false)
}
func convertCertificate(certBytes []byte) (*ctx509.Certificate, error) {
ret, err := ctx509.ParseCertificate(certBytes)
if err != nil {