Enforce X.509 name constraints, so HTTPS netboot works against FOG's CA - #6
Merged
Merged
Conversation
FOG issues its web certificate through a "FOG Web CA" intermediate carrying
a critical nameConstraints extension. iPXE's x509_extensions[] knows five
extensions -- basicConstraints, keyUsage, extKeyUsage, authorityInfoAccess
and subjectAltName -- and x509_parse_extension() refuses any critical
extension it does not recognise:
if ( ! extension ) {
if ( is_critical ) {
/* Fail if we cannot handle a critical extension */
return -ENOTSUP_EXTENSION;
So iPXE cannot parse FOG's own Web CA, and the netboot chain dies with
https://<server>/fog/service/ipxe/boot.php... Operation not supported
(https://ipxe.org/3c16e283)
That is the -ENOTSUP_EXTENSION above. The binary boots, iPXE starts and
reports HTTPS among its features, and only then cannot read the
certificate it was given -- which is why it reads like a TLS or DNS fault.
The alternative was to stop marking the constraints critical. The flag
exists so that a verifier which cannot enforce a constraint refuses the
certificate rather than ignoring it; dropping it would turn every such
verifier from fail-closed to fail-open to accommodate one that happens to
be ours. Fixing ours is the honest direction. See fogproject ADR 0016.
Implementation notes:
- Enforced in x509_validate_chain(), not x509_validate(). A constraining
CA binds every certificate below it, not just the one it signed.
path_remaining can live in x509_validate() because an inherited integer
collapses to a single value; a set of permitted subtrees does not.
Checking only the immediate issuer would be correct for a depth-3 chain
and silently wrong for anything deeper -- and the failure mode is a
chain that is ACCEPTED.
- Unenforceable constraints are refused when the extension is PARSED, not
when names are compared. Only dNSName and iPAddress subtrees are
implemented; any other subtree type, and any minimum/maximum, fails the
certificate. A constraint on a name type that no certificate in the
chain happens to use would otherwise never be examined, and the chain
would be accepted without the constraint having been enforced.
- dNSName constraints also apply to the commonName of an end entity that
carries no subjectAltName. Read strictly, RFC 5280 constrains the
subject DN through directoryName subtrees, which are refused above --
but x509_check_name() accepts a commonName as a host name, so without
this a compromised constrained CA could issue "CN=somewhere.else" with
no SAN and be trusted for that name. Narrow on purpose: only when there
is no SAN at all, and never for CA certificates, whose commonName is a
label rather than a host name.
Testing: 300 assertions in the x509 self-test, up from 173, covering label
boundary near-misses (notexample.com against example.com), IPv4/IPv6
subtree mismatch, excluded overriding permitted, an unconstrained name
type, the commonName fallback, and a leaf two levels below the
constrained CA -- the case an immediate-issuer-only implementation would
wrongly accept. Every fixture is cross-checked with "openssl verify", and
each gate was mutation-tested: six deliberate breakages, all caught.
patches/ is new. This repository overlays configuration onto a pristine
upstream checkout rather than forking it, and that is still the intent --
patches are applied after the existing reset --hard, so every build starts
from pristine upstream and re-applies the full set, and a patch that stops
applying fails the build rather than silently producing a binary without
it. Because IPXEVER is a fixed tag they cannot rot between builds.
The patch is written to upstream's standards so it can be offered to
ipxe/ipxe. Doing so is a separate decision.
mastacontrola
added a commit
to FOGProject/fogproject
that referenced
this pull request
Aug 18, 2026
Records why the fix for the HTTPS netboot failure went into iPXE rather than into FOG's certificate issuance. FOG's Web CA carries a critical nameConstraints extension. iPXE knows five extensions and refuses any critical one it does not recognise, so it cannot parse the CA FOG bakes into it, and HTTPS netboot fails with "Operation not supported (https://ipxe.org/3c16e283)". The two features -- constrained issuance and CA-embedded iPXE -- are wired to enable each other and were mutually incompatible. The decision is to teach iPXE the extension rather than stop marking it critical. Criticality exists so that a verifier which cannot enforce a constraint refuses the certificate instead of ignoring it; removing it would convert every such verifier from fail-closed to fail-open to accommodate one that happens to be ours. Also records the two implementation choices that are easy to get wrong in a way that fails open -- enforcing per path rather than per issuer, and refusing unenforceable constraints when the extension is parsed rather than when names are compared -- and the deliberate deviation from a strict RFC 5280 reading for the commonName of an end entity with no subjectAltName, which x509_check_name() would otherwise accept as a host name outside the permitted set. Implementation is FOGProject/fog-ipxe#6. The FOG_IPXE_VERSION pin bump follows once that is released.
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
FOG issues its web certificate through a
FOG Web CAintermediate carrying a criticalnameConstraintsextension. iPXE'sx509_extensions[]table knows five extensions —basicConstraints,keyUsage,extKeyUsage,authorityInfoAccess,subjectAltName— andx509_parse_extension()refuses any critical extension it doesn't recognise.So iPXE cannot parse FOG's own Web CA:
3c16e283is-ENOTSUP_EXTENSION, "Unsupported extension". It reads like a TLS or DNS fault — the binary boots, iPXE starts, reportsHTTPSamong its features, and only then can't read the certificate it was handed.This affects any install where
_resolveNetbootProto()selects HTTPS, which it does automatically wheneverrebuildIpxeWithMyCAis set. Bothworking-1.6anddev-branchare affected.Why fix iPXE rather than the CA
The obvious one-liner is to stop marking the constraints critical. That flag exists so a verifier which cannot enforce a constraint refuses the certificate rather than ignoring it. Dropping it converts every such verifier from fail-closed to fail-open in order to accommodate one that happens to be ours. Full reasoning in fogproject ADR 0016.
Design
Enforced in
x509_validate_chain(), notx509_validate(). A constraining CA binds every certificate below it, not only the one it signed.path_remainingcan live inx509_validate()because an inherited integer collapses to one value (x509_set_valid()); a set of permitted subtrees does not. Checking only the immediate issuer is correct for a depth-3 chain and silently wrong for anything deeper — and the failure mode is a chain that gets accepted.Unenforceable constraints are refused at parse time. Only
dNSNameandiPAddresssubtrees are implemented; any other subtree type, and anyminimum/maximum, fails the certificate. Checking at parse time rather than at comparison time matters: a constraint on a name type that no certificate in the chain happens to use would otherwise never be examined, and the chain would be accepted without the constraint having been enforced — support in appearance only.dNSNameconstraints also cover thecommonNameof an end entity with no SAN. Strictly, RFC 5280 constrains the subject DN throughdirectoryNamesubtrees, which are refused above. Butx509_check_name()accepts acommonNameas a host name, so without this a compromised constrained CA could issueCN=somewhere.elsewith no SAN and be trusted for it. Deliberately narrow: only when there is no SAN at all, and never for CA certificates.Testing
x509 self-test goes from 173 to 300 assertions. Coverage includes the label-boundary near-miss (
notexample.comvs constraintexample.com), IPv4/IPv6 subtree mismatch, excluded overriding permitted, an unconstrained name type, thecommonNamefallback, and a leaf two levels below the constrained CA — the case an immediate-issuer-only implementation would wrongly accept.openssl verify, so the expectations aren't just this implementation agreeing with itself.contrib/name-constraints/holds the generator rather than leaving the DER as magic blobs.patches/is newThis repository overlays configuration onto a pristine upstream checkout rather than forking it, and that stays the intent. Patches apply after the existing
git reset --hard, so every build starts from pristine upstream and re-applies the full set; a patch that stops applying fails the build rather than silently producing a binary without it.IPXEVERis a fixed tag, so they can't rot between builds.The patch is written to upstream's standards so it can be offered to
ipxe/ipxe. That's a separate decision and carrying it here doesn't depend on it.🤖 Generated with Claude Code