~~NOTOC~~ ====== Scenarij 5.5: Omejitve imen ====== **Kategorija:** [[.:start|Validacija in zaupanje]] \\ **Kompleksnost:** ⭐⭐⭐⭐ (Visoka) \\ **Predpogoji:** CA certifikat z razširitvijo Name Constraints \\ **Predviden čas:** 15-20 minut ---- ===== Opis ===== Ta scenarij opisuje **validacijo omejitev imen** (RFC 5280 §4.2.1.10). Omejitve imen določajo, katera imena lahko CA uporablja v certifikatih: * **permittedSubtrees** - Dovoljeni imenski prostori (bela lista) * **excludedSubtrees** - Prepovedani imenski prostori (črna lista) **Primeri uporabe:** * Omejitev vmesnih CA-jev na določene domene * Izolacija notranjih CA-jev od javnih domen * Ločitev najemnikov v večnajemniških okoljih ---- ===== Potek dela ===== flowchart TD CERT[Končni certifikat] --> EXTRACT[Ekstrakcija Subject + SANs] EXTRACT --> CHECK_CHAIN[Za vsak CA v verigi] CHECK_CHAIN --> HAS_NC{Omejitve imen?} HAS_NC -->|Ne| NEXT[Naslednji CA] HAS_NC -->|Da| PERMITTED{V permitted?} PERMITTED -->|Ne| FAIL[Omejitev kršena] PERMITTED -->|Da| EXCLUDED{V excluded?} EXCLUDED -->|Da| FAIL EXCLUDED -->|Ne| NEXT NEXT --> CHECK_CHAIN NEXT --> OK[Vse omejitve izpolnjene] style OK fill:#e8f5e9 style FAIL fill:#ffebee ---- ===== Primer kode: Nastavitev omejitev imen ===== using WvdS.Security.Cryptography.X509Certificates.Extensions.PQ; using var ctx = PqCryptoContext.Initialize(); // Nalaganje korenskega CA var rootCert = ctx.LoadCertificate("root-ca.crt.pem"); var rootKey = ctx.LoadPrivateKey("root-ca.key.pem", "RootPassword!"); // Generiranje ključa vmesnega CA 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); // Ustvarjanje vmesnega CA z omejitvami imen var intermediateCert = ctx.IssueCertificate( csr, issuerCert: rootCert, issuerKey: rootKey, validDays: 1825, // 5 let extensions: new ExtBuilder() .BasicConstraints(ca: true, pathLengthConstraint: 0, critical: true) .KeyUsage(KeyUsageFlags.KeyCertSign | KeyUsageFlags.CrlSign, critical: true) // Razširitev Name Constraints .NameConstraints( permitted: new NameConstraint[] { // Dovoljeno samo *.example.com new DnsNameConstraint("example.com"), // E-pošta samo @example.com new EmailNameConstraint("example.com"), // IP obseg 192.168.0.0/16 new IpNameConstraint("192.168.0.0", "255.255.0.0") }, excluded: new NameConstraint[] { // Brez *.dev.example.com (razvojne poddomene) new DnsNameConstraint("dev.example.com") }, critical: true // MORA biti critical ) .SubjectKeyIdentifier(intKey.PublicKey) .AuthorityKeyIdentifier(rootCert) .Build() ); intermediateCert.ToPemFile("internal-intermediate-ca.crt.pem"); intKey.ToEncryptedPemFile("internal-intermediate-ca.key.pem", "IntPassword!"); Console.WriteLine("Vmesni CA z omejitvami imen ustvarjen:"); Console.WriteLine(" Dovoljeno: *.example.com, *@example.com, 192.168.0.0/16"); Console.WriteLine(" Prepovedano: *.dev.example.com"); ---- ===== Primer kode: Validacija omejitev imen ===== public class NameConstraintValidator { public ValidationResult ValidateNameConstraints( X509Certificate2 endEntity, X509Certificate2Collection chain) { var result = new ValidationResult { IsValid = true }; // Ekstrakcija vseh imen iz končnega certifikata var names = ExtractAllNames(endEntity); // Preverjanje vsakega CA v verigi foreach (var ca in chain) { var constraints = ExtractNameConstraints(ca); if (constraints == null) continue; foreach (var name in names) { // Preverjanje Permitted Subtrees if (constraints.Permitted.Any()) { bool isPermitted = constraints.Permitted.Any(p => MatchesConstraint(name, p)); if (!isPermitted) { result.IsValid = false; result.Errors.Add($"Ime '{name.Value}' ni v permitted subtrees od {ca.Subject}"); } } // Preverjanje Excluded Subtrees bool isExcluded = constraints.Excluded.Any(e => MatchesConstraint(name, e)); if (isExcluded) { result.IsValid = false; result.Errors.Add($"Ime '{name.Value}' je v excluded subtrees od {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) { // Omejitev ".example.com" se ujema z "www.example.com" in "example.com" if (constraint.StartsWith(".")) { return dnsName.EndsWith(constraint, StringComparison.OrdinalIgnoreCase) || dnsName.Equals(constraint.TrimStart('.'), StringComparison.OrdinalIgnoreCase); } // Omejitev "example.com" se ujema tudi z vsemi poddomenami return dnsName.Equals(constraint, StringComparison.OrdinalIgnoreCase) || dnsName.EndsWith("." + constraint, StringComparison.OrdinalIgnoreCase); } private bool MatchesEmailConstraint(string email, string constraint) { // Omejitev "@example.com" se ujema z vsemi e-poštami te domene if (constraint.StartsWith("@")) { return email.EndsWith(constraint, StringComparison.OrdinalIgnoreCase); } // Omejitev "example.com" se ujema tudi z vsemi poddomenami 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 omejitev imen ===== ^ Tip ^ ASN.1 oznaka ^ Primer ^ Opis ^ | **dNSName** | 2 | example.com | DNS domena | | **rfc822Name** | 1 | @example.com | E-poštna domena | | **iPAddress** | 7 | 192.168.0.0/255.255.0.0 | IP obseg | | **directoryName** | 4 | O=Example | DN predpona | | **uniformResourceIdentifier** | 6 | https://example.com | URI predpona | ---- ===== Tipični scenariji omejitev ===== ^ Scenarij ^ Permitted ^ Excluded ^ | **Notranji CA** | .internal.local | (prazno) | | **Oddelek CA** | .dept.example.com | .admin.dept.example.com | | **Partner CA** | .partner.com | .example.com | | **IoT CA** | 10.0.0.0/8 | (prazno) | ---- ===== Preverjanje omejitev imen z X509Chain ===== // .NET Framework izvaja preverjanje omejitev imen samodejno var chain = new X509Chain(); chain.ChainPolicy.ExtraStore.Add(intermediateCert); bool isValid = chain.Build(endEntityCert); // Prepoznavanje napak omejitev imen 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("Kršitev omejitve imen:"); foreach (var error in ncErrors) { Console.WriteLine($" {error.StatusInformation}"); } } ---- ===== Povezani scenariji ===== ^ Povezava ^ Scenarij ^ Opis ^ | **Predpogoj** | [[.:policy_validation|5.4 Validacija politik]] | Preverjanje politik | | **Povezano** | [[sl:int:pqcrypt:szenarien:pki:intermediate_ca_erstellen|1.2 Vmesni CA]] | CA z omejitvami | | **Povezano** | [[sl:int:pqcrypt:szenarien:pki:ca_hierarchie_aufbauen|1.3 CA hierarhija]] | Načrtovanje omejitev | ---- << [[.:policy_validation|← 5.4 Validacija politik]] | [[.:start|↑ Pregled validacije]] | [[sl:int:pqcrypt:szenarien:start|→ Vsi scenariji]] >> {{tag>scenarij validacija omejitve-imen x509 rfc5280}} ---- //Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional//