Scenario 5.5: Name Constraints

Category: 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:

Use cases:


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<GeneralName> ExtractAllNames(X509Certificate2 cert)
    {
        var names = new List<GeneralName>();
 
        // 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}");
    }
}

Relationship Scenario Description
Prerequisite 5.4 Policy Validation Check policies
Related 1.2 Intermediate CA CA with constraints
Related 1.3 CA Hierarchy Constraint design

« <- 5.4 Policy Validation | ^ Validation Overview | -> All Scenarios »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional