153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/x509"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"git.wntrmute.dev/kyle/goutils/certlib"
|
|
"git.wntrmute.dev/kyle/goutils/certlib/revoke"
|
|
"git.wntrmute.dev/kyle/goutils/lib"
|
|
)
|
|
|
|
func printRevocation(cert *x509.Certificate) {
|
|
remaining := time.Until(cert.NotAfter)
|
|
fmt.Printf("certificate expires in %s.\n", lib.Duration(remaining))
|
|
|
|
revoked, ok := revoke.VerifyCertificate(cert)
|
|
if !ok {
|
|
fmt.Fprintf(os.Stderr, "[!] the revocation check failed (failed to determine whether certificate\nwas revoked)")
|
|
return
|
|
}
|
|
|
|
if revoked {
|
|
fmt.Fprintf(os.Stderr, "[!] the certificate has been revoked\n")
|
|
return
|
|
}
|
|
}
|
|
|
|
type appConfig struct {
|
|
caFile, intFile string
|
|
forceIntermediateBundle bool
|
|
revexp, skipVerify, verbose bool
|
|
strictTLS bool
|
|
}
|
|
|
|
func parseFlags() appConfig {
|
|
var cfg appConfig
|
|
flag.StringVar(&cfg.caFile, "ca", "", "CA certificate `bundle`")
|
|
flag.StringVar(&cfg.intFile, "i", "", "intermediate `bundle`")
|
|
flag.BoolVar(&cfg.forceIntermediateBundle, "f", false,
|
|
"force the use of the intermediate bundle, ignoring any intermediates bundled with certificate")
|
|
flag.BoolVar(&cfg.skipVerify, "k", false, "skip CA verification")
|
|
flag.BoolVar(&cfg.revexp, "r", false, "print revocation and expiry information")
|
|
flag.BoolVar(&cfg.verbose, "v", false, "verbose")
|
|
lib.StrictTLSFlag(&cfg.strictTLS)
|
|
flag.Parse()
|
|
return cfg
|
|
}
|
|
|
|
func loadRoots(caFile string, verbose bool) (*x509.CertPool, error) {
|
|
if caFile == "" {
|
|
return x509.SystemCertPool()
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Println("[+] loading root certificates from", caFile)
|
|
}
|
|
return certlib.LoadPEMCertPool(caFile)
|
|
}
|
|
|
|
func loadIntermediates(intFile string, verbose bool) (*x509.CertPool, error) {
|
|
if intFile == "" {
|
|
return x509.NewCertPool(), nil
|
|
}
|
|
if verbose {
|
|
fmt.Println("[+] loading intermediate certificates from", intFile)
|
|
}
|
|
// Note: use intFile here (previously used caFile mistakenly)
|
|
return certlib.LoadPEMCertPool(intFile)
|
|
}
|
|
|
|
func addBundledIntermediates(chain []*x509.Certificate, pool *x509.CertPool, verbose bool) {
|
|
for _, intermediate := range chain[1:] {
|
|
if verbose {
|
|
fmt.Printf("[+] adding intermediate with SKI %x\n", intermediate.SubjectKeyId)
|
|
}
|
|
pool.AddCert(intermediate)
|
|
}
|
|
}
|
|
|
|
func verifyCert(cert *x509.Certificate, roots, ints *x509.CertPool) error {
|
|
opts := x509.VerifyOptions{
|
|
Intermediates: ints,
|
|
Roots: roots,
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
|
|
}
|
|
_, err := cert.Verify(opts)
|
|
return err
|
|
}
|
|
|
|
func run(cfg appConfig) error {
|
|
roots, err := loadRoots(cfg.caFile, cfg.verbose)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ints, err := loadIntermediates(cfg.intFile, cfg.verbose)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if flag.NArg() != 1 {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [-ca bundle] [-i bundle] cert", lib.ProgName())
|
|
}
|
|
|
|
combinedPool, err := certlib.LoadFullCertPool(cfg.caFile, cfg.intFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to build combined pool: %w", err)
|
|
}
|
|
|
|
tlsCfg, err := lib.BaselineTLSConfig(cfg.skipVerify, cfg.strictTLS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tlsCfg.RootCAs = combinedPool
|
|
|
|
chain, err := lib.GetCertificateChain(flag.Arg(0), tlsCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if cfg.verbose {
|
|
fmt.Printf("[+] %s has %d certificates\n", flag.Arg(0), len(chain))
|
|
}
|
|
|
|
cert := chain[0]
|
|
if len(chain) > 1 && !cfg.forceIntermediateBundle {
|
|
addBundledIntermediates(chain, ints, cfg.verbose)
|
|
}
|
|
|
|
if err = verifyCert(cert, roots, ints); err != nil {
|
|
return fmt.Errorf("certificate verification failed: %w", err)
|
|
}
|
|
|
|
if cfg.verbose {
|
|
fmt.Println("OK")
|
|
}
|
|
|
|
if cfg.revexp {
|
|
printRevocation(cert)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
cfg := parseFlags()
|
|
if err := run(cfg); err != nil {
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|