~~NOTOC~~
====== Scenario 5.5: Name Constraints ======
**Category:** [[.:start|Validation & Trust]] \\
**Complexity:** **** (High) \\
**Prerequisites:** CA certificate with Name Constraints extension \\
**Estimated Time:** 15-20 minutes
----
===== Description =====
This scenario describes **validation of Name Constraints** (RFC 5280 Section 4.2.1.10). Name Constraints restrict which names a CA may use in certificates:
* **permittedSubtrees** - Allowed namespaces (whitelist)
* **excludedSubtrees** - Forbidden namespaces (blacklist)
**Use cases:**
* Restrict intermediate CAs to specific domains
* Isolate internal CAs from public domains
* Tenant separation in multi-tenant environments
----
===== Workflow =====
flowchart TD
CERT[End-Entity Certificate] --> EXTRACT[Extract Subject + SANs]
EXTRACT --> CHECK_CHAIN[For each CA in chain]
CHECK_CHAIN --> HAS_NC{Name Constraints?}
HAS_NC -->|No| NEXT[Next CA]
HAS_NC -->|Yes| PERMITTED{In permitted?}
PERMITTED -->|No| FAIL[Constraint violated]
PERMITTED -->|Yes| EXCLUDED{In excluded?}
EXCLUDED -->|Yes| FAIL
EXCLUDED -->|No| NEXT
NEXT --> CHECK_CHAIN
NEXT --> OK[All constraints satisfied]
style OK fill:#e8f5e9
style FAIL fill:#ffebee
----
===== Code Example: Setting Name Constraints =====
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using var ctx = PqCryptoContext.Initialize();
// Load Root CA
var rootCert = ctx.LoadCertificate("root-ca.crt.pem");
var rootKey = ctx.LoadPrivateKey("root-ca.key.pem", "RootPassword!");
// Generate Intermediate CA key
using var intKey = ctx.GenerateKeyPair(PqAlgorithm.MlDsa65);
var dn = new DnBuilder()
.AddCN("Internal CA - example.com Only")
.AddO("Example GmbH")
.AddC("DE")
.Build();
var csr = ctx.CreateCertificateRequest(intKey, dn);
// Create Intermediate CA with Name Constraints
var intermediateCert = ctx.IssueCertificate(
csr,
issuerCert: rootCert,
issuerKey: rootKey,
validDays: 1825, // 5 years
extensions: new ExtBuilder()
.BasicConstraints(ca: true, pathLengthConstraint: 0, critical: true)
.KeyUsage(KeyUsageFlags.KeyCertSign | KeyUsageFlags.CrlSign, critical: true)
// Name Constraints Extension
.NameConstraints(
permitted: new NameConstraint[]
{
// Only *.example.com allowed
new DnsNameConstraint("example.com"),
// Email only @example.com
new EmailNameConstraint("example.com"),
// IP range 192.168.0.0/16
new IpNameConstraint("192.168.0.0", "255.255.0.0")
},
excluded: new NameConstraint[]
{
// No *.dev.example.com (developer subdomains)
new DnsNameConstraint("dev.example.com")
},
critical: true // MUST be critical
)
.SubjectKeyIdentifier(intKey.PublicKey)
.AuthorityKeyIdentifier(rootCert)
.Build()
);
intermediateCert.ToPemFile("internal-intermediate-ca.crt.pem");
intKey.ToEncryptedPemFile("internal-intermediate-ca.key.pem", "IntPassword!");
Console.WriteLine("Intermediate CA with Name Constraints created:");
Console.WriteLine(" Permitted: *.example.com, *@example.com, 192.168.0.0/16");
Console.WriteLine(" Excluded: *.dev.example.com");
----
===== Code Example: Validating Name Constraints =====
public class NameConstraintValidator
{
public ValidationResult ValidateNameConstraints(
X509Certificate2 endEntity,
X509Certificate2Collection chain)
{
var result = new ValidationResult { IsValid = true };
// Extract all names from end-entity
var names = ExtractAllNames(endEntity);
// Check each CA in the chain
foreach (var ca in chain)
{
var constraints = ExtractNameConstraints(ca);
if (constraints == null) continue;
foreach (var name in names)
{
// Check permitted subtrees
if (constraints.Permitted.Any())
{
bool isPermitted = constraints.Permitted.Any(p => MatchesConstraint(name, p));
if (!isPermitted)
{
result.IsValid = false;
result.Errors.Add($"Name '{name.Value}' not in permitted subtrees of {ca.Subject}");
}
}
// Check excluded subtrees
bool isExcluded = constraints.Excluded.Any(e => MatchesConstraint(name, e));
if (isExcluded)
{
result.IsValid = false;
result.Errors.Add($"Name '{name.Value}' in excluded subtrees of {ca.Subject}");
}
}
}
return result;
}
private List ExtractAllNames(X509Certificate2 cert)
{
var names = new List();
// Subject CN
var cn = cert.GetNameInfo(X509NameType.SimpleName, false);
if (!string.IsNullOrEmpty(cn))
{
names.Add(new GeneralName { Type = GeneralNameType.DnsName, Value = cn });
}
// Subject Email
var email = cert.GetNameInfo(X509NameType.EmailName, false);
if (!string.IsNullOrEmpty(email))
{
names.Add(new GeneralName { Type = GeneralNameType.Email, Value = email });
}
// Subject Alternative Names
var sanExt = cert.Extensions["2.5.29.17"];
if (sanExt != null)
{
names.AddRange(ParseSan(sanExt.RawData));
}
return names;
}
private bool MatchesConstraint(GeneralName name, NameConstraint constraint)
{
if (name.Type != constraint.Type) return false;
return constraint.Type switch
{
GeneralNameType.DnsName => MatchesDnsConstraint(name.Value, constraint.Value),
GeneralNameType.Email => MatchesEmailConstraint(name.Value, constraint.Value),
GeneralNameType.IpAddress => MatchesIpConstraint(name.Value, constraint.Value, constraint.Mask),
GeneralNameType.DirectoryName => MatchesDnConstraint(name.Value, constraint.Value),
_ => false
};
}
private bool MatchesDnsConstraint(string dnsName, string constraint)
{
// Constraint ".example.com" matches "www.example.com" and "example.com"
if (constraint.StartsWith("."))
{
return dnsName.EndsWith(constraint, StringComparison.OrdinalIgnoreCase) ||
dnsName.Equals(constraint.TrimStart('.'), StringComparison.OrdinalIgnoreCase);
}
// Constraint "example.com" also matches all subdomains
return dnsName.Equals(constraint, StringComparison.OrdinalIgnoreCase) ||
dnsName.EndsWith("." + constraint, StringComparison.OrdinalIgnoreCase);
}
private bool MatchesEmailConstraint(string email, string constraint)
{
// Constraint "@example.com" matches all emails of this domain
if (constraint.StartsWith("@"))
{
return email.EndsWith(constraint, StringComparison.OrdinalIgnoreCase);
}
// Constraint "example.com" also matches all subdomains
var domain = email.Split('@').LastOrDefault();
return MatchesDnsConstraint(domain ?? "", constraint);
}
private bool MatchesIpConstraint(string ip, string network, string mask)
{
var ipBytes = IPAddress.Parse(ip).GetAddressBytes();
var networkBytes = IPAddress.Parse(network).GetAddressBytes();
var maskBytes = IPAddress.Parse(mask).GetAddressBytes();
for (int i = 0; i < ipBytes.Length; i++)
{
if ((ipBytes[i] & maskBytes[i]) != (networkBytes[i] & maskBytes[i]))
{
return false;
}
}
return true;
}
}
----
===== Name Constraint Types =====
^ Type ^ ASN.1 Tag ^ Example ^ Description ^
| **dNSName** | 2 | example.com | DNS domain |
| **rfc822Name** | 1 | @example.com | Email domain |
| **iPAddress** | 7 | 192.168.0.0/255.255.0.0 | IP range |
| **directoryName** | 4 | O=Example | DN prefix |
| **uniformResourceIdentifier** | 6 | https://example.com | URI prefix |
----
===== Typical Constraint Scenarios =====
^ Scenario ^ Permitted ^ Excluded ^
| **Internal CA** | .internal.local | (empty) |
| **Department CA** | .dept.example.com | .admin.dept.example.com |
| **Partner CA** | .partner.com | .example.com |
| **IoT CA** | 10.0.0.0/8 | (empty) |
----
===== X509Chain Name Constraint Check =====
// .NET Framework performs name constraint checking automatically
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(intermediateCert);
bool isValid = chain.Build(endEntityCert);
// Detect name constraint errors
var ncErrors = chain.ChainElements
.SelectMany(e => e.ChainElementStatus)
.Where(s => s.Status == X509ChainStatusFlags.InvalidNameConstraints ||
s.Status == X509ChainStatusFlags.HasNotSupportedNameConstraint ||
s.Status == X509ChainStatusFlags.HasNotPermittedNameConstraint ||
s.Status == X509ChainStatusFlags.HasExcludedNameConstraint)
.ToList();
if (ncErrors.Any())
{
Console.WriteLine("Name Constraint violation:");
foreach (var error in ncErrors)
{
Console.WriteLine($" {error.StatusInformation}");
}
}
----
===== Related Scenarios =====
^ Relationship ^ Scenario ^ Description ^
| **Prerequisite** | [[.:policy_validation|5.4 Policy Validation]] | Check policies |
| **Related** | [[en:int:pqcrypt:szenarien:pki:intermediate_ca_erstellen|1.2 Intermediate CA]] | CA with constraints |
| **Related** | [[en:int:pqcrypt:szenarien:pki:ca_hierarchie_aufbauen|1.3 CA Hierarchy]] | Constraint design |
----
<< [[.:policy_validation|<- 5.4 Policy Validation]] | [[.:start|^ Validation Overview]] | [[en:int:pqcrypt:szenarien:start|-> All Scenarios]] >>
{{tag>scenario validation name-constraints x509 rfc5280}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//