private async Task <bool> IsManagedCertificateRunning(string id, ICertifiedServer iis = null)
        {
            var managedCertificate = await _itemManager.GetById(id);

            if (managedCertificate != null)
            {
                if (iis == null)
                {
                    iis = _serverProvider;
                }

                try
                {
                    return(await iis.IsSiteRunning(managedCertificate.GroupId));
                }
                catch
                {
                    // by default we assume the site is running
                    return(true);
                }
            }
            else
            {
                //site not identified, assume it is running
                return(true);
            }
        }
Пример #2
0
        private Func <bool> PrepareChallengeResponse_TlsSni01(ILog log, ICertifiedServer iisManager, string domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedCertificate.RequestConfig;

            var tlsSniChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI);

            if (tlsSniChallenge == null)
            {
                log.Warning($"No tls-sni-01 challenge to complete for {managedCertificate.Name}. Request cannot continue.");
                return(() => false);
            }

            var sha256 = System.Security.Cryptography.SHA256.Create();

            var z = new byte[tlsSniChallenge.HashIterationCount][];

            // compute n sha256 hashes, where n=challengedata.iterationcount
            z[0] = sha256.ComputeHash(Encoding.UTF8.GetBytes(tlsSniChallenge.Value));

            for (var i = 1; i < z.Length; i++)
            {
                z[i] = sha256.ComputeHash(z[i - 1]);
            }

            // generate certs and install iis bindings
            var cleanupQueue = new List <Action>();

            var checkQueue = new List <Func <bool> >();

            foreach (var hex in z.Select(b =>
                                         BitConverter.ToString(b).Replace("-", "").ToLower()))
            {
                var sni = $"{hex.Substring(0, 32)}.{hex.Substring(32)}.acme.invalid";

                log.Information($"Preparing binding at: https://{domain}, sni: {sni}");

                var x509 = CertificateManager.GenerateSelfSignedCertificate(sni);

                CertificateManager.StoreCertificate(x509);

                var certStoreName = CertificateManager.GetStore().Name;

                // iisManager.InstallCertificateforBinding(certStoreName, x509.GetCertHash(),
                // managedCertificate.ServerSiteId, sni);

                // add check to the queue
                checkQueue.Add(() => _netUtil.CheckSNI(domain, sni).Result);

                // add cleanup actions to queue
                cleanupQueue.Add(() => iisManager.RemoveHttpsBinding(managedCertificate.ServerSiteId, sni));

                cleanupQueue.Add(() => CertificateManager.RemoveCertificate(x509));
            }

            // configure cleanup to execute the cleanup queue
            pendingAuth.Cleanup = () => cleanupQueue.ForEach(a => a());

            // perform our own config checks
            return(() => checkQueue.All(check => check()));
        }
        public CertifyManager()
        {
            var serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig();

            SettingsManager.LoadAppSettings();

            InitLogging(serverConfig);

            Util.SetSupportedTLSVersions();

            _itemManager    = new ItemManager();
            _serverProvider = (ICertifiedServer) new ServerProviderIIS();

            _progressResults = new ObservableCollection <RequestProgressState>();

            _pluginManager = new PluginManager();
            _pluginManager.LoadPlugins(new List <string> {
                "Licensing", "DashboardClient", "DeploymentTasks"
            });

            // TODO: convert providers to plugins, allow for async init
            var userAgent = Util.GetUserAgent();

            var certes = new CertesACMEProvider(Management.Util.GetAppDataFolder() + "\\certes", userAgent);

            certes.InitProvider(_serviceLog).Wait();

            _acmeClientProvider = certes;
            _vaultProvider      = certes;

            // init remaining utilities and optionally enable telematics
            _challengeDiagnostics = new ChallengeDiagnostics(CoreAppSettings.Current.EnableValidationProxyAPI);

            if (CoreAppSettings.Current.EnableAppTelematics)
            {
                _tc = new Util().InitTelemetry();
            }

            _httpChallengePort = serverConfig.HttpChallengeServerPort;
            _httpChallengeServerClient.Timeout = new TimeSpan(0, 0, 5);

            if (_tc != null)
            {
                _tc.TrackEvent("ServiceStarted");
            }

            _serviceLog?.Information("Certify Manager Started");

            PerformUpgrades().Wait();
        }
Пример #4
0
        public async Task <PendingAuthorization> PerformAutomatedChallengeResponse(ICertifiedServer iisManager, ManagedSite managedSite, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedSite.RequestConfig;
            var domain        = pendingAuth.Identifier.Dns;

            if (pendingAuth.Challenges != null)
            {
                // from list of possible challenges, select the one we prefer to attempt
                var requiredChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == managedSite.RequestConfig.ChallengeType);

                if (requiredChallenge != null)
                {
                    pendingAuth.AttemptedChallenge = requiredChallenge;
                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        // perform http-01 challenge response
                        var check = PrepareChallengeResponse_Http01(iisManager, domain, managedSite, pendingAuth);
                        if (requestConfig.PerformExtensionlessConfigChecks)
                        {
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check();
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        // perform tls-sni-01 challenge response
                        var check = PrepareChallengeResponse_TlsSni01(iisManager, domain, managedSite, pendingAuth);
                        if (requestConfig.PerformTlsSniBindingConfigChecks)
                        {
                            // set config check OK if all checks return true
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check();
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        // perform dns-01 challenge response FIXME:
                        var check = PrepareChallengeResponse_Dns01(domain, managedSite, pendingAuth);

                        /*if (requestConfig.PerformTlsSniBindingConfigChecks)
                         * {
                         *  // set config check OK if all checks return true
                         *  pendingAuth.AttemptedChallenge.ConfigCheckedOK = check();
                         * }*/
                    }
                }
            }
            return(pendingAuth);
        }
Пример #5
0
        public CertifyManager()
        {
            _serviceLog = new Loggy(
                new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .WriteTo.Debug()
                .WriteTo.File(Util.GetAppDataFolder("logs") + "\\sessionlog.txt", shared: true, flushToDiskInterval: new TimeSpan(0, 0, 10))
                .CreateLogger()
                );

            Util.SetSupportedTLSVersions();

            _itemManager    = new ItemManager();
            _serverProvider = (ICertifiedServer) new ServerProviderIIS();

            _progressResults = new ObservableCollection <RequestProgressState>();

            _pluginManager = new PluginManager();
            _pluginManager.LoadPlugins();

            // TODO: convert providers to plugins
            var certes = new Certify.Providers.Certes.CertesACMEProvider(Management.Util.GetAppDataFolder() + "\\certes");

            _acmeClientProvider = certes;
            _vaultProvider      = certes;

            // init remaining utilities and optionally enable telematics
            _challengeDiagnostics = new ChallengeDiagnostics(CoreAppSettings.Current.EnableValidationProxyAPI);

            if (CoreAppSettings.Current.EnableAppTelematics)
            {
                _tc = new Util().InitTelemetry();
            }

            PerformUpgrades();

            var serverConfig = Util.GetAppServiceConfig();

            _httpChallengePort = serverConfig.HttpChallengeServerPort;
            _httpChallengeServerClient.Timeout = new TimeSpan(0, 0, 5);

            if (_tc != null)
            {
                _tc.TrackEvent("ServiceStarted");
            }
        }
