/// <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; })); }
/// <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; })); }