/// <summary> /// Test if the host fits to the binding /// 0: no match /// 100: default match /// 500: partial match (todo: make different levels for # of subdomains /// 1000: full match /// </summary> /// <param name=""></param> /// <param name=""></param> /// <returns></returns> private int Fits(string binding, string host, SSLFlags flags) { // The default (emtpy) binding matches with all hostnames. // But it's not supported with Central SSL if (string.IsNullOrEmpty(binding) && (!flags.HasFlag(SSLFlags.CentralSSL))) { return(10); } // Match sub.example.com with *.example.com if (binding.StartsWith("*.")) { if (host.ToLower().EndsWith(binding.ToLower().Replace("*.", "."))) { // If there is a binding for *.a.b.c.com (5) and one for *.c.com (3) // then the hostname test.a.b.c.com (5) is a better (more specific) // for the former than for the latter, so we prefer to use that. var hostLevel = host.Split('.').Count(); var bindingLevel = binding.Split('.').Count(); return(90 - (hostLevel - bindingLevel)); } else { return(0); } } // Full match return(string.Equals(binding, host, StringComparison.CurrentCultureIgnoreCase) ? 100 : 0); }
/// <summary> /// Make sure the flags are set correctly for updating the binding, /// because special conditions apply to the default binding /// </summary> /// <param name="host"></param> /// <param name="flags"></param> /// <returns></returns> private SSLFlags CheckFlags(bool newBinding, string host, SSLFlags flags) { // SSL flags are not supported at all by Windows 2008 if (_client.Version.Major < 8) { return(SSLFlags.None); } // Do not allow CentralSSL flag to be set on the default binding if (string.IsNullOrEmpty(host)) { if (flags.HasFlag(SSLFlags.CentralSSL)) { throw new InvalidOperationException("Central SSL is not supported without a hostname"); } } // Add SNI on Windows Server 2012+ if (newBinding) { if (!string.IsNullOrEmpty(host) && _client.Version.Major >= 8) { flags = flags | SSLFlags.SNI; } } return(flags); }
/// <summary> /// Make sure the flags are set correctly for updating the binding, /// because special conditions apply to the default binding /// </summary> /// <param name="host"></param> /// <param name="flags"></param> /// <returns></returns> private SSLFlags CheckFlags(string host, SSLFlags flags) { // Remove SNI flag from empty binding if (string.IsNullOrEmpty(host)) { if (flags.HasFlag(SSLFlags.CentralSSL)) { throw new InvalidOperationException("Central SSL is not supported without a hostname"); } } return(flags); }
/// <summary> /// Test if the host fits to the binding /// 100: full match /// 90: partial match (Certificate less specific, e.g. *.example.com cert for sub.example.com binding) /// 50,59,48,...: partial match (IIS less specific, e.g. sub.example.com cert for *.example.com binding) /// 10: default match (catch-all binding) /// 0: no match /// </summary> /// <param name=""></param> /// <param name=""></param> /// <returns></returns> private int Fits(IIISBinding iis, Identifier certificate, SSLFlags flags) { // The default (empty) binding matches with all hostnames. // But it's not supported with Central SSL if (string.IsNullOrEmpty(iis.Host) && (!flags.HasFlag(SSLFlags.CentralSsl))) { return(10); } // Match sub.example.com (certificate) with *.example.com (IIS) if (iis.Host.StartsWith("*.") && !certificate.Value.StartsWith("*.")) { if (certificate.Value.ToLower().EndsWith(iis.Host.ToLower().Replace("*.", "."))) { // If there is a binding for *.a.b.c.com (5) and one for *.c.com (3) // then the hostname test.a.b.c.com (5) is a better (more specific) // for the former than for the latter, so we prefer to use that. var hostLevel = certificate.Value.Split('.').Length; var bindingLevel = iis.Host.Split('.').Length; return(50 - (hostLevel - bindingLevel)); } return(0); } // Match *.example.com (certificate) with sub.example.com (IIS) if (!iis.Host.StartsWith("*.") && certificate.Value.StartsWith("*.")) { if (iis.Host.ToLower().EndsWith(certificate.Value.ToLower().Replace("*.", "."))) { // But it should not match with another.sub.example.com. var hostLevel = certificate.Value.Split('.').Length; var bindingLevel = iis.Host.Split('.').Length; if (hostLevel == bindingLevel) { return(90); } } else if (iis.Host.ToLower().Equals(certificate.Value.ToLower().Replace("*.", ""))) { if (iis.Host.Split('.').Length == 2) { return(89); } } return(0); } // Full match return(string.Equals(iis.Host, certificate.Value, StringComparison.CurrentCultureIgnoreCase) ? 100 : 0); }
/// <summary> /// Make sure the flags are set correctly for updating the binding, /// because special conditions apply to the default binding /// </summary> /// <param name="host"></param> /// <param name="flags"></param> /// <returns></returns> private SSLFlags CheckFlags(bool newBinding, string host, SSLFlags flags) { // SSL flags are not supported at all by Windows 2008 if (_client.Version.Major < 8) { return(SSLFlags.None); } // Add SNI on Windows Server 2012+ for new bindings if (newBinding && !string.IsNullOrEmpty(host) && _client.Version.Major >= 8) { flags |= SSLFlags.SNI; } // Modern flags are not supported by IIS versions lower than 10. // In fact they are not even supported by all versions of IIS 10, // but so far we don't know how to check for these features // availability (IIS reports its version as 10.0.0 even on // Server 2019). if (_client.Version.Major < 10) { flags &= ~SSLFlags.IIS10_Flags; } // Some flags cannot be used together with the CentralSsl flag, // because when using CentralSsl they are supposedly configured at // the server level instead of at the binding level (though the IIS // Manager doesn't seem to expose these options). if (flags.HasFlag(SSLFlags.CentralSsl)) { // Do not allow CentralSSL flag to be set on the default binding // Logic elsewhere in the program should prevent this // from happening. This is merely a sanity check if (string.IsNullOrEmpty(host)) { throw new InvalidOperationException("Central SSL is not supported without a hostname"); } flags &= ~SSLFlags.NotWithCentralSsl; } // All checks passed, return flags return(flags); }
public void UpdateWildcardFuzzy(string storeName, string bindingIp, int bindingPort, SSLFlags inputFlags, SSLFlags expectedFlags) { var originalBindings = new List <MockBinding> { new MockBinding() { IP = DefaultIP, Port = DefaultPort, Host = "site1.example.com", Protocol = "https", CertificateHash = scopeCert } }; var site = new MockSite() { Id = httpOnlyId, Bindings = originalBindings.ToList() }; var iis = new MockIISClient(log) { MockSites = new[] { site } }; var bindingOptions = new BindingOptions(). WithSiteId(httpOnlyId). WithIP(bindingIp). WithPort(bindingPort). WithStore(storeName). WithFlags(inputFlags). WithThumbprint(newCert); iis.AddOrUpdateBindings(new[] { "*.example.com" }, bindingOptions, oldCert1); var expectedBindings = inputFlags.HasFlag(SSLFlags.CentralSSL) ? 2 : 1; Assert.AreEqual(expectedBindings, site.Bindings.Count); foreach (var newBinding in site.Bindings.Except(originalBindings)) { Assert.AreEqual("https", newBinding.Protocol); Assert.AreEqual(storeName, newBinding.CertificateStoreName); Assert.AreEqual(newCert, newBinding.CertificateHash); Assert.AreEqual(DefaultPort, newBinding.Port); Assert.AreEqual(DefaultIP, newBinding.IP); Assert.AreEqual(expectedFlags, newBinding.SSLFlags); } }