Name constraints
Limit the names a CA may issue for. A constrained root cannot vouch for any other site, even if its key leaks.
Why constrain a CA
A root CA in your trust store can vouch for any name, including github.com or your bank. If its key leaks, anyone holding it can impersonate those sites on your machine. A name constraint limits the names a CA may issue for. A root restricted to .klimax.internal cannot sign for any other domain, even with its key.
Go, macOS, Chrome and Firefox enforce name constraints on roots and intermediates. Constrain a root before you trust it system-wide.
Syntax
root-ca and intermediate-ca take a repeatable --name-constraint, written in OpenSSL syntax:
[permitted;|excluded;]TYPE:value
$ homepki root-ca -d klimax.internal \
--name-constraint "permitted;DNS:.klimax.internal"
$ homepki intermediate-ca -d klimax.internal -n bu1 \
--name-constraint "permitted;DNS:.bu1.klimax.internal" \
--name-constraint "IP:10.0.0.0/8"
| Part | Values |
|---|---|
| subtree | permitted; or excluded;. permitted; may be left out. |
TYPE | DNS, IP, email or URI, any case |
DNS value | .example.internal matches subdomains only; example.internal matches the domain and its subdomains |
IP value | a range in CIDR (10.0.0.0/8) or address/netmask (10.0.0.0/255.0.0.0) form. A bare address is rejected. |
email value | a mailbox, a host, or .domain for every host under it |
URI value | a host or .domain, matched against the URI's host |
The extension is marked critical, as RFC 5280 requires. Invalid values stop the command before anything is written:
$ homepki root-ca -d x.test --name-constraint "IP:10.0.0.0"
Error: name constraint "IP:10.0.0.0": IP constraint needs a range, e.g. 10.0.0.0/8 or 10.0.0.0/255.0.0.0
Each constraint covers one name type
Constraints apply per name type. A DNS constraint on its own leaves IP, email and URI SANs unrestricted. To restrict IP SANs too, add an IP: constraint; once one is present, every IP SAN must fall in a permitted range.
Enforced when a leaf is issued
server-cert, client-cert and sign verify every new certificate against its chain before writing it. A name outside the constraints is refused, nothing is written, and the command exits 1:
$ homepki server-cert -d klimax.internal -i bu1 -s gw --san evil.example.com
Generating server certificate gw under klimax.internal/bu1
Error: x509: a root or intermediate certificate is not authorized to sign for this name: DNS name "evil.example.com" is not permitted by any constraint
A CA in the chain carries name constraints that exclude one of this certificate's Subject Alternative Names, so no TLS client would accept it. Nothing was written; check the constraints with: openssl x509 -noout -ext nameConstraints -in ~/.homepki/klimax-internal/bu1/bu1-intermediate-ca.crt
Constraints on the root and on the intermediate both apply. A leaf must satisfy every CA in its chain.
Pick a domain inside the constraint
Every generated leaf carries <leaf>.<intermediate>.<domain> as its first SAN, so the constraints must permit that name. The CA commands warn when they do not:
$ homepki intermediate-ca -d klimax.internal -n bu2 --name-constraint "permitted;DNS:.other.internal"
Initializing Intermediate CA bu2 for klimax.internal
Warning: server-cert and client-cert always add <leaf>.bu2.klimax.internal as a Subject Alternative Name, which these constraints do not permit. Only certificates signed with 'homepki sign' can be issued under this CA.
Under such a CA only homepki sign can issue, with requests whose SANs fit the constraint.
Changing constraints
Constraints are part of the CA certificate and are fixed when it is issued. Adding or changing them means re-generating the CA with --force, which orphans every certificate beneath it. See Checking after --force.
Inspecting constraints
$ openssl x509 -noout -ext nameConstraints -in ~/.homepki/klimax-internal/bu1/bu1-intermediate-ca.crt
X509v3 Name Constraints: critical
Permitted:
DNS:.bu1.klimax.internal
IP:10.0.0.0/255.0.0.0
Building constraints in Go
Go code that imports github.com/bcollard/homepki/pkg/pki can build the same constraints without the OpenSSL syntax. Each method returns a copy, so calls chain:
lan, err := pki.ParseIPRange("10.0.0.0/8")
// handle err
nc := pki.NameConstraints{}.
PermitDNS(".klimax.internal").
PermitIP(lan)
See Go package.