public void Install(PrivateKey pk, Crt crt, IEnumerable <Crt> chain, IPkiTool cp)
        {
            var bindings = IisHelper.ResolveSiteBindings(WebSiteRef);
            var existing = IisHelper.ResolveSiteBindings(
                BindingAddress, BindingPort, BindingHost, bindings).ToArray();

            if (existing?.Length > 0 && !Force)
            {
                throw new InvalidOperationException(
                          "found existing conflicting bindings for target site;"
                          + " use Force parameter to overwrite");
            }

            // TODO: should we expose these as optional params to be overridden by user?
            var storeLocation = StoreLocation.LocalMachine;
            var storeName     = StoreName.My;
            var cert          = ImportCertificate(pk, crt, chain, cp,
                                                  storeName, storeLocation, CertificateFriendlyName);

            var certStore = Enum.GetName(typeof(StoreName), storeName);
            var certHash  = cert.GetCertHash();


            if (existing?.Length > 0)
            {
                foreach (var oldBinding in existing)
                {
                    if (BindingHostRequired.HasValue)
                    {
                        oldBinding.BindingHostRequired = BindingHostRequired;
                    }
                    IisHelper.UpdateSiteBinding(oldBinding, certStore, certHash);
                }
            }
            else
            {
                var firstBinding = bindings.First();
                var newBinding   = new IisWebSiteBinding
                {
                    // Copy over some existing site info
                    SiteId   = firstBinding.SiteId,
                    SiteName = firstBinding.SiteName,
                    SiteRoot = firstBinding.SiteRoot,

                    // New binding specifics
                    BindingProtocol     = "https",
                    BindingAddress      = this.BindingAddress,
                    BindingPort         = this.BindingPort.ToString(),
                    BindingHost         = this.BindingHost,
                    BindingHostRequired = this.BindingHostRequired,
                };

                IisHelper.CreateSiteBinding(newBinding, certStore, certHash);
            }
        }
        public static void CreateSiteBinding(IisWebSiteBinding binding, string certStore, byte[] certHash)
        {
            using (var iis = new ServerManager())
            {
                var sites = iis.Sites.Where(_ => _.Id == binding.SiteId).ToArray();
                if (sites?.Length == 0)
                {
                    throw new ArgumentException("no matching sites found")
                          .With(nameof(binding.SiteId), binding.SiteId);
                }

                foreach (var site in sites)
                {
                    // Binding Information spec
                    //      (https://msdn.microsoft.com/en-us/library/bb339271(v=vs.90).aspx):
                    //
                    //    IpAddr:Port:HostHeader
                    //
                    // where IpAddr can be * for all interfaces
                    // where HostHeader is only valid for SNI-capable IIS (8+)

                    var bindingAddr = string.IsNullOrEmpty(binding.BindingAddress)
                            ? "*"
                            : binding.BindingAddress;
                    var bindingPort = string.IsNullOrEmpty(binding.BindingPort)
                            ? "443"
                            : int.Parse(binding.BindingPort).ToString();
                    var bindingHost = string.IsNullOrEmpty(binding.BindingHost)
                            ? ""
                            : binding.BindingHost;

                    var bindingInfo = $"{bindingAddr}:{bindingPort}:{bindingHost}";
                    var b           = site.Bindings.Add(bindingInfo, certHash, certStore);

                    // PATCH - Set SNI flag for new binding
                    if (binding.BindingHostRequired.GetValueOrDefault() && GetIisVersion().Major >= 8)
                    {
                        b.SetAttributeValue("sslFlags", 1);
                    }
                    else
                    {
                        b.SetAttributeValue("sslFlags", 3);
                    }
                }

                iis.CommitChanges();
            }
        }
