Skip to content
ecdsa.com

Verify a certificate chain · Go

How to verify a certificate chain against your own trust store in Go

To verify a certificate chain against your own trust store in Go, call cert.Verify with x509.VerifyOptions: Roots is a CertPool with the roots you trust, Intermediates a pool of untrusted candidates, DNSName the expected host. It returns every valid chain or an error. This recipe validates a leaf → intermediate → root chain from a test CA and three chains that must fail, with the standard library only.

Tested with Go 1.27.3 (crypto/x509, linux/arm64).

verify.go — path validation with crypto/x509
package main
import "crypto/x509"
// verifyChain builds and checks every path from leaf to root: signatures,
// validity periods, CA constraints, key usage and the host name.
func verifyChain(leaf *x509.Certificate, intermediates []*x509.Certificate, root *x509.Certificate, host string) ([][]*x509.Certificate, error) {
roots := x509.NewCertPool() // only this root is trusted, not the system store
roots.AddCert(root)
pool := x509.NewCertPool() // candidates for the path, not trusted on their own
for _, cert := range intermediates {
pool.AddCert(cert)
}
return leaf.Verify(x509.VerifyOptions{
Roots: roots,
Intermediates: pool,
DNSName: host,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, // the default when empty
})
}

How it works

  1. x509.NewCertPool() plus AddCert(root) makes a trust store with just your root; a nil Roots would mean the system store.
  2. Intermediates holds certificates that may appear in the path but are not trusted by themselves — typically the extra certificates a server sent.
  3. Verify checks signatures, validity, basic constraints, name constraints and extended key usage (ExtKeyUsageServerAuth by default), and the host name when DNSName is set.
  4. The result is a slice of chains, leaf first; the run prints api.example.com → ECDSA.com Howto Issuing CA 1 → ECDSA.com Howto Root CA.

Gotchas

  • A look-alike root fails with x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate …) — Go found a root with the right name, but its key did not sign the intermediate.
  • The missing intermediate gives the same x509: certificate signed by unknown authority, without the hint; Go does not fetch issuers from AIA URLs.
  • The expired leaf fails with x509: certificate has expired or is not yet valid: current time … is after 2025-03-31T23:59:59Z; set CurrentTime to validate at another moment.
  • Leave KeyUsages empty and Go requires serverAuth; asking for another usage the certificate lacks fails with x509: certificate specifies an incompatible key usage (code signing in the run). Pass the usage you need, or x509.ExtKeyUsageAny deliberately.

Related recipes