Files
goutils/certlib/verify/verify.go
Kyle Isom 154d5a6c2e Major refactoring.
+ Many lib functions have been split out into separate packages.
+ Adding cert/key generation tooling.
+ Add new time.Duration parser.
2025-11-19 01:35:37 -08:00

145 lines
3.2 KiB
Go

package verify
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"git.wntrmute.dev/kyle/goutils/certlib/revoke"
"git.wntrmute.dev/kyle/goutils/lib/dialer"
"git.wntrmute.dev/kyle/goutils/lib/fetch"
)
func bundleIntermediates(w io.Writer, chain []*x509.Certificate, pool *x509.CertPool, verbose bool) *x509.CertPool {
for _, intermediate := range chain[1:] {
if verbose {
fmt.Fprintf(w, "[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId)
}
pool.AddCert(intermediate)
}
return pool
}
type Opts struct {
Verbose bool
Config *tls.Config
Intermediates *x509.CertPool
ForceIntermediates bool
CheckRevocation bool
KeyUsages []x509.ExtKeyUsage
}
type verifyResult struct {
chain []*x509.Certificate
roots *x509.CertPool
ints *x509.CertPool
}
func prepareVerification(w io.Writer, target string, opts *Opts) (*verifyResult, error) {
var (
roots, ints *x509.CertPool
err error
)
if opts == nil {
opts = &Opts{
Config: dialer.StrictBaselineTLSConfig(),
ForceIntermediates: false,
}
}
if opts.Config.RootCAs == nil {
roots, err = x509.SystemCertPool()
if err != nil {
return nil, fmt.Errorf("couldn't load system cert pool: %w", err)
}
opts.Config.RootCAs = roots
}
if opts.Intermediates == nil {
ints = x509.NewCertPool()
} else {
ints = opts.Intermediates.Clone()
}
roots = opts.Config.RootCAs.Clone()
chain, err := fetch.GetCertificateChain(target, opts.Config)
if err != nil {
return nil, fmt.Errorf("fetching certificate chain: %w", err)
}
if opts.Verbose {
fmt.Fprintf(w, "[+] %s has %d certificates\n", target, len(chain))
}
if len(chain) > 1 && opts.ForceIntermediates {
ints = bundleIntermediates(w, chain, ints, opts.Verbose)
}
return &verifyResult{
chain: chain,
roots: roots,
ints: ints,
}, nil
}
// Chain fetches the certificate chain for a target and verifies it.
func Chain(w io.Writer, target string, opts *Opts) ([]*x509.Certificate, error) {
result, err := prepareVerification(w, target, opts)
if err != nil {
return nil, fmt.Errorf("certificate verification failed: %w", err)
}
chains, err := CertWith(result.chain[0], result.roots, result.ints, opts.CheckRevocation, opts.KeyUsages...)
if err != nil {
return nil, fmt.Errorf("certificate verification failed: %w", err)
}
return chains, nil
}
// CertWith verifies a certificate against a set of roots and intermediates.
func CertWith(
cert *x509.Certificate,
roots, ints *x509.CertPool,
checkRevocation bool,
keyUses ...x509.ExtKeyUsage,
) ([]*x509.Certificate, error) {
if len(keyUses) == 0 {
keyUses = []x509.ExtKeyUsage{x509.ExtKeyUsageAny}
}
opts := x509.VerifyOptions{
Intermediates: ints,
Roots: roots,
KeyUsages: keyUses,
}
chains, err := cert.Verify(opts)
if err != nil {
return nil, err
}
if checkRevocation {
revoked, ok := revoke.VerifyCertificate(cert)
if !ok {
return nil, errors.New("failed to check certificate revocation status")
}
if revoked {
return nil, errors.New("certificate is revoked")
}
}
if len(chains) == 0 {
return nil, errors.New("no valid certificate chain found")
}
return chains[0], nil
}