示例#3
0
        public static void UpdateSiteBinding(IisWebSiteBinding binding, string certStore, byte[] certHash)
        {
            using (var iis = new ServerManager())
            {
                var sites = iis.Sites.Where(_ => _.Id == binding.SiteId).ToArray();
                if (sites?.Length == 0)
                {
                    throw new ArgumentException("no matching sites found")
                          .With(nameof(binding.SiteId), binding.SiteId);
                }

                var bindingCount = 0;
                foreach (var site in sites)
                {
                    foreach (var b in site.Bindings)
                    {
                        if (!string.Equals(binding.BindingProtocol, b.Protocol))
                        {
                            continue;
                        }
                        if (!string.Equals(binding.BindingAddress, b.EndPoint?.Address?.ToString()))
                        {
                            continue;
                        }
                        if (!string.Equals(binding.BindingHost, b.Host))
                        {
                            continue;
                        }
                        if (!string.Equals(binding.BindingPort, b.EndPoint?.Port.ToString()))
                        {
                            continue;
                        }

                        ++bindingCount;

                        b.CertificateStoreName = certStore;
                        b.CertificateHash      = certHash;
                        if (binding.BindingHostRequired.GetValueOrDefault() && GetIisVersion().Major >= 8)
                        {
                            b.SetAttributeValue("sslFlags", 1);
                        }
                        else
                        {
                            b.SetAttributeValue("sslFlags", 3);
                        }
                    }
                }

                if (bindingCount == 0)
                {
                    throw new ArgumentException("no matching bindings found")
                          .With(nameof(binding.SiteId), binding.SiteId)
                          .With(nameof(binding.BindingProtocol), binding.BindingProtocol)
                          .With(nameof(binding.BindingAddress), binding.BindingAddress)
                          .With(nameof(binding.BindingPort), binding.BindingPort)
                          .With(nameof(binding.BindingHost), binding.BindingHost);
                }

                iis.CommitChanges();
            }
        }
        private void EditFile(HttpChallenge httpChallenge, bool delete, TextWriter msg)
        {
            IisWebSiteBinding site = IisHelper.ResolveSingleSite(WebSiteRef,
                                                                 IisHelper.ListDistinctHttpWebSites());

            var siteRoot = site.SiteRoot;

            if (!string.IsNullOrEmpty(OverrideSiteRoot))
            {
                siteRoot = OverrideSiteRoot;
            }
            if (string.IsNullOrEmpty(siteRoot))
            {
                throw new InvalidOperationException("missing root path for resolve site")
                      .With(nameof(IisWebSiteBinding.SiteId), site.SiteId)
                      .With(nameof(IisWebSiteBinding.SiteName), site.SiteName);
            }

            // IIS-configured Site Root can use env vars
            siteRoot = Environment.ExpandEnvironmentVariables(siteRoot);

            // Make sure we're using the canonical full path
            siteRoot = Path.GetFullPath(siteRoot);

            // We need to strip off any leading '/' in the path
            var filePath = httpChallenge.FilePath;

            if (filePath.StartsWith("/"))
            {
                filePath = filePath.Substring(1);
            }

            var fullFilePath   = Path.Combine(siteRoot, filePath);
            var fullDirPath    = Path.GetDirectoryName(fullFilePath);
            var fullConfigPath = Path.Combine(fullDirPath, "web.config");

            // This meta-data file will be placed next to the actual
            // Challenge answer content file and it captures some details
            // that we need in order to properly clean up the handling of
            // this Challenge after it has been submitted
            var fullMetaPath = $"{fullFilePath}-acmesharp_meta";

            // Check if user is running with elevated privs and warn if not
            if (!IisHelper.IsAdministrator())
            {
                Console.Error.WriteLine("WARNING:  You are not running with elelvated privileges.");
                Console.Error.WriteLine("          Write access may be denied to the destination.");
            }

            if (delete)
            {
                bool          skipLocalWebConfig = SkipLocalWebConfig;
                List <string> dirsCreated        = null;

                // First see if there's a meta file there to help us out
                if (File.Exists(fullMetaPath))
                {
                    var meta = JsonHelper.Load <IisChallengeHandlerMeta>(
                        File.ReadAllText(fullMetaPath));

                    skipLocalWebConfig = meta.SkippedLocalWebConfig;
                    dirsCreated        = meta.DirsCreated;
                }

                // Get rid of the Challenge answer content file
                if (File.Exists(fullFilePath))
                {
                    File.Delete(fullFilePath);
                    msg.WriteLine("* Challenge response content has been removed from local file path");
                    msg.WriteLine("    at:  [{0}]", fullFilePath);
                }

                // Get rid of web.config if necessary
                if (!skipLocalWebConfig && File.Exists(fullConfigPath))
                {
                    File.Delete(fullConfigPath);
                    msg.WriteLine("* Local web.config has been removed from local file path");
                    msg.WriteLine("    at:  [{0}]", fullFilePath);
                }

                // Get rid of the meta file so that we can clean up the dirs
                if (File.Exists(fullMetaPath))
                {
                    File.Delete(fullMetaPath);
                }

                // Walk up the tree if needed
                if (dirsCreated?.Count > 0)
                {
                    var dirsDeleted = new List <string>();
                    dirsCreated.Reverse();
                    foreach (var dir in dirsCreated)
                    {
                        if (Directory.Exists(dir))
                        {
                            if (Directory.GetFileSystemEntries(dir).Length == 0)
                            {
                                Directory.Delete(dir);
                                dirsDeleted.Add(dir);
                            }
                        }
                    }

                    if (dirsDeleted.Count > 0)
                    {
                        msg.WriteLine("* Removed the following directories:");
                        foreach (var dd in dirsDeleted)
                        {
                            msg.WriteLine("  - [{0}]", dd);
                        }
                    }
                }
            }
            else
            {
                // Figure out which dirs we have to create so
                // we can capture and clean it up later on
                var meta = new IisChallengeHandlerMeta
                {
                    WasAdmin = IisHelper.IsAdministrator(),
                    SkippedLocalWebConfig = SkipLocalWebConfig,
                    DirsCreated           = new List <string>(),
                };

                // In theory this ascending of the dir path should work
                // just fine, but just in case, this path segment counter
                // should gaurd against the possibility of an infinite loop
                var dirLimit = 100;

                var testDir = fullDirPath;
                while (!Directory.Exists(testDir))
                {
                    // Sanity check against an infinite loop
                    if (--dirLimit <= 0)
                    {
                        throw new Exception("Unexpected directory path segment count reached")
                              .With(nameof(dirLimit), "100")
                              .With(nameof(fullDirPath), fullDirPath)
                              .With(nameof(testDir), testDir)
                              .With($"first-{nameof(meta.DirsCreated)}",
                                    meta.DirsCreated[0])
                              .With($"last-{nameof(meta.DirsCreated)}",
                                    meta.DirsCreated[meta.DirsCreated.Count - 1]);
                    }

                    if (Path.GetFullPath(testDir) == siteRoot)
                    {
                        break;
                    }

                    // Add to the top of the list
                    meta.DirsCreated.Insert(0, testDir);
                    // Move to the parent
                    testDir = Path.GetDirectoryName(testDir);
                }

                foreach (var dir in meta.DirsCreated)
                {
                    Directory.CreateDirectory(dir);
                }

                File.WriteAllText(fullFilePath, httpChallenge.FileContent);
                File.WriteAllText(fullMetaPath, JsonHelper.Save(meta));

                msg.WriteLine("* Challenge response content has been written to local file path");
                msg.WriteLine("    at:  [{0}]", fullFilePath);
                msg.WriteLine("* Challenge response should be accessible with a MIME type of [text/json]");
                msg.WriteLine("    at:  [{0}]", httpChallenge.FileUrl);

                if (!SkipLocalWebConfig)
                {
                    var t = typeof(IisChallengeHandler);
                    var r = $"{t.Namespace}.{t.Name}-WebConfig";
                    using (Stream rs = t.Assembly.GetManifestResourceStream(r))
                    {
                        using (var fs = new FileStream(fullConfigPath, FileMode.Create))
                        {
                            rs.CopyTo(fs);
                        }
                    }
                    msg.WriteLine("* Local web.config has been created to serve response file as JSON");
                    msg.WriteLine("  however, you may need to adjust this file for your environment");
                    msg.WriteLine("    at: [{0}]", httpChallenge.FileUrl);
                }
                else
                {
                    msg.WriteLine("* Local web.config file creation has been skipped!");
                    msg.WriteLine("  You may need to manually adjust your configuration to serve the");
                    msg.WriteLine("  Challenge Response file with a MIME type of [text/json]");
                }
            }
        }