Scenario 5.5: Name Constraints
Categoria: Validazione e fiducia
Complessità: Alta
Prerequisiti: Certificato CA con estensione Name Constraints
Tempo stimato: 15-20 minuti
Descrizione
Questo scenario descrive la validazione dei Name Constraints (RFC 5280 paragrafo 4.2.1.10). I Name Constraints limitano quali nomi una CA può utilizzare nei certificati:
- permittedSubtrees - Namespace consentiti (whitelist)
- excludedSubtrees - Namespace vietati (blacklist)
Casi d'uso:
- Limitare Intermediate-CA a determinati domini
- Isolare CA interne dai domini pubblici
- Separazione tenant in ambienti multi-tenant
Workflow
flowchart TD
CERT[Certificato End-Entity] --> EXTRACT[Estrarre Subject + SAN]
EXTRACT --> CHECK_CHAIN[Per ogni CA nella chain]
CHECK_CHAIN --> HAS_NC{Name Constraints?}
HAS_NC -->|No| NEXT[CA successiva]
HAS_NC -->|Si| PERMITTED{In permitted?}
PERMITTED -->|No| FAIL[Constraint violato]
PERMITTED -->|Si| EXCLUDED{In excluded?}
EXCLUDED -->|Si| FAIL
EXCLUDED -->|No| NEXT
NEXT --> CHECK_CHAIN
NEXT --> OK[Tutti i constraints soddisfatti]
style OK fill:#e8f5e9
style FAIL fill:#ffebee
Esempio codice: Impostare Name Constraints
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ; using var ctx = PqCryptoContext.Initialize(); // Caricare Root-CA var rootCert = ctx.LoadCertificate("root-ca.crt.pem"); var rootKey = ctx.LoadPrivateKey("root-ca.key.pem", "RootPassword!"); // Generare chiave Intermediate-CA using var intKey = ctx.GenerateKeyPair(PqAlgorithm.MlDsa65); var dn = new DnBuilder() .AddCN("Internal CA - Solo example.com") .AddO("Example GmbH") .AddC("DE") .Build(); var csr = ctx.CreateCertificateRequest(intKey, dn); // Creare Intermediate-CA con Name Constraints var intermediateCert = ctx.IssueCertificate( csr, issuerCert: rootCert, issuerKey: rootKey, validDays: 1825, // 5 anni extensions: new ExtBuilder() .BasicConstraints(ca: true, pathLengthConstraint: 0, critical: true) .KeyUsage(KeyUsageFlags.KeyCertSign | KeyUsageFlags.CrlSign, critical: true) // Estensione Name Constraints .NameConstraints( permitted: new NameConstraint[] { // Solo *.example.com consentito new DnsNameConstraint("example.com"), // E-mail solo @example.com new EmailNameConstraint("example.com"), // Range IP 192.168.0.0/16 new IpNameConstraint("192.168.0.0", "255.255.0.0") }, excluded: new NameConstraint[] { // Nessun *.dev.example.com (sottodomini sviluppatori) new DnsNameConstraint("dev.example.com") }, critical: true // DEVE essere 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 con Name Constraints creata:"); Console.WriteLine(" Consentiti: *.example.com, *@example.com, 192.168.0.0/16"); Console.WriteLine(" Vietati: *.dev.example.com");
Esempio codice: Validare Name Constraints
public class NameConstraintValidator { public ValidationResult ValidateNameConstraints( X509Certificate2 endEntity, X509Certificate2Collection chain) { var result = new ValidationResult { IsValid = true }; // Estrarre tutti i nomi dall'End-Entity var names = ExtractAllNames(endEntity); // Verificare ogni CA nella chain foreach (var ca in chain) { var constraints = ExtractNameConstraints(ca); if (constraints == null) continue; foreach (var name in names) { // Verificare Permitted Subtrees if (constraints.Permitted.Any()) { bool isPermitted = constraints.Permitted.Any(p => MatchesConstraint(name, p)); if (!isPermitted) { result.IsValid = false; result.Errors.Add($"Nome '{name.Value}' non in permitted subtrees di {ca.Subject}"); } } // Verificare Excluded Subtrees bool isExcluded = constraints.Excluded.Any(e => MatchesConstraint(name, e)); if (isExcluded) { result.IsValid = false; result.Errors.Add($"Nome '{name.Value}' in excluded subtrees di {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 E-Mail 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" corrisponde a "www.example.com" e "example.com" if (constraint.StartsWith(".")) { return dnsName.EndsWith(constraint, StringComparison.OrdinalIgnoreCase) || dnsName.Equals(constraint.TrimStart('.'), StringComparison.OrdinalIgnoreCase); } // Constraint "example.com" corrisponde anche a tutti i sottodomini return dnsName.Equals(constraint, StringComparison.OrdinalIgnoreCase) || dnsName.EndsWith("." + constraint, StringComparison.OrdinalIgnoreCase); } private bool MatchesEmailConstraint(string email, string constraint) { // Constraint "@example.com" corrisponde a tutte le e-mail di quel dominio if (constraint.StartsWith("@")) { return email.EndsWith(constraint, StringComparison.OrdinalIgnoreCase); } // Constraint "example.com" corrisponde anche a tutti i sottodomini 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; } }
Tipi di Name Constraint
| Tipo | Tag ASN.1 | Esempio | Descrizione |
|---|---|---|---|
| dNSName | 2 | example.com | Dominio DNS |
| rfc822Name | 1 | @example.com | Dominio e-mail |
| iPAddress | 7 | 192.168.0.0/255.255.0.0 | Range IP |
| directoryName | 4 | O=Example | Prefisso DN |
| uniformResourceIdentifier | 6 | https://example.com | Prefisso URI |
Scenari tipici di constraint
| Scenario | Permitted | Excluded |
|---|---|---|
| CA interna | .internal.local | (vuoto) |
| CA di reparto | .dept.example.com | .admin.dept.example.com |
| CA partner | .partner.com | .example.com |
| CA IoT | 10.0.0.0/8 | (vuoto) |
Verifica Name Constraint con X509Chain
// .NET Framework esegue automaticamente la verifica dei Name Constraints var chain = new X509Chain(); chain.ChainPolicy.ExtraStore.Add(intermediateCert); bool isValid = chain.Build(endEntityCert); // Riconoscere errori Name Constraint 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("Violazione Name Constraint:"); foreach (var error in ncErrors) { Console.WriteLine($" {error.StatusInformation}"); } }
Scenari correlati
| Relazione | Scenario | Descrizione |
|---|---|---|
| Prerequisito | 5.4 Validazione Policy | Verificare policies |
| Correlato | 1.2 Intermediate-CA | CA con constraints |
| Correlato | 1.3 Gerarchia CA | Design constraints |
« ← 5.4 Validazione Policy | ↑ Panoramica validazione | → Tutti gli scenari »
Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional
Zuletzt geändert: il 30/01/2026 alle 07:05