~~NOTOC~~
====== Szenario 5.5: Name Constraints ======
**Kategorie:** [[.:start|Validierung & Vertrauen]] \\
**Komplexität:** ⭐⭐⭐⭐ (Hoch) \\
**Voraussetzungen:** CA-Zertifikat mit Name Constraints Extension \\
**Geschätzte Zeit:** 15-20 Minuten
----
===== Beschreibung =====
Dieses Szenario beschreibt die **Validierung von Name Constraints** (RFC 5280 §4.2.1.10). Name Constraints schränken ein, welche Namen eine CA in Zertifikaten verwenden darf:
* **permittedSubtrees** - Erlaubte Namensräume (Whitelist)
* **excludedSubtrees** - Verbotene Namensräume (Blacklist)
**Anwendungsfälle:**
* Intermediate-CAs auf bestimmte Domains beschränken
* Interne CAs von öffentlichen Domains isolieren
* Mandantentrennung in Multi-Tenant-Umgebungen
----
===== Workflow =====
flowchart TD
CERT[End-Entity Zertifikat] --> EXTRACT[Subject + SANs extrahieren]
EXTRACT --> CHECK_CHAIN[Für jede CA in Chain]
CHECK_CHAIN --> HAS_NC{Name Constraints?}
HAS_NC -->|Nein| NEXT[Nächste CA]
HAS_NC -->|Ja| PERMITTED{In permitted?}
PERMITTED -->|Nein| FAIL[Constraint verletzt]
PERMITTED -->|Ja| EXCLUDED{In excluded?}
EXCLUDED -->|Ja| FAIL
EXCLUDED -->|Nein| NEXT
NEXT --> CHECK_CHAIN
NEXT --> OK[Alle Constraints erfüllt]
style OK fill:#e8f5e9
style FAIL fill:#ffebee
----
===== Code-Beispiel: Name Constraints setzen =====
using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ;
using var ctx = PqCryptoContext.Initialize();
// Root-CA laden
var rootCert = ctx.LoadCertificate("root-ca.crt.pem");
var rootKey = ctx.LoadPrivateKey("root-ca.key.pem", "RootPassword!");
// Intermediate-CA-Schlüssel generieren
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);
// Intermediate-CA mit Name Constraints erstellen
var intermediateCert = ctx.IssueCertificate(
csr,
issuerCert: rootCert,
issuerKey: rootKey,
validDays: 1825, // 5 Jahre
extensions: new ExtBuilder()
.BasicConstraints(ca: true, pathLengthConstraint: 0, critical: true)
.KeyUsage(KeyUsageFlags.KeyCertSign | KeyUsageFlags.CrlSign, critical: true)
// Name Constraints Extension
.NameConstraints(
permitted: new NameConstraint[]
{
// Nur *.example.com erlaubt
new DnsNameConstraint("example.com"),
// E-Mail nur @example.com
new EmailNameConstraint("example.com"),
// IP-Bereich 192.168.0.0/16
new IpNameConstraint("192.168.0.0", "255.255.0.0")
},
excluded: new NameConstraint[]
{
// Keine *.dev.example.com (Entwickler-Subdomains)
new DnsNameConstraint("dev.example.com")
},
critical: true // MUSS critical sein
)
.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 mit Name Constraints erstellt:");
Console.WriteLine(" Erlaubt: *.example.com, *@example.com, 192.168.0.0/16");
Console.WriteLine(" Verboten: *.dev.example.com");
----
===== Code-Beispiel: Name Constraints validieren =====
public class NameConstraintValidator
{
public ValidationResult ValidateNameConstraints(
X509Certificate2 endEntity,
X509Certificate2Collection chain)
{
var result = new ValidationResult { IsValid = true };
// Alle Namen aus End-Entity extrahieren
var names = ExtractAllNames(endEntity);
// Jede CA in der Chain prüfen
foreach (var ca in chain)
{
var constraints = ExtractNameConstraints(ca);
if (constraints == null) continue;
foreach (var name in names)
{
// Permitted Subtrees prüfen
if (constraints.Permitted.Any())
{
bool isPermitted = constraints.Permitted.Any(p => MatchesConstraint(name, p));
if (!isPermitted)
{
result.IsValid = false;
result.Errors.Add($"Name '{name.Value}' nicht in permitted subtrees von {ca.Subject}");
}
}
// Excluded Subtrees prüfen
bool isExcluded = constraints.Excluded.Any(e => MatchesConstraint(name, e));
if (isExcluded)
{
result.IsValid = false;
result.Errors.Add($"Name '{name.Value}' in excluded subtrees von {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" matched "www.example.com" und "example.com"
if (constraint.StartsWith("."))
{
return dnsName.EndsWith(constraint, StringComparison.OrdinalIgnoreCase) ||
dnsName.Equals(constraint.TrimStart('.'), StringComparison.OrdinalIgnoreCase);
}
// Constraint "example.com" matched auch alle Subdomains
return dnsName.Equals(constraint, StringComparison.OrdinalIgnoreCase) ||
dnsName.EndsWith("." + constraint, StringComparison.OrdinalIgnoreCase);
}
private bool MatchesEmailConstraint(string email, string constraint)
{
// Constraint "@example.com" matched alle E-Mails dieser Domain
if (constraint.StartsWith("@"))
{
return email.EndsWith(constraint, StringComparison.OrdinalIgnoreCase);
}
// Constraint "example.com" matched auch alle 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 Typen =====
^ Typ ^ ASN.1 Tag ^ Beispiel ^ Beschreibung ^
| **dNSName** | 2 | example.com | DNS-Domäne |
| **rfc822Name** | 1 | @example.com | E-Mail-Domäne |
| **iPAddress** | 7 | 192.168.0.0/255.255.0.0 | IP-Bereich |
| **directoryName** | 4 | O=Example | DN-Präfix |
| **uniformResourceIdentifier** | 6 | https://example.com | URI-Präfix |
----
===== Typische Constraint-Szenarien =====
^ Szenario ^ Permitted ^ Excluded ^
| **Interne CA** | .internal.local | (leer) |
| **Abteilungs-CA** | .dept.example.com | .admin.dept.example.com |
| **Partner-CA** | .partner.com | .example.com |
| **IoT-CA** | 10.0.0.0/8 | (leer) |
----
===== X509Chain Name Constraint Prüfung =====
// .NET Framework führt Name Constraint Prüfung automatisch durch
var chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(intermediateCert);
bool isValid = chain.Build(endEntityCert);
// Name Constraint Fehler erkennen
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 Verletzung:");
foreach (var error in ncErrors)
{
Console.WriteLine($" {error.StatusInformation}");
}
}
----
===== Verwandte Szenarien =====
^ Beziehung ^ Szenario ^ Beschreibung ^
| **Voraussetzung** | [[.:policy_validation|5.4 Policy Validation]] | Policies prüfen |
| **Verwandt** | [[de:int:pqcrypt:szenarien:pki:intermediate_ca_erstellen|1.2 Intermediate-CA]] | CA mit Constraints |
| **Verwandt** | [[de:int:pqcrypt:szenarien:pki:ca_hierarchie_aufbauen|1.3 CA-Hierarchie]] | Constraint-Design |
----
<< [[.:policy_validation|← 5.4 Policy Validation]] | [[.:start|↑ Validierung-Übersicht]] | [[de:int:pqcrypt:szenarien:start|→ Alle Szenarien]] >>
{{tag>szenario validierung name-constraints x509 rfc5280}}
----
//Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//