x509.CreateCertificate produced a CA whose signatures verifiers reject as unauthorized — which template fields were unset?
answer
- one flag is a switch, not a property
- set, but never encoded
- absent is permissive, present is exhaustive
- the zero time is year one
- verify what you issued, in the build
basics
~10 sAlmost always BasicConstraintsValid, which must be true or IsCA is never encoded, so verifiers see an ordinary end-entity certificate. The issuer's KeyUsage must also include x509.KeyUsageCertSign whenever KeyUsage is set at all.
solid answer
~50 sTwo template fields work as a pair: `IsCA` is only written into the certificate when `BasicConstraintsValid` is also true, so `IsCA: true` alone produces a certificate with no basic constraints extension — indistinguishable from a leaf, and rejected as an issuer by every verifier including Go's own. The second is `KeyUsage`: an absent key usage extension imposes no restriction, but a present one that omits `x509.KeyUsageCertSign` positively forbids certificate signing, so a CA that sets `KeyUsageDigitalSignature` and nothing else has locked itself out. Related unset-field traps are a zero `NotAfter`, which encodes a validity window ending in year 1 so everything reports the certificate expired, and a leaf whose `ExtKeyUsage` omits `ExtKeyUsageServerAuth`, which fails a TLS server check. All of these are silent at issuance time; the fix is a CI test that issues and then verifies for the intended usage.
code
go · 10 linescaTmpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: "platform issuing CA"},
NotBefore: now.Add(-5 * time.Minute), // tolerate clock skew
NotAfter: now.AddDate(5, 0, 0),
IsCA: true,
BasicConstraintsValid: true, // without this, IsCA is never encoded
MaxPathLenZero: true, // may sign leaves, not further CAs
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
}go deeper
Learn the pairing: IsCA only takes effect when BasicConstraintsValid is also true, and an issuing certificate needs KeyUsageCertSign. Also set NotBefore and NotAfter explicitly, because their zero value is year 1.
Explain the asymmetry of usage extensions — absent means unrestricted, present means exhaustive — and why that makes a partially filled KeyUsage worse than an empty one on a CA.
Show that you debug upward from the rejected leaf to the issuer, and that you close the loop with a round-trip test in CI that issues and then verifies for the intended usage rather than trusting a template review.
Decide what certificate profiles your platform offers and where they are defined once, so that individual services cannot hand-roll a template; then require that every profile is covered by an issue-and-verify test before it can be used.
## Why this fails silently `x509.CreateCertificate` validates almost nothing about intent. It checks that a serial is present and that the keys are usable, signs the structure you described, and returns bytes. A template that describes a nonsensical certificate produces a perfectly well-formed nonsensical certificate. The failure surfaces on some other machine, in some other language, at handshake time — which is exactly the pattern the platform engineer replacing a runbook of shell commands walks into, because the old commands carried a configuration file that set these fields and the new Go code does not. ## The paired-field trap: IsCA and BasicConstraintsValid `x509.Certificate` carries `IsCA bool`, `MaxPathLen int`, `MaxPathLenZero bool` and `BasicConstraintsValid bool`. The last one is not a property of the certificate — it is a switch that says "the other three are meaningful, encode them". Set `IsCA: true` and leave `BasicConstraintsValid` false and the issued certificate simply has no basic constraints extension. What happens next is the interesting part. A certificate with no basic constraints extension is treated as an end entity: it may not issue. When Go verifies a chain it rejects a parent that either lacks valid basic constraints or has them and is not a CA, returning a constraint violation. Other stacks do the same. So the symptom is that every leaf your new CA signs is rejected — not because the leaf is wrong, but because the thing above it is not allowed to be above anything. The companion field matters too. `MaxPathLenZero: true` (with `BasicConstraintsValid: true`) encodes a path length of zero, meaning this CA may sign leaves but no further intermediates. That is the right posture for an issuing CA and it needs the explicit boolean because `MaxPathLen: 0` is indistinguishable from the field's zero value. ## The second trap: KeyUsage semantics are asymmetric `KeyUsage` is a bitmask, and its semantics are easy to get backwards. If the extension is **absent** — the field left at zero — no usage restriction is asserted and a verifier will not object. If the extension is **present**, it is exhaustive: any usage not listed is forbidden. So a CA template that sets `KeyUsage: x509.KeyUsageDigitalSignature` has actively declared that it may not sign certificates, and Go's chain check will reject it for that reason. A correct issuing CA sets `x509.KeyUsageCertSign` and, if it will publish revocation lists, `x509.KeyUsageCRLSign` alongside it. The leaf has the mirror-image version of this. `ExtKeyUsage` is a slice; leaving it empty writes no extended key usage extension, which is permissive. Setting it makes the list exhaustive — a certificate whose only extended usage is `x509.ExtKeyUsageClientAuth` cannot serve TLS, and Go's verification defaults to requiring server authentication when you do not say otherwise. For an issuing service that mints workload certificates used in both directions, that means listing both `ExtKeyUsageServerAuth` and `ExtKeyUsageClientAuth` deliberately, rather than discovering which one production needed. ## The third trap: validity times you forgot to set `NotBefore` and `NotAfter` are `time.Time` values with no defaults. A zero `time.Time` is year 1, so a certificate whose `NotAfter` was never set encodes a validity window that closed nineteen centuries before anyone ran the code, and every verifier calls it expired. A zero `NotBefore` is less dramatic but still wrong: it advertises that the certificate was valid for two millennia before its key existed. Set both explicitly, and give `NotBefore` a small backdate — a minute or five — so that clock skew on the verifying machine does not reject a certificate issued seconds ago. ## The diagnostic that ends this class of bug Every failure above is invisible at issuance and expensive at use. The fix is not more careful reading; it is a round trip in CI. Call the same issuing code path production uses to mint a CA and a leaf under it, then verify the leaf against that CA for the exact usage it is meant to serve — the hostname it will be presented for, and the extended key usage the peer will require. A table-driven test over the certificate profiles your platform issues turns every one of these into a failing build. It is worth adding the negative cases too: assert that the CA cannot be used as a leaf, that a client-only certificate is rejected for server use, and that a certificate outside its validity window fails. Those assertions document the profile far better than a comment, and they catch the day someone "tidies up" the template and drops a field. ## Reviewing the change When this lands as a pull request, the two lines worth stopping on are the CA template's basic constraints pair and the key usage bitmask. If `IsCA` appears without `BasicConstraintsValid` immediately beside it, the code is wrong. If `KeyUsage` is set on a CA without certificate signing in the mask, the code is wrong. Everything else is recoverable by reissuing; those two produce a hierarchy that has never worked and looks like it should.
- A leaf your issuer signs has ExtKeyUsage set to only x509.ExtKeyUsageClientAuth. What breaks?It cannot be used as a TLS server certificate. An extended key usage extension, once present, is exhaustive, and Go's verification requires server authentication unless you explicitly ask for a different usage. Leaving the slice empty writes no extension and is permissive, but many environments treat an unrestricted certificate as a policy violation, so listing the usages you mean is the better habit.
- How do you stop your issuing CA from being able to mint further intermediates?Set `BasicConstraintsValid: true`, `IsCA: true` and `MaxPathLenZero: true`. That encodes a maximum path length of zero, so the CA may sign end-entity certificates but nothing that can itself issue. The explicit boolean is needed because a `MaxPathLen` of zero is otherwise indistinguishable from the field being unset.
- What would you put in CI so this class of mistake cannot ship again?A round-trip test: issue a CA and a leaf through the production code path, then verify the leaf against that CA for the hostname and extended key usage it is meant to serve, plus negative cases — a client-only certificate rejected for server use, a certificate outside its window rejected. It runs in milliseconds and turns a silent misconfiguration into a red build.
saying these in an interview costs you the question
- Sets IsCA true and leaves BasicConstraintsValid false
- Thinks KeyUsageDigitalSignature lets a CA sign certificates
- Believes an absent key usage extension forbids everything
- Leaves NotBefore and NotAfter at the zero time
- Debugs the rejected leaf instead of the issuer that signed it