Esempio n. 1
0
        public async Task TestDNSTests()
        {
            var net = new NetworkUtils(enableProxyValidationAPI: true);

            var logImp = new LoggerConfiguration()
                         .WriteTo.Debug()
                         .CreateLogger();

            var log = new Loggy(logImp);

            // check invalid domain
            var result = await net.CheckDNS(log, "fdlsakdfoweinoijsjdfpsdkfspdf.com");

            Assert.IsFalse(result.All(r => r.IsSuccess), "Non-existant DNS does not throw an error");

            result = await net.CheckDNS(log, "cloudapp.net");

            Assert.IsFalse(result.All(r => r.IsSuccess), "Valid domain that does not resolve to an IP Address does not throw an error");

            // certifytheweb.com = no CAA records
            result = await net.CheckDNS(log, "webprofusion.com");

            Assert.IsTrue(result.All(r => r.IsSuccess), "CAA records are not required");

            // google.com = no letsencrypt.org CAA records (returns "pki.goog" only)
            result = await net.CheckDNS(log, "google.com");

            Assert.IsFalse(result.All(r => r.IsSuccess), "If CAA records are present, letsencrypt.org is returned.");

            // dnsimple.com = correctly configured letsencrypt.org CAA record
            result = await net.CheckDNS(log, "dnsimple.com");

            Assert.IsTrue(result.All(r => r.IsSuccess), "Correctly configured LE CAA entries work");

            // example.com = correctly configured DNSSEC record
            result = await net.CheckDNS(log, "example.com");

            Assert.IsTrue(result.All(r => r.IsSuccess), "correctly configured DNSSEC record should pass dns check");

            // dnssec-failed.org = incorrectly configured DNSSEC record
            result = await net.CheckDNS(log, "dnssec-failed.org");

            Assert.IsFalse(result.All(r => r.IsSuccess), "incorrectly configured DNSSEC record should fail dns check");
        }
Esempio n. 2
0
        public void TestDNSTests()
        {
            var net = new NetworkUtils();

            // check invalid domain
            Assert.IsFalse(net.CheckDNS("fdlsakdfoweinoijsjdfpsdkfspdf.com").Ok, "Non-existant DNS does not throw an error");

            Assert.IsFalse(net.CheckDNS("cloudapp.net").Ok, "Valid domain that does not resolve to an IP Address does not throw an error");

            // certifytheweb.com = no CAA records
            Assert.IsTrue(net.CheckDNS("certifytheweb.com").Ok, "CAA records are not required");

            // google.com = no letsencrypt.org CAA records (returns "pki.goog" only)
            Assert.IsFalse(net.CheckDNS("google.com").Ok, "If CAA records are present, letsencrypt.org is returned.");

            // dnsimple.com = correctly configured letsencrypt.org CAA record
            Assert.IsTrue(net.CheckDNS("dnsimple.com").Ok, "Correctly configured LE CAA entries work");

            // example.com = correctly configured DNSSEC record
            Assert.IsTrue(net.CheckDNS("example.com").Ok, "correctly configured DNSSEC record should pass dns check");

            // dnssec-failed.org = incorrectly configured DNSSEC record
            Assert.IsFalse(net.CheckDNS("dnssec-failed.org").Ok, "incorrectly configured DNSSEC record should fail dns check");
        }
Esempio n. 3
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);
        }
Esempio n. 4
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 <APIResult> TestChallengeResponse(IISManager iisManager, ManagedSite managedSite, bool isPreviewMode)
        {
            return(await Task.Run(() =>
            {
                ActionLogs.Clear(); // reset action logs

                var requestConfig = managedSite.RequestConfig;
                var result = new APIResult();
                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 && CoreAppSettings.Current.EnableDNSValidationChecks)
                    {
                        // 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 == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP)
                    {
                        foreach (var domain in domains.Distinct())
                        {
                            string challengeFileUrl = $"http://{domain}/.well-known/acme-challenge/configcheck";

                            var simulatedAuthorization = new PendingAuthorization
                            {
                                Challenge = new AuthorizeChallengeItem
                                {
                                    ChallengeData = new ACMESharp.ACME.HttpChallenge(ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP,
                                                                                     new ACMESharp.ACME.HttpChallengeAnswer {
                                        KeyAuthorization = GenerateSimulatedKeyAuth()
                                    })
                                    {
                                        FilePath = ".well-known/acme-challenge/configcheck",
                                        FileContent = "Extensionless File Config Test - OK",
                                        FileUrl = challengeFileUrl
                                    }
                                }
                            };

                            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}");
                            }
                        }
                    }
                    else if (requestConfig.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI)
                    {
                        if (iisManager.GetIisVersion().Major < 8)
                        {
                            result.IsOK = false;
                            result.FailedItemSummary.Add($"The {ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI} challenge is only available for IIS versions 8+.");
                            return result;
                        }

                        result.IsOK = domains.Distinct().All(domain =>
                        {
                            var simulatedAuthorization = new PendingAuthorization
                            {
                                Challenge = new AuthorizeChallengeItem()
                                {
                                    ChallengeData = new ACMESharp.ACME.TlsSniChallenge(ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI,
                                                                                       new ACMESharp.ACME.TlsSniChallengeAnswer {
                                        KeyAuthorization = GenerateSimulatedKeyAuth()
                                    })
                                    {
                                        IterationCount = 1
                                    }
                                }
                            };
                            generatedAuthorizations.Add(simulatedAuthorization);
                            return PrepareChallengeResponse_TlsSni01(
                                iisManager, 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;
            }));
        }
Esempio n. 5
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;
            }));
        }