~~NOTOC~~
====== Scenario 5.5: Name Constraints ======
**Categoria:** [[.:start|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 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 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** | [[.:policy_validation|5.4 Validazione Policy]] | Verificare policies |
| **Correlato** | [[it:int:pqcrypt:szenarien:pki:intermediate_ca_erstellen|1.2 Intermediate-CA]] | CA con constraints |
| **Correlato** | [[it:int:pqcrypt:szenarien:pki:ca_hierarchie_aufbauen|1.3 Gerarchia CA]] | Design constraints |
----
<< [[.:policy_validation|← 5.4 Validazione Policy]] | [[.:start|↑ Panoramica validazione]] | [[it:int:pqcrypt:szenarien:start|→ Tutti gli scenari]] >>
{{tag>scenario validazione name-constraints x509 rfc5280}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//