Esempio n. 1
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,
            IProgress <RequestProgressState> progress = null
            )
        {
            var results = new List <StatusMessage>();

            var requestConfig = managedCertificate.RequestConfig;
            var result        = new StatusMessage {
                IsOK = true
            };
            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)
                {
                    bool 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);

                    if (challengeConfig.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  = ".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)
                        {
                            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 (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_SNI)
                    {
                        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 (challengeConfig.ChallengeType == SupportedChallengeTypes.CHALLENGE_TYPE_DNS)
                    {
                        var recordName = $"_acme-challenge-test.{domain}".Replace("*.", "");

                        if (challengeConfig.ChallengeProvider == Certify.Providers.DNS.AcmeDns.DnsProviderAcmeDns.Definition.Id)
                        {
                            // 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.Replace("*.", ""),
                                managedCertificate,
                                simulatedAuthorization
                                );

                        result.Message = dnsResult.Result.Message;
                        result.IsOK    = dnsResult.Result.IsSuccess;

                        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);
        }
Esempio n. 2
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;
            }));
        }