Пример #6
0
        /// <summary>
        /// WIP: For current configured environment, show preview of recommended site management (for
        ///      local IIS, scan sites and recommend actions)
        /// </summary>
        /// <returns></returns>
        public async Task <List <ManagedCertificate> > PreviewManagedCertificates(StandardServerTypes serverType,
                                                                                  ICertifiedServer serverProvider, ICertifyManager certifyManager)
        {
            var sites = new List <ManagedCertificate>();

            if (serverType == StandardServerTypes.IIS)
            {
                try
                {
                    var allSites = await serverProvider.GetSiteBindingList(CoreAppSettings.Current.IgnoreStoppedSites);

                    var iisSites = allSites
                                   .OrderBy(s => s.SiteId)
                                   .ThenBy(s => s.Host);

                    var siteIds = iisSites.GroupBy(x => x.SiteId);

                    foreach (var s in siteIds)
                    {
                        var managedCertificate = new ManagedCertificate {
                            Id = s.Key
                        };
                        managedCertificate.ItemType   = ManagedCertificateType.SSL_ACME;
                        managedCertificate.TargetHost = "localhost";
                        managedCertificate.Name       = iisSites.First(i => i.SiteId == s.Key).SiteName;

                        //TODO: replace site binding with domain options
                        //managedCertificate.SiteBindings = new List<ManagedCertificateBinding>();

                        /* foreach (var binding in s)
                         * {
                         *   var managedBinding = new ManagedCertificateBinding { Hostname = binding.Host, IP = binding.IP, Port = binding.Port, UseSNI = true, CertName = "Certify_" + binding.Host };
                         *   // managedCertificate.SiteBindings.Add(managedBinding);
                         * }*/
                        sites.Add(managedCertificate);
                    }
                }
                catch (Exception)
                {
                    //can't read sites
                    Debug.WriteLine("Can't get IIS site list.");
                }
            }

            return(sites);
        }
Пример #7
0
        /// <summary>
        /// Generate a list of actions which will be performed on the next renewal of this managed certificate, populating
        /// the description of each action with a Markdown format description
        /// </summary>
        /// <param name="item"></param>
        /// <param name="serverProvider"></param>
        /// <param name="certifyManager"></param>
        /// <returns></returns>
        public async Task <List <ActionStep> > GeneratePreview(
            ManagedCertificate item,
            ICertifiedServer serverProvider,
            ICertifyManager certifyManager,
            ICredentialsManager credentialsManager
            )
        {
            var newLine = "\r\n";
            var steps   = new List <ActionStep>();

            var stepIndex = 1;

            var hasDomains = true;

            var allTaskProviders = await certifyManager.GetDeploymentProviders();

            var certificateAuthorities = await certifyManager.GetCertificateAuthorities();

            // ensure defaults are applied for the deployment mode, overwriting any previous selections
            item.RequestConfig.ApplyDeploymentOptionDefaults();

            if (string.IsNullOrEmpty(item.RequestConfig.PrimaryDomain))
            {
                hasDomains = false;
            }

            if (hasDomains)
            {
                var allCredentials = await credentialsManager.GetCredentials();

                var allDomains = new List <string> {
                    item.RequestConfig.PrimaryDomain
                };

                if (item.RequestConfig.SubjectAlternativeNames != null)
                {
                    allDomains.AddRange(item.RequestConfig.SubjectAlternativeNames);
                }
                allDomains = allDomains.Distinct().OrderBy(d => d).ToList();

                // certificate summary
                var certDescription = new StringBuilder();
                var ca = certificateAuthorities.FirstOrDefault(c => c.Id == item.CertificateAuthorityId);

                certDescription.AppendLine(
                    $"A new certificate will be requested from the *{ca?.Title ?? "Default"}* certificate authority for the following domains:"
                    );

                certDescription.AppendLine($"\n**{item.RequestConfig.PrimaryDomain}** (Primary Domain)");

                if (item.RequestConfig.SubjectAlternativeNames.Any(s => s != item.RequestConfig.PrimaryDomain))
                {
                    certDescription.AppendLine($" and will include the following *Subject Alternative Names*:");

                    foreach (var d in item.RequestConfig.SubjectAlternativeNames)
                    {
                        certDescription.AppendLine($"* {d} ");
                    }
                }

                steps.Add(new ActionStep
                {
                    Title       = "Summary",
                    Description = certDescription.ToString()
                });

                // validation steps :
                // TODO: preview description should come from the challenge type provider

                var challengeInfo = new StringBuilder();
                foreach (var challengeConfig in item.RequestConfig.Challenges)
                {
                    challengeInfo.AppendLine(
                        $"{newLine}Authorization will be attempted using the **{challengeConfig.ChallengeType}** challenge type." +
                        newLine
                        );

                    var matchingDomains = item.GetChallengeConfigDomainMatches(challengeConfig, allDomains);
                    if (matchingDomains.Any())
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}The following matching domains will use this challenge: " + newLine
                            );

                        foreach (var d in matchingDomains)
                        {
                            challengeInfo.AppendLine($"{newLine} * {d}");
                        }

                        challengeInfo.AppendLine(
                            $"{newLine}**Please review the Deployment section below to ensure this certificate will be applied to the expected website bindings (if any).**" + newLine
                            );
                    }
                    else
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}*No domains will match this challenge type.* Either the challenge is not required or domain matches are not fully configured."
                            );
                    }

                    challengeInfo.AppendLine(newLine);

                    if (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        challengeInfo.AppendLine(
                            $"This will involve the creation of a randomly named (extensionless) text file for each domain (website) included in the certificate." +
                            newLine
                            );

                        if (CoreAppSettings.Current.EnableHttpChallengeServer)
                        {
                            challengeInfo.AppendLine(
                                $"The *Http Challenge Server* option is enabled. This will create a temporary web service on port 80 during validation. " +
                                $"This process co-exists with your main web server and listens for http challenge requests to /.well-known/acme-challenge/. " +
                                $"If you are using a web server on port 80 other than IIS (or other http.sys enabled server), that will be used instead." +
                                newLine
                                );
                        }

                        if (!string.IsNullOrEmpty(item.RequestConfig.WebsiteRootPath) && string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
                        {
                            challengeInfo.AppendLine(
                                $"The file will be created at the path `{item.RequestConfig.WebsiteRootPath}\\.well-known\\acme-challenge\\` " +
                                newLine
                                );
                        }

                        if (!string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
                        {
                            challengeInfo.AppendLine(
                                $"The file will be created at the path `{challengeConfig.ChallengeRootPath}\\.well-known\\acme-challenge\\` " +
                                newLine
                                );
                        }

                        challengeInfo.AppendLine(
                            $"The text file will need to be accessible from the URL `http://<yourdomain>/.well-known/acme-challenge/<randomfilename>` " +
                            newLine);

                        challengeInfo.AppendLine(
                            $"The issuing Certificate Authority will follow any redirection in place (such as rewriting the URL to *https*) but the initial request will be made via *http* on port 80. " +
                            newLine);
                    }

                    if (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        challengeInfo.AppendLine(
                            $"This will involve the creation of a DNS TXT record named `_acme-challenge.yourdomain.com` for each domain or subdomain included in the certificate. " +
                            newLine);

                        if (!string.IsNullOrEmpty(challengeConfig.ChallengeCredentialKey))
                        {
                            var creds = allCredentials.FirstOrDefault(c => c.StorageKey == challengeConfig.ChallengeCredentialKey);
                            if (creds != null)
                            {
                                challengeInfo.AppendLine(
                                    $"The following DNS API Credentials will be used:  **{creds.Title}** " + newLine);
                            }
                            else
                            {
                                challengeInfo.AppendLine(
                                    $"**Invalid credential settngs.**  The currently selected credential does not exist."
                                    );
                            }
                        }
                        else
                        {
                            challengeInfo.AppendLine(
                                $"No DNS API Credentials have been set.  API Credentials are normally required to make automatic updates to DNS records."
                                );
                        }

                        challengeInfo.AppendLine(
                            newLine + $"The issuing Certificate Authority will follow any redirection in place (such as a substitute CNAME pointing to another domain) but the initial request will be made against any of the domain's nameservers. "
                            );
                    }

                    if (!string.IsNullOrEmpty(challengeConfig.DomainMatch))
                    {
                        challengeInfo.AppendLine(
                            $"{newLine}This challenge type will be selected based on matching domain **{challengeConfig.DomainMatch}** ");
                    }
                    else
                    {
                        if (item.RequestConfig.Challenges.Count > 1)
                        {
                            challengeInfo.AppendLine(
                                $"{newLine}This challenge type will be selected for any domain not matched by another challenge. ");
                        }
                        else
                        {
                            challengeInfo.AppendLine(
                                $"{newLine}**This challenge type will be selected for all domains.**");
                        }
                    }

                    challengeInfo.AppendLine($"{newLine}---{newLine}");
                }

                steps.Add(new ActionStep
                {
                    Title       = $"{stepIndex}. Domain Validation",
                    Category    = "Validation",
                    Description = challengeInfo.ToString()
                });
                stepIndex++;

                // pre request tasks
                if (item.PreRequestTasks?.Any() == true)
                {
                    var substeps = item.PreRequestTasks.Select(t => new ActionStep {
                        Key = t.Id, Title = $"{t.TaskName} ({allTaskProviders.FirstOrDefault(p => p.Id == t.TaskTypeId)?.Title})", Description = t.Description
                    });

                    steps.Add(new ActionStep
                    {
                        Title       = $"{stepIndex}. Pre-Request Tasks",
                        Category    = "PreRequestTasks",
                        Description = $"Execute {substeps.Count()} Pre-Request Tasks",
                        Substeps    = substeps.ToList()
                    });
                    stepIndex++;
                }

                // cert request step
                var certRequest =
                    $"A Certificate Signing Request (CSR) will be submitted to the Certificate Authority, using the **{item.RequestConfig.CSRKeyAlg}** signing algorithm.";
                steps.Add(new ActionStep
                {
                    Title       = $"{stepIndex}. Certificate Request",
                    Category    = "CertificateRequest",
                    Description = certRequest
                });
                stepIndex++;


                // deployment & binding steps

                var deploymentDescription = new StringBuilder();
                var deploymentStep        = new ActionStep
                {
                    Title       = $"{stepIndex}. Deployment",
                    Category    = "Deployment",
                    Description = ""
                };

                if (
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.Auto ||
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.AllSites ||
                    item.RequestConfig.DeploymentSiteOption == DeploymentOption.SingleSite
                    )
                {
                    // deploying to single or multiple Site

                    if (item.RequestConfig.DeploymentBindingMatchHostname)
                    {
                        deploymentDescription.AppendLine(
                            "* Deploy to hostname bindings matching certificate domains.");
                    }

                    if (item.RequestConfig.DeploymentBindingBlankHostname)
                    {
                        deploymentDescription.AppendLine("* Deploy to bindings with blank hostname.");
                    }

                    if (item.RequestConfig.DeploymentBindingReplacePrevious)
                    {
                        deploymentDescription.AppendLine("* Deploy to bindings with previous certificate.");
                    }

                    if (item.RequestConfig.DeploymentBindingOption == DeploymentBindingOption.AddOrUpdate)
                    {
                        deploymentDescription.AppendLine("* Add or Update https bindings as required");
                    }

                    if (item.RequestConfig.DeploymentBindingOption == DeploymentBindingOption.UpdateOnly)
                    {
                        deploymentDescription.AppendLine("* Update https bindings as required (no auto-created https bindings)");
                    }

                    if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.SingleSite)
                    {
                        if (!string.IsNullOrEmpty(item.ServerSiteId))
                        {
                            try
                            {
                                var siteInfo = await serverProvider.GetSiteById(item.ServerSiteId);

                                deploymentDescription.AppendLine($"## Deploying to Site" + newLine + newLine +
                                                                 $"`{siteInfo.Name}`" + newLine);
                            }
                            catch (Exception exp)
                            {
                                deploymentDescription.AppendLine($"Error: **cannot identify selected site.** {exp.Message} ");
                            }
                        }
                    }
                    else
                    {
                        deploymentDescription.AppendLine($"## Deploying to all matching sites:");
                    }

                    // add deployment sub-steps (if any)
                    var bindingRequest = await certifyManager.DeployCertificate(item, null, true);

                    if (bindingRequest.Actions == null || !bindingRequest.Actions.Any())
                    {
                        deploymentStep.Substeps = new List <ActionStep>
                        {
                            new ActionStep {
                                Description = newLine + "**There are no matching targets to deploy to. Certificate will be stored but currently no bindings will be updated.**"
                            }
                        };
                    }
                    else
                    {
                        deploymentStep.Substeps = bindingRequest.Actions;

                        deploymentDescription.AppendLine(" Action | Site | Binding ");
                        deploymentDescription.Append(" ------ | ---- | ------- ");
                    }
                }
                else if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.DeploymentStoreOnly)
                {
                    deploymentDescription.AppendLine("* The certificate will be saved to the local machines Certificate Store only (Personal/My Store)");
                }
                else if (item.RequestConfig.DeploymentSiteOption == DeploymentOption.NoDeployment)
                {
                    deploymentDescription.AppendLine("* The certificate will be saved to local disk only.");
                }

                deploymentStep.Description = deploymentDescription.ToString();

                steps.Add(deploymentStep);
                stepIndex++;


                // post request deployment tasks
                if (item.PostRequestTasks?.Any() == true)
                {
                    var substeps = item.PostRequestTasks.Select(t => new ActionStep {
                        Key = t.Id, Title = $"{t.TaskName} ({allTaskProviders.FirstOrDefault(p => p.Id == t.TaskTypeId)?.Title})", Description = t.Description
                    });

                    steps.Add(new ActionStep
                    {
                        Title       = $"{stepIndex}. Post-Request (Deployment) Tasks",
                        Category    = "PostRequestTasks",
                        Description = $"Execute {substeps.Count()} Post-Request Tasks",
                        Substeps    = substeps.ToList()
                    });
                    stepIndex++;
                }

                stepIndex = steps.Count;
            }
            else
            {
                steps.Add(new ActionStep
                {
                    Title       = "Certificate has no domains",
                    Description = "No domains have been added to this certificate, so a certificate cannot be requested. Each certificate requires a primary domain (a 'subject') and an optional list of additional domains (subject alternative names)."
                });
            }

            return(steps);
        }
Пример #8
0
        /// <summary>
        /// Prepares IIS to respond to a http-01 challenge
        /// </summary>
        /// <returns> Test the challenge response locally. </returns>
        private async Task <ActionResult> PerformChallengeResponse_Http01(ILog log, ICertifiedServer iisManager, string domain, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedCertificate.RequestConfig;
            var httpChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP);

            if (httpChallenge == null)
            {
                var msg = $"No http challenge to complete for {managedCertificate.Name}. Request cannot continue.";
                log.Warning(msg);
                return(new ActionResult {
                    IsSuccess = false, Message = msg
                });
            }

            log.Information($"Preparing challenge response for the issuing Certificate Authority to check at: {httpChallenge.ResourceUri} with content {httpChallenge.Value}");
            log.Information("If the challenge response file is not accessible at this exact URL the validation will fail and a certificate will not be issued.");

            // get website root path (from challenge config or fallback to deprecated
            // WebsiteRootPath), expand environment variables if required
            var websiteRootPath = requestConfig.WebsiteRootPath;
            var challengeConfig = managedCertificate.GetChallengeConfig(domain);

            if (!string.IsNullOrEmpty(challengeConfig.ChallengeRootPath))
            {
                websiteRootPath = challengeConfig.ChallengeRootPath;
            }

            if (!string.IsNullOrEmpty(managedCertificate.ServerSiteId))
            {
                var siteInfo = await iisManager.GetSiteById(managedCertificate.ServerSiteId);

                if (siteInfo == null)
                {
                    return(new ActionResult {
                        IsSuccess = false, Message = "IIS Website unavailable. Site may be removed or IIS is unavailable"
                    });
                }

                // if website root path not specified, determine it now
                if (string.IsNullOrEmpty(websiteRootPath))
                {
                    websiteRootPath = siteInfo.Path;
                }

                if (!string.IsNullOrEmpty(websiteRootPath) && websiteRootPath.Contains("%"))
                {
                    // if websiteRootPath contains %websiteroot% variable, replace that with the
                    // current physical path for the site
                    if (websiteRootPath.Contains("%websiteroot%"))
                    {
                        // sets env variable for this process only
                        Environment.SetEnvironmentVariable("websiteroot", siteInfo.Path);
                    }
                    // expand any environment variables present in site path
                    websiteRootPath = Environment.ExpandEnvironmentVariables(websiteRootPath);
                }
            }

            log.Information("Using website path {path}", websiteRootPath ?? "[Auto]");

            if (string.IsNullOrEmpty(websiteRootPath) || !Directory.Exists(websiteRootPath))
            {
                // our website no longer appears to exist on disk, continuing would potentially
                // create unwanted folders, so it's time for us to give up

                var msg = $"The website root path for {managedCertificate.Name} could not be determined. Request cannot continue.";
                log.Error(msg);
                return(new ActionResult {
                    IsSuccess = false, Message = msg
                });
            }

            // copy temp file to path challenge expects in web folder
            var destFile = Path.Combine(websiteRootPath, httpChallenge.ResourcePath);
            var destPath = Path.GetDirectoryName(destFile);

            if (!Directory.Exists(destPath))
            {
                try
                {
                    Directory.CreateDirectory(destPath);
                }
                catch (Exception exp)
                {
                    // failed to create directory, probably permissions or may be invalid config

                    var msg = $"Pre-config check failed: Could not create directory: {destPath}";
                    log.Error(exp, msg);
                    return(new ActionResult {
                        IsSuccess = false, Message = msg
                    });
                }
            }

            // copy challenge response to web folder /.well-known/acme-challenge. Check if it already
            // exists (as in 'configcheck' file) as can cause conflicts.
            if (!File.Exists(destFile) || !destFile.EndsWith("configcheck"))
            {
                try
                {
                    File.WriteAllText(destFile, httpChallenge.Value);
                }
                catch (Exception exp)
                {
                    // failed to create configcheck file, probably permissions or may be invalid config

                    var msg = $"Pre-config check failed: Could not create file: {destFile}";
                    log.Error(exp, msg);
                    return(new ActionResult {
                        IsSuccess = false, Message = msg
                    });
                }
            }

            // prepare cleanup - should this be configurable? Because in some case many sites
            // renewing may all point to the same web root, we keep the configcheck file
            pendingAuth.Cleanup = () =>
            {
                if (!destFile.EndsWith("configcheck") && File.Exists(destFile))
                {
                    log.Debug("Challenge Cleanup: Removing {file}", destFile);
                    try
                    {
                        File.Delete(destFile);
                    }
                    catch { }
                }
            };

            // if config checks are enabled but our last renewal was successful, skip auto config
            // until we have failed twice
            if (requestConfig.PerformExtensionlessConfigChecks)
            {
                if (managedCertificate.DateRenewed != null && managedCertificate.RenewalFailureCount < 2)
                {
                    return(new ActionResult {
                        IsSuccess = true, Message = $"Skipping URL access checks and auto config (if applicable): {httpChallenge.ResourceUri}. Will resume checks if renewal failure count exceeds 2 attempts."
                    });
                }

                // first check if it already works with no changes
                if (await _netUtil.CheckURL(log, httpChallenge.ResourceUri))
                {
                    return(new ActionResult {
                        IsSuccess = true, Message = $"Verified URL is accessible: {httpChallenge.ResourceUri}"
                    });
                }

                // initial check didn't work, if auto config enabled attempt to find a working config
                if (requestConfig.PerformAutoConfig)
                {
                    // FIXME: need to only overwrite config we have auto populated, not user
                    // specified config, compare to our preconfig and only overwrite if same as ours?
                    // Or include preset key in our config, or make behaviour configurable
                    LogAction($"Pre-config check failed: Auto-config will overwrite existing config: {destPath}\\web.config");

                    var configOptions = Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "Scripts", "Web.config"), "*.config");

                    foreach (var configFile in configOptions)
                    {
                        // create a web.config for extensionless files, then test it (make a request
                        // for the extensionless configcheck file over http)

                        var webConfigContent = File.ReadAllText(configFile);

                        // no existing config, attempt auto config and perform test
                        LogAction($"Testing config alternative: " + configFile);

                        try
                        {
                            System.IO.File.WriteAllText(Path.Combine(destPath, "web.config"), webConfigContent);
                        }
                        catch (Exception exp)
                        {
                            LogAction($"Failed to write config: " + exp.Message);
                        }

                        if (await _netUtil.CheckURL(log, httpChallenge.ResourceUri))
                        {
                            return(new ActionResult {
                                IsSuccess = true, Message = $"Verified URL is accessible: {httpChallenge.ResourceUri}"
                            });
                        }
                    }
                }

                // failed to auto configure or confirm resource is accessible
                return(new ActionResult
                {
                    IsSuccess = false,
                    Message = $"Could not verify URL is accessible: {httpChallenge.ResourceUri}"
                });
            }
            else
            {
                return(new ActionResult
                {
                    IsSuccess = false,
                    Message = $"Config checks disabled. Did not verify URL access: {httpChallenge.ResourceUri}"
                });
            }
        }
Пример #9
0
        /// <summary>
        /// Simulates responding to a challenge, performs a sample configuration and attempts to
        /// verify it.
        /// </summary>
        /// <param name="serverManager">  </param>
        /// <param name="managedCertificate">  </param>
        /// <returns> APIResult </returns>
        /// <remarks>
        /// The purpose of this method is to test the options (permissions, configuration) before
        /// submitting a request to the ACME server, to avoid creating failed requests and hitting
        /// usage limits.
        /// </remarks>
        public async Task <List <StatusMessage> > TestChallengeResponse(
            ILog log,
            ICertifiedServer serverManager,
            ManagedCertificate managedCertificate,
            bool isPreviewMode,
            bool enableDnsChecks,
            ICredentialsManager credentialsManager,
            IProgress <RequestProgressState> progress = null
            )
        {
            var results = new List <StatusMessage>();

            var requestConfig = managedCertificate.RequestConfig;

            var domains = new List <string> {
                requestConfig.PrimaryDomain
            };

            if (requestConfig.SubjectAlternativeNames != null)
            {
                domains.AddRange(requestConfig.SubjectAlternativeNames);
            }

            domains = domains.Distinct().ToList();

            // if wildcard domain included, check first level labels not also specified, i.e.
            // *.example.com & www.example.com cannot be mixed, but example.com, *.example.com &
            // test.wwww.example.com can
            var invalidLabels = new List <string>();

            if (domains.Any(d => d.StartsWith("*.")))
            {
                foreach (var wildcard in domains.Where(d => d.StartsWith("*.")))
                {
                    var rootDomain = wildcard.Replace("*.", "");
                    // add list of domains where label count exceeds root domain label count
                    invalidLabels.AddRange(domains.Where(domain => domain != wildcard && domain.EndsWith(rootDomain) && domain.Count(s => s == '.') == wildcard.Count(s => s == '.')));

                    if (invalidLabels.Any())
                    {
                        results.Add(new StatusMessage {
                            IsOK = false, Message = $"Wildcard domain certificate requests (e.g. {wildcard}) cannot be mixed with requests including immediate subdomains (e.g. {invalidLabels[0]})."
                        });
                        return(results);
                    }
                }
            }

            var generatedAuthorizations = new List <PendingAuthorization>();

            try
            {
                // if DNS checks enabled, attempt them here
                if (isPreviewMode && enableDnsChecks)
                {
                    var includeIPResolution = false;
                    if (managedCertificate.RequestConfig.Challenges.Any(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP))
                    {
                        includeIPResolution = true;
                    }

                    log.Information("Performing preview DNS tests. {managedItem}", managedCertificate);

                    var tasks = new List <Task <List <ActionResult> > >();

                    foreach (var d in domains)
                    {
                        tasks.Add(_netUtil.CheckDNS(log, d.Replace("*.", ""), includeIPResolution));
                    }

                    var allResults = await Task.WhenAll(tasks);

                    // add DNS check results. DNS check fails are considered a warning instead of an error.
                    foreach (var checkResults in allResults)
                    {
                        foreach (var c in checkResults)
                        {
                            results.Add(new StatusMessage
                            {
                                IsOK       = true,
                                HasWarning = !c.IsSuccess,
                                Message    = c.Message
                            });
                        }
                    }
                }

                foreach (var domain in domains)
                {
                    var challengeConfig = managedCertificate.GetChallengeConfig(domain);

                    var challengeType = challengeConfig.ChallengeType;
                    if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        log.Warning("tls-sni-01 challenge type is no longer supported by the Certificate Authority. Falling back to http-01");
                        challengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP;
                    }

                    if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        // if dns validation not selected but one or more domains is a wildcard, reject
                        if (domain.StartsWith("*."))
                        {
                            results.Add(new StatusMessage {
                                IsOK = false, Message = $"http-01 authorization cannot be used for wildcard domains: {domain}. Use DNS (dns-01) validation instead."
                            });
                            return(results);
                        }

                        var challengeFileUrl = $"http://{domain}/.well-known/acme-challenge/configcheck";

                        var simulatedAuthorization = new PendingAuthorization
                        {
                            Challenges = new List <AuthorizationChallengeItem> {
                                new AuthorizationChallengeItem
                                {
                                    ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP,
                                    ResourcePath  = Path.Combine(".well-known", "acme-challenge", "configcheck"),
                                    ResourceUri   = challengeFileUrl,
                                    Value         = "Extensionless File Config Test - OK"
                                }
                            }
                        };

                        generatedAuthorizations.Add(simulatedAuthorization);

                        var httpChallengeResult = await PerformChallengeResponse_Http01(
                            log, serverManager, domain, managedCertificate, simulatedAuthorization
                            );

                        if (!httpChallengeResult.IsSuccess)
                        {
                            var result = new StatusMessage();
                            result.IsOK = false;
                            result.FailedItemSummary.Add($"Config checks failed to verify http://{domain} is both publicly accessible and can serve extensionless files e.g. {challengeFileUrl}");
                            result.Message = httpChallengeResult.Message;
                            results.Add(result);

                            // don't check any more after first failure
                            break;
                        }
                        else
                        {
                            results.Add(new StatusMessage {
                                IsOK = true, Message = httpChallengeResult.Message, Result = httpChallengeResult
                            });
                        }
                    }
                    else if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        var result = new StatusMessage();
                        result.IsOK = false;
                        result.FailedItemSummary.Add($"The {SupportedChallengeTypes.CHALLENGE_TYPE_SNI} challenge type is no longer available.");
                        results.Add(result);

                        return(results);

                        /*
                         *
                         * var serverVersion = await serverManager.GetServerVersion();
                         *
                         * if (serverVersion.Major < 8)
                         * {
                         *  result.IsOK = false;
                         *  result.FailedItemSummary.Add($"The {SupportedChallengeTypes.CHALLENGE_TYPE_SNI} challenge is only available for IIS versions 8+.");
                         *  results.Add(result);
                         *
                         *  return results;
                         * }
                         *
                         * var simulatedAuthorization = new PendingAuthorization
                         * {
                         *  Challenges = new List<AuthorizationChallengeItem> {
                         *           new AuthorizationChallengeItem
                         *           {
                         *                ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_SNI,
                         *                HashIterationCount= 1,
                         *                Value = GenerateSimulatedKeyAuth()
                         *           }
                         *       }
                         * };
                         *
                         * generatedAuthorizations.Add(simulatedAuthorization);
                         *
                         * result.IsOK =
                         *   PrepareChallengeResponse_TlsSni01(
                         *      log, serverManager, domain, managedCertificate, simulatedAuthorization
                         *  )();
                         *
                         * results.Add(result);
                         */
                    }
                    else if (challengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        var recordName = $"_acme-challenge-test.{domain}".Replace("*.", "");

                        // ISSUE: dependency on changing behavior for a specific plugin
                        if (challengeConfig.ChallengeProvider == "DNS01.API.AcmeDns")
                        {
                            // use real cname to avoid having to setup different records
                            recordName = $"_acme-challenge.{domain}".Replace("*.", "");
                        }

                        var simulatedAuthorization = new PendingAuthorization
                        {
                            Challenges = new List <AuthorizationChallengeItem> {
                                new AuthorizationChallengeItem
                                {
                                    ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS,
                                    Key           = recordName,
                                    Value         = GenerateSimulatedDnsAuthValue()
                                }
                            }
                        };
                        generatedAuthorizations.Add(simulatedAuthorization);

                        var dnsResult =
                            await PerformChallengeResponse_Dns01(
                                log,
                                domain,
                                managedCertificate,
                                simulatedAuthorization,
                                isTestMode : true,
                                credentialsManager
                                );

                        var result = new StatusMessage();

                        result.Message = dnsResult.Result.Message;
                        result.IsOK    = dnsResult.Result.IsSuccess;
                        if (!result.IsOK)
                        {
                            result.FailedItemSummary.Add(dnsResult.Result.Message);
                        }

                        results.Add(result);
                    }
                    else
                    {
                        throw new NotSupportedException($"ChallengeType not supported: {challengeConfig.ChallengeType}");
                    }
                }
            }
            finally
            {
                //FIXME: needs to be filtered by managed site: result.Message = String.Join("\r\n", GetActionLogSummary());
                generatedAuthorizations.ForEach(ga => ga.Cleanup());
            }

            return(results);
        }
Пример #10
0
        public async Task <PendingAuthorization> PerformAutomatedChallengeResponse(ILog log, ICertifiedServer iisManager, ManagedCertificate managedCertificate, PendingAuthorization pendingAuth, ICredentialsManager credentialsManager)
        {
            var requestConfig   = managedCertificate.RequestConfig;
            var domain          = pendingAuth.Identifier.Dns;
            var challengeConfig = managedCertificate.GetChallengeConfig(domain);

            if (pendingAuth.Challenges != null)
            {
                // from list of possible challenges, select the one we prefer to attempt
                var requiredChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == challengeConfig.ChallengeType);

                if (requiredChallenge != null)
                {
                    pendingAuth.AttemptedChallenge = requiredChallenge;
                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        // perform http-01 challenge response
                        var check = await PerformChallengeResponse_Http01(log, iisManager, domain, managedCertificate, pendingAuth);

                        if (requestConfig.PerformExtensionlessConfigChecks)
                        {
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check.IsSuccess;
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        // perform tls-sni-01 challenge response
                        var check = PrepareChallengeResponse_TlsSni01(log, iisManager, domain, managedCertificate, pendingAuth);
                        if (requestConfig.PerformTlsSniBindingConfigChecks)
                        {
                            // set config check OK if all checks return true
                            pendingAuth.AttemptedChallenge.ConfigCheckedOK = check();
                        }
                    }

                    if (requiredChallenge.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        // perform dns-01 challenge response
                        var check = await PerformChallengeResponse_Dns01(log, domain, managedCertificate, pendingAuth, isTestMode : false, credentialsManager);

                        pendingAuth.AttemptedChallenge.IsFailure          = !check.Result.IsSuccess;
                        pendingAuth.AttemptedChallenge.ChallengeResultMsg = check.Result.Message;
                        pendingAuth.AttemptedChallenge.IsAwaitingUser     = check.IsAwaitingUser;
                        pendingAuth.AttemptedChallenge.PropagationSeconds = check.PropagationSeconds;
                        pendingAuth.IsFailure          = !check.Result.IsSuccess;
                        pendingAuth.AuthorizationError = check.Result.Message;
                    }
                }
            }
            return(pendingAuth);
        }
Пример #11
0
 public Task <StatusMessage> TestChallengeResponse(ICertifiedServer iisManager, ManagedSite managedSite, bool isPreviewMode)
 {
     throw new System.NotImplementedException();
 }
Пример #12
0
        public CertifyManager()
        {
            var serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig();

            SettingsManager.LoadAppSettings();

            InitLogging(serverConfig);

            Util.SetSupportedTLSVersions();

            _itemManager        = new ItemManager();
            _credentialsManager = new CredentialsManager();
            _serverProvider     = (ICertifiedServer) new ServerProviderIIS();

            _progressResults = new ObservableCollection <RequestProgressState>();

            _pluginManager = new PluginManager();
            _pluginManager.LoadPlugins(new List <string> {
                "Licensing", "DashboardClient", "DeploymentTasks", "CertificateManagers"
            });


            // load core CAs and custom CAs
            foreach (var ca in CertificateAuthority.CoreCertificateAuthorities)
            {
                _certificateAuthorities.TryAdd(ca.Id, ca);
            }

            try
            {
                var customCAs = SettingsManager.GetCustomCertificateAuthorities();

                foreach (var ca in customCAs)
                {
                    _certificateAuthorities.TryAdd(ca.Id, ca);
                }
            }
            catch (Exception exp)
            {
                // failed to load custom CAs
                _serviceLog.Error(exp.Message);
            }


            // init remaining utilities and optionally enable telematics
            _challengeDiagnostics = new ChallengeDiagnostics(CoreAppSettings.Current.EnableValidationProxyAPI);

            if (CoreAppSettings.Current.EnableAppTelematics)
            {
                _tc = new Util().InitTelemetry();
            }

            _httpChallengePort = serverConfig.HttpChallengeServerPort;
            _httpChallengeServerClient.Timeout = new TimeSpan(0, 0, 20);

            if (_tc != null)
            {
                _tc.TrackEvent("ServiceStarted");
            }

            _serviceLog?.Information("Certify Manager Started");

            PerformAccountUpgrades().Wait();

            PerformManagedCertificateMigrations().Wait();
        }
Пример #13
0
        /// <summary>
        /// Simulates responding to a challenge, performs a sample configuration and attempts to
        /// verify it.
        /// </summary>
        /// <param name="iisManager"></param>
        /// <param name="managedSite"></param>
        /// <returns> APIResult </returns>
        /// <remarks>
        /// The purpose of this method is to test the options (permissions, configuration) before
        /// submitting a request to the ACME server, to avoid creating failed requests and hitting
        /// usage limits.
        /// </remarks>
        public async Task <StatusMessage> TestChallengeResponse(ICertifiedServer iisManager, ManagedSite managedSite, bool isPreviewMode, bool enableDnsChecks)
        {
            return(await Task.Run(() =>
            {
                _actionLogs.Clear(); // reset action logs

                var requestConfig = managedSite.RequestConfig;
                var result = new StatusMessage {
                    IsOK = true
                };
                var domains = new List <string> {
                    requestConfig.PrimaryDomain
                };

                if (requestConfig.SubjectAlternativeNames != null)
                {
                    domains.AddRange(requestConfig.SubjectAlternativeNames);
                }

                var generatedAuthorizations = new List <PendingAuthorization>();

                try
                {
                    // if DNS checks enabled, attempt them here
                    if (isPreviewMode && enableDnsChecks)
                    {
                        // check all domain configs
                        Parallel.ForEach(domains.Distinct(), new ParallelOptions
                        {
                            // check 8 domains at a time
                            MaxDegreeOfParallelism = 8
                        },
                                         domain =>
                        {
                            var(ok, message) = _netUtil.CheckDNS(domain);
                            if (!ok)
                            {
                                result.IsOK = false;
                                result.FailedItemSummary.Add(message);
                            }
                        });
                        if (!result.IsOK)
                        {
                            return result;
                        }
                    }

                    if (requestConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP)
                    {
                        foreach (var domain in domains.Distinct())
                        {
                            string challengeFileUrl = $"http://{domain}/.well-known/acme-challenge/configcheck";

                            var simulatedAuthorization = new PendingAuthorization
                            {
                                Challenges = new List <AuthorizationChallengeItem> {
                                    new AuthorizationChallengeItem
                                    {
                                        ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_HTTP,
                                        ResourcePath = ".well-known\\acme-challenge\\configcheck",
                                        ResourceUri = challengeFileUrl,
                                        Value = "Extensionless File Config Test - OK"
                                    }
                                }
                            };

                            generatedAuthorizations.Add(simulatedAuthorization);

                            var resultOK = PrepareChallengeResponse_Http01(
                                iisManager, domain, managedSite, simulatedAuthorization
                                )();

                            if (!resultOK)
                            {
                                result.IsOK = false;
                                result.FailedItemSummary.Add($"Config checks failed to verify http://{domain} is both publicly accessible and can serve extensionless files e.g. {challengeFileUrl}");

                                // don't check any more after first failure
                                break;
                            }
                        }
                    }
                    else if (requestConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        if (iisManager.GetServerVersion().Major < 8)
                        {
                            result.IsOK = false;
                            result.FailedItemSummary.Add($"The {SupportedChallengeTypes.CHALLENGE_TYPE_SNI} challenge is only available for IIS versions 8+.");
                            return result;
                        }

                        result.IsOK = domains.Distinct().All(domain =>
                        {
                            var simulatedAuthorization = new PendingAuthorization
                            {
                                Challenges = new List <AuthorizationChallengeItem> {
                                    new AuthorizationChallengeItem
                                    {
                                        ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_SNI,
                                        HashIterationCount = 1,
                                        Value = GenerateSimulatedKeyAuth()
                                    }
                                }
                            };
                            generatedAuthorizations.Add(simulatedAuthorization);
                            return PrepareChallengeResponse_TlsSni01(
                                iisManager, domain, managedSite, simulatedAuthorization
                                )();
                        });
                    }
                    else if (requestConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        result.IsOK = domains.Distinct().All(domain =>
                        {
                            var simulatedAuthorization = new PendingAuthorization
                            {
                                Challenges = new List <AuthorizationChallengeItem> {
                                    new AuthorizationChallengeItem
                                    {
                                        ChallengeType = SupportedChallengeTypes.CHALLENGE_TYPE_DNS,
                                        Key = "_acme-challenge.test." + domain,
                                        Value = GenerateSimulatedKeyAuth()
                                    }
                                }
                            };

                            generatedAuthorizations.Add(simulatedAuthorization);

                            return PrepareChallengeResponse_Dns01(
                                domain, managedSite, simulatedAuthorization
                                )();
                        });
                    }
                    else
                    {
                        throw new NotSupportedException($"ChallengeType not supported: {requestConfig.ChallengeType}");
                    }
                }
                finally
                {
                    //FIXME: needs to be filtered by managed site: result.Message = String.Join("\r\n", GetActionLogSummary());
                    generatedAuthorizations.ForEach(ga => ga.Cleanup());
                }
                return result;
            }));
        }
Пример #14
0
        /// <summary>
        /// Prepares IIS to respond to a http-01 challenge
        /// </summary>
        /// <returns>
        /// A Boolean returning Func. Invoke the Func to test the challenge response locally.
        /// </returns>
        private Func <bool> PrepareChallengeResponse_Http01(ICertifiedServer iisManager, string domain, ManagedSite managedSite, PendingAuthorization pendingAuth)
        {
            var requestConfig = managedSite.RequestConfig;
            var httpChallenge = pendingAuth.Challenges.FirstOrDefault(c => c.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_HTTP);

            if (httpChallenge == null)
            {
                this.LogAction($"No http challenge to complete for {managedSite.Name}. Request cannot continue.");
                return(() => false);
            }

            this.LogAction("Preparing challenge response for Let's Encrypt server to check at: " + httpChallenge.ResourceUri);
            this.LogAction("If the challenge response file is not accessible at this exact URL the validation will fail and a certificate will not be issued.");

            // get website root path, expand environment variables if required
            string websiteRootPath = requestConfig.WebsiteRootPath;

            // if website root path not specified, determine it now
            if (String.IsNullOrEmpty(websiteRootPath))
            {
                websiteRootPath = iisManager.GetSitePhysicalPath(managedSite);
            }

            if (!String.IsNullOrEmpty(websiteRootPath) && websiteRootPath.Contains("%"))
            {
                // if websiteRootPath contains %websiteroot% variable, replace that with the current
                // physical path for the site
                if (websiteRootPath.Contains("%websiteroot%"))
                {
                    // sets env variable for this process only
                    Environment.SetEnvironmentVariable("websiteroot", iisManager.GetSitePhysicalPath(managedSite));
                }
                // expand any environment variables present in site path
                websiteRootPath = Environment.ExpandEnvironmentVariables(websiteRootPath);
            }

            if (String.IsNullOrEmpty(websiteRootPath) || !Directory.Exists(websiteRootPath))
            {
                // our website no longer appears to exist on disk, continuing would potentially
                // create unwanted folders, so it's time for us to give up
                this.LogAction($"The website root path for {managedSite.Name} could not be determined. Request cannot continue.");
                return(() => false);
            }

            // copy temp file to path challenge expects in web folder
            var destFile = Path.Combine(websiteRootPath, httpChallenge.ResourcePath);
            var destPath = Path.GetDirectoryName(destFile);

            if (!Directory.Exists(destPath))
            {
                try
                {
                    Directory.CreateDirectory(destPath);
                }
                catch (Exception)
                {
                    // failed to create directory, probably permissions or may be invalid config
                    this.LogAction($"Pre-config check failed: Could not create directory: {destPath}");
                    return(() => { return false; });
                }
            }

            // copy challenge response to web folder /.well-known/acme-challenge. Check if it already
            // exists (as in 'configcheck' file) as can cause conflicts.
            if (!File.Exists(destFile))
            {
                try
                {
                    File.WriteAllText(destFile, httpChallenge.Value);
                }
                catch (Exception)
                {
                    // failed to create configcheck file, probably permissions or may be invalid config
                    this.LogAction($"Pre-config check failed: Could not create file: {destFile}");
                    return(() => { return false; });
                }
            }

            // configure cleanup - should this be configurable? Because in some case many sites
            // renewing may all point to the same web root, we keep the configcheck file
            pendingAuth.Cleanup = () =>
            {
                if (!destFile.EndsWith("configcheck") && File.Exists(destFile))
                {
                    File.Delete(destFile);
                }
            };

            // create a web.config for extensionless files, then test it (make a request for the
            // extensionless configcheck file over http)
            string webConfigContent = ConfigResources.IISWebConfig;

            if (!File.Exists(destPath + "\\web.config"))
            {
                // no existing config, attempt auto config and perform test
                this.LogAction($"Config does not exist, writing default config to: {destPath}\\web.config");
                System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent);
                return(() => _netUtil.CheckURL($"http://{domain}/{httpChallenge.ResourcePath}"));
            }
            else
            {
                // web config already exists, don't overwrite it, just test it
                return(() =>
                {
                    if (_netUtil.CheckURL(httpChallenge.ResourceUri))
                    {
                        return true;
                    }

                    if (requestConfig.PerformAutoConfig)
                    {
                        this.LogAction($"Pre-config check failed: Auto-config will overwrite existing config: {destPath}\\web.config");
                        // didn't work, try our default config
                        try
                        {
                            System.IO.File.WriteAllText(destPath + "\\web.config", webConfigContent);
                        }
                        catch (System.IO.IOException)
                        {
                            this.LogAction($"Failed to update alternative web config: {destPath}\\web.config");
                        }

                        if (_netUtil.CheckURL(httpChallenge.ResourceUri))
                        {
                            return true;
                        }
                    }
                    return false;
                });
            }
        }
Пример #15
0
        public CertifyManager(bool useWindowsNativeFeatures = true)
        {
            _useWindowsNativeFeatures = useWindowsNativeFeatures;

            _serverConfig = SharedUtils.ServiceConfigManager.GetAppServiceConfig();

            SettingsManager.LoadAppSettings();

            InitLogging(_serverConfig);

            Util.SetSupportedTLSVersions();
            try
            {
                _itemManager = new ItemManager(null, _serviceLog);

                if (!_itemManager.IsInitialised())
                {
                    _serviceLog.Error($"Item Manager failed to initialise properly. Check service logs for more information.");
                }
            }
            catch (Exception exp)
            {
                _serviceLog.Error($"Failed to open or upgrade the managed items database. Check service has required file access permissions. :: {exp}");
            }

            _credentialsManager = new CredentialsManager(useWindowsNativeFeatures);
            _serverProvider     = (ICertifiedServer) new ServerProviderIIS(_serviceLog);

            _progressResults = new ObservableCollection <RequestProgressState>();

            _pluginManager = new PluginManager();
            _pluginManager.EnableExternalPlugins = CoreAppSettings.Current.IncludeExternalPlugins;
            _pluginManager.LoadPlugins(new List <string> {
                "Licensing", "DashboardClient", "DeploymentTasks", "CertificateManagers", "DnsProviders"
            });

            _migrationManager = new MigrationManager(_itemManager, _credentialsManager, _serverProvider);

            LoadCertificateAuthorities();


            // init remaining utilities and optionally enable telematics
            _challengeDiagnostics = new ChallengeDiagnostics(CoreAppSettings.Current.EnableValidationProxyAPI);

            if (CoreAppSettings.Current.EnableAppTelematics)
            {
                _tc = new Util().InitTelemetry(Locales.ConfigResources.AIInstrumentationKey);
            }

            _httpChallengePort = _serverConfig.HttpChallengeServerPort;
            _httpChallengeServerClient.Timeout = new TimeSpan(0, 0, 20);

            if (_tc != null)
            {
                _tc.TrackEvent("ServiceStarted");
            }

            _serviceLog?.Information("Certify Manager Started");

            try
            {
                PerformAccountUpgrades().Wait();
            }
            catch (Exception exp)
            {
                _serviceLog.Error($"Failed to perform ACME account upgrades. :: {exp}");
            }

            PerformManagedCertificateMigrations().Wait();
        }
Пример #16
0
 public MigrationManager(IItemManager itemManager, ICredentialsManager credentialsManager, ICertifiedServer targetServer)
 {
     _itemManager        = itemManager;
     _credentialsManager = credentialsManager;
     _targetServer       = targetServer;
 }
Пример #17
0
 public PendingAuthorization PerformAutomatedChallengeResponse(ICertifiedServer iisManager, ManagedSite managedSite, PendingAuthorization pendingAuth)
 {
     throw new System.NotImplementedException();
 }