What does calling Verify on an *x509.Certificate with x509.VerifyOptions check, and what does it not?
answer
- two pools with different meanings
- one is trusted, one is only available
- the default usage is server authentication
- dates yes, names yes, revocation never
- it will not fetch a missing issuer for you
basics
~20 sVerify builds chains from that certificate to a root in opts.Roots, using opts.Intermediates as available links, and checks signatures, validity dates, CA constraints, extended key usage and, when DNSName is set, the hostname. It never checks revocation.
solid answer
~40 s`cert.Verify(opts x509.VerifyOptions)` returns `([][]*x509.Certificate, error)` — every chain it could build from the leaf up to a trusted root, each slice starting with the leaf and ending at the root. `opts.Roots` is the trust anchor set; `opts.Intermediates` is a pool of candidate links that are *not* trusted, merely available for path building. Along each candidate chain it verifies signatures, checks validity against `opts.CurrentTime` (zero means now), enforces CA and path-length and name constraints, and matches extended key usage against `opts.KeyUsages`, which defaults to `ExtKeyUsageServerAuth` when nil. If `opts.DNSName` is set, the leaf's SANs must match. What it deliberately does not do: any revocation check — no CRL, no OCSP — and it never fetches a missing issuer over the network. If `opts.Roots` is nil it falls back to the system trust store.
code
go · 13 lineschains, err := leaf.Verify(x509.VerifyOptions{
Roots: roots, // trust anchors
Intermediates: intermediates, // links only, not trusted
DNSName: "api.example.com",
CurrentTime: time.Now(), // zero value already means now
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
})
if err != nil {
return fmt.Errorf("chain rejected: %w", err)
}
for _, hop := range chains[0] {
log.Printf("%s expires %s", hop.Subject.CommonName, hop.NotAfter)
}go deeper
Know that verification is one call on the leaf certificate, that you must supply trusted roots in the options, and that a non-nil error means the chain was not accepted.
Be able to walk each option field and say what it controls: Roots versus Intermediates, CurrentTime, DNSName, and the KeyUsages default of server authentication. State plainly that revocation is not part of the check.
Demonstrate judgment about the gaps: no revocation and no network fetch of missing issuers means your service accepts a revoked certificate and rejects a server that omits its intermediate. Say what you would build or monitor to cover each.
Own the policy those options encode — which roots the fleet trusts, whether revocation is handled at all, and who is accountable when a CA migration adds a second root. Those are decisions with a blast radius, not per-service configuration.
## The call ``` chains, err := leaf.Verify(x509.VerifyOptions{ ... }) ``` The receiver is the leaf certificate — the one whose trustworthiness is in question. The return is `[][]*x509.Certificate`: one entry per valid chain found, and there can legitimately be more than one when a CA is cross-signed. Each inner slice is ordered leaf first, trusted root last. A non-nil error means no chain could be built and validated. ## The two pools, which are not interchangeable `opts.Roots` holds **trust anchors**. A chain is only accepted if its last certificate is in this pool. `opts.Intermediates` holds **candidates for path building** — certificates the verifier may use as links, with no trust implied. Putting an intermediate in `Roots` is a real security mistake, because it makes that intermediate a trust anchor in its own right and everything it signed becomes trusted, whether or not the real root would have permitted it. If `Roots` is nil, Go falls back to the host's trust store, and on platforms with a native verifier the details of the check are the platform's rather than Go's. ## What it checks - **Signatures.** Each certificate's signature is verified with its issuer's public key, all the way to a root in `Roots`. - **Validity window.** Every certificate in the chain must be valid at `opts.CurrentTime`. The zero value means "now", which is what you want in a server; setting it explicitly is how a diagnostic tool answers "would this chain have been valid last Tuesday?" or "will it still be valid in thirty days?". - **CA and constraints.** Non-leaf certificates must be CAs, path-length constraints are enforced, and name constraints carried by an intermediate are applied to the names in the chain. - **Extended key usage.** `opts.KeyUsages` lists acceptable EKUs; a chain is accepted if it permits any of them. **A nil or empty list means `ExtKeyUsageServerAuth`** — a default that surprises people verifying a *client* certificate, whose EKU is `ExtKeyUsageClientAuth` and which therefore fails against the default. Passing `x509.ExtKeyUsageAny` disables the check. - **Hostname.** If `opts.DNSName` is non-empty, the leaf must carry a matching subject alternative name. Modern Go matches only SANs — `DNSNames` and `IPAddresses` — and ignores the Subject CommonName entirely, so a certificate whose only identity is a CN fails no matter how it looks in a viewer. You can also run this check separately with `cert.VerifyHostname(host)`. ## What it does not check - **Revocation.** There is no CRL fetch and no OCSP query, at any depth. A revoked-but-unexpired certificate verifies happily. If revocation matters, it is a separate mechanism you build on top. - **Missing issuers.** Go's verifier never goes to the network to find one. It builds paths strictly out of what you handed it in the two pools. This is the single most common reason a chain that a browser accepts fails here: browsers cache intermediates from earlier connections and may fetch them, so a server that forgets to send its intermediate looks fine in a browser and fails in Go. ## Reading the error The error types are typed structures you can inspect with `errors.As`. `x509.UnknownAuthorityError` means no path to a trusted root. `x509.CertificateInvalidError` carries a `Reason` field — `x509.Expired` for a validity failure, plus reasons for CA and constraint violations. `x509.HostnameError` means the chain was fine but the name did not match. Distinguishing these three turns a vague TLS failure into a specific one, and the distinction is worth wiring into any tool whose output another engineer reads. ## Using the returned chains Most callers ignore the chains and check only the error, but the chains are useful: they tell you which root was actually reached, which matters when two roots are present during a CA migration, and they let a diagnostic print every hop's subject and expiry so the operator can see exactly which certificate in the path expires first.
- Why is putting an intermediate into opts.Roots instead of opts.Intermediates a security problem?`Roots` is the set of trust anchors, so anything placed there is trusted on its own authority. An intermediate promoted to a root means every certificate it ever signed is accepted, including ones the real root's constraints would have rejected, and the chain stops before those constraints are ever evaluated. `Intermediates` supplies the same certificate as a link without granting it that power.
- You are verifying a client certificate and Verify fails even though the chain is correct. What is the likely cause?`opts.KeyUsages` was left nil, which means `ExtKeyUsageServerAuth`. A client certificate carries `ExtKeyUsageClientAuth`, so the chain is rejected on usage rather than on trust. Set `KeyUsages` to `[]x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}` explicitly. The error is a `CertificateInvalidError`, which is why it reads as an invalid certificate rather than an untrusted one.
- What is opts.CurrentTime good for beyond leaving it at its zero value?The zero value means now, which is right in a server. Setting it explicitly is what makes an inspection tool useful: pass a future time to answer "does this chain still verify in sixty days?" and find the hop that expires first, or pass a past time to confirm that an incident window really was a certificate expiry rather than something else.
- Why does Verify return a slice of chains rather than a single chain?A CA can be cross-signed, so a leaf may reach more than one trusted root through different paths, and all of them are valid. Returning every chain lets a caller see which root was actually used — genuinely useful during a CA migration when both the old and the new root are in the pool and you want to know which one is still carrying traffic.
saying these in an interview costs you the question
- Claims Verify performs OCSP or CRL revocation checking
- Expects Go to download a missing intermediate automatically
- Puts intermediates into Roots so the chain resolves
- Forgets that a nil KeyUsages means server authentication only
- Believes Subject CommonName still satisfies the DNSName check
- Reads any non-nil error as 'untrusted' without inspecting its type