vault/sdk/helper/certutil/cert_verification.go
Kit Haines 371ffc4bd4
Move all pki-verification calls from sdk-Verify() to pki-specific (#29342)
* Move all pki-verification calls from sdk-Verify() to pki-specific
VerifyCertifcate(...); update sdk-Verify to allow multiple chains,
but validate that at least one of those chains is valid.

* Updates to Validate on Parse PEMBlock, so that a single cert or a single key parses (test fixes).

* Add changelog.

* Make test certificate expire in a while, not at linux epoch.

* Remove duplicate code.

* Fix header file + go mod tidy.

* Updates based on review.
2025-01-29 11:05:55 -05:00

77 lines
2.5 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package certutil
import (
"bytes"
"fmt"
"time"
ctx509 "github.com/google/certificate-transparency-go/x509"
"github.com/hashicorp/errwrap"
)
func VerifyCertificate(parsedBundle *ParsedCertBundle, options ctx509.VerifyOptions) 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())
if err != nil {
return errwrap.Wrapf("could not compare public and private keys: {{err}}", err)
}
if !equal {
return fmt.Errorf("public key of certificate does not match private key")
}
}
rootCertPool := ctx509.NewCertPool()
intermediateCertPool := ctx509.NewCertPool()
for index, certificate := range parsedBundle.CAChain {
cert, err := convertCertificate(certificate.Bytes)
if err != nil {
return fmt.Errorf("could not parse certificate number %v in chain: %w", index, err)
}
if index > 0 && !cert.IsCA {
// Sometimes the leaf certificate is contained inside the bundle
return fmt.Errorf("certificate %v is not a CA certificate", index)
}
if bytes.Equal(cert.RawIssuer, cert.RawSubject) {
// Occasionally verify is called with a self-signed certificate that is not a CA;
// We don't break that use case here
rootCertPool.AddCert(cert)
} else {
intermediateCertPool.AddCert(cert)
}
}
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.
rootCertPool, intermediateCertPool = intermediateCertPool, rootCertPool
}
// Note that we use github.com/google/certificate-transparency-go/x509 to perform certificate verification,
// since that library provides options to disable checks that the standard library does not.
options.Roots = rootCertPool
options.Intermediates = intermediateCertPool
options.CurrentTime = time.Now()
certificate, err := convertCertificate(parsedBundle.CertificateBytes)
if err != nil {
return err
}
_, err = certificate.Verify(options)
return err
}
func convertCertificate(certBytes []byte) (*ctx509.Certificate, error) {
ret, err := ctx509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("cannot convert certificate for validation: %w", err)
}
return ret, nil
}