/// <summary> /// if not already validate, ask ACME CA to check we have answered the nominated challenges correctly /// </summary> /// <param name="log"> </param> /// <param name="challengeType"> </param> /// <param name="attemptedChallenge"> </param> /// <returns> </returns> public async Task <StatusMessage> SubmitChallenge(ILog log, string challengeType, AuthorizationChallengeItem attemptedChallenge) { if (!attemptedChallenge.IsValidated) { IChallengeContext challenge = (IChallengeContext)attemptedChallenge.ChallengeData; try { Challenge result = await challenge.Validate(); int attempts = 10; while (attempts > 0 && result.Status == ChallengeStatus.Pending || result.Status == ChallengeStatus.Processing) { result = await challenge.Resource(); } if (result.Status == ChallengeStatus.Valid) { return(new StatusMessage { IsOK = true, Message = "Submitted" }); } else { var challengeError = await challenge.Resource(); return(new StatusMessage { IsOK = false, Message = challengeError.Error?.Detail }); } } catch (AcmeRequestException exp) { var msg = $"Submit Challenge failed: {exp.Error?.Detail}"; log.Error(msg); return(new StatusMessage { IsOK = false, Message = msg }); } } else { return(new StatusMessage { IsOK = true, Message = "Validated" }); } }
public async Task <StatusMessage> SubmitChallenge(string domainIdentifierId, string challengeType, AuthorizationChallengeItem attemptedChallenge) { // if not already validate, ask ACME server to validate we have answered the required // challenge correctly if (!attemptedChallenge.IsValidated) { IChallengeContext challenge = (IChallengeContext)attemptedChallenge.ChallengeData; try { var result = await challenge.Validate(); if (result.Status == ChallengeStatus.Valid || result.Status == ChallengeStatus.Pending) { return(new StatusMessage { IsOK = true, Message = "Submitted" }); } else { var challengeError = await challenge.Resource(); return(new StatusMessage { IsOK = false, Message = challengeError.ToString() }); } } catch (Exception exp) { LogAction("SubmitChallenge failed. ", exp.Message); var challengeError = await challenge.Resource(); return(new StatusMessage { IsOK = false, Message = challengeError.ToString() }); } } else { return(new StatusMessage { IsOK = true, Message = "Validated" }); } }
/// <summary> /// Validates a DNS challenge. Similar to HTTP Validation, but different because of DNSChallenge value which is signed by account key /// </summary> /// <param name="dnsChallenge"></param> /// <returns></returns> private async Task <bool> ValidateDNSChallenge(String domain, IChallengeContext dnsChallenge, IDNSChallengeValidator dnsChallengeValidator) { if (dnsChallenge == null) { throw new Exception("DNS Validation mode setup, but server returned no DNS challenge."); } // We get the resource fresh var dnsChallengeStatus = await dnsChallenge.Resource(); // If it's invalid, we stop right away. Should not happen, but anyway... if (dnsChallengeStatus.Status == ChallengeStatus.Invalid) { throw new Exception("DNS challenge has an invalid status"); } // Let's prepare for ACME-DNS validation var dnsValue = _acme.AccountKey.DnsTxt(dnsChallenge.Token); var dnsKey = $"_acme-challenge.{domain}".Replace("*.", ""); if (!dnsChallengeValidator.PrepareChallengeForValidation(dnsKey, dnsValue)) { return(false); } // We sleep 5 seconds gefore first check, in order to leave the time to DNS to propagate System.Threading.Thread.Sleep(5000); // Now let's ping the ACME service to validate the challenge token Challenge challengeRes = await dnsChallenge.Validate(); // We need to loop, because ACME service might need some time to validate the challenge token int retry = 0; while (((challengeRes.Status == ChallengeStatus.Pending) || (challengeRes.Status == ChallengeStatus.Processing)) && (retry < 10)) { // We sleep 2 seconds between each request, to leave time to ACME service to refresh System.Threading.Thread.Sleep(2000); // We refresh the challenge object from ACME service challengeRes = await dnsChallenge.Resource(); retry++; } // If challenge is Invalid, Pending or Processing, something went wrong... if (challengeRes.Status != ChallengeStatus.Valid) { return(false); } return(true); }
/// <summary> /// 执行HTTP-01验证 /// </summary> /// <param name="authz">授权上下文</param> /// <param name="challengeTokenPath">http-01验证令牌文件存放路径</param> /// <returns></returns> public static async Task <ChallengeStatus?> HttpChallengeValidateAsync(IAuthorizationContext authz, string challengeTokenPath = "") { IChallengeContext httpChallenge = await authz.Http(); var keyAuthz = httpChallenge.KeyAuthz; File.WriteAllText(Path.Combine(challengeTokenPath, httpChallenge.Token), keyAuthz); /* * 向ACME服务器发送申请/请求,以验证域名所有权 * 注意,此处需要延迟轮询等等,具体可以参考GITHUB的几个issues * 需要延迟轮询:https://github.com/fszlin/certes/issues/194 * 如何获取当前验证状态:https://github.com/fszlin/certes/issues/89 */ Challenge challengeResult = await httpChallenge.Validate(); //等待服务器验证结果,每隔3秒轮询一次,最多轮询10次 var attempts = 10; while (attempts > 0 && (challengeResult.Status == ChallengeStatus.Pending || challengeResult.Status == ChallengeStatus.Processing)) { await Task.Delay(3000); challengeResult = await httpChallenge.Resource(); attempts--; } return(challengeResult.Status); }
public async Task ValidateDnsAuthorizationAsync() { var challengeResult = await challengeContext.Validate(); int numberOfRetries = 0; while (challengeResult.Status.HasValue && challengeResult.Status.Value == Certes.Acme.Resource.ChallengeStatus.Pending && numberOfRetries <= maxNumberOfRetries) { log.LogInformation($"Validation is pending. Will retry in 1 second. Number of retries - {numberOfRetries}"); await Task.Delay(1 * 1000); challengeResult = await challengeContext.Resource(); numberOfRetries += 1; } if (numberOfRetries >= maxNumberOfRetries) { throw new ChallengeValidationFailedException(); } if (!challengeResult.Status.HasValue || challengeResult.Status.Value != Certes.Acme.Resource.ChallengeStatus.Valid) { log.LogError("Unable to validate challenge - {0} - {1}", challengeResult.Error.Detail, string.Join('~', challengeResult.Error.Subproblems.Select(x => x.Detail))); throw new ChallengeValidationFailedException(); } }
/// <summary> /// Small method that validates one challenge using the specified validator /// </summary> /// <param name="httpChallenge"></param> /// <param name="challengeValidator"></param> /// <returns>true if validated, false otherwise</returns> private async Task <bool> ValidateChallenge(IChallengeContext httpChallenge, IHTTPChallengeValidator challengeValidator) { // We get the resource fresh var httpChallengeStatus = await httpChallenge.Resource(); // If it's invalid, we stop right away. Should not happen, but anyway... if (httpChallengeStatus.Status == ChallengeStatus.Invalid) { throw new Exception("HTTP challenge has an invalid status"); } // Else we start the challenge validation if (!challengeValidator.PrepareChallengeForValidation(httpChallenge.Token, httpChallenge.KeyAuthz)) { return(false); } // Now let's ping the ACME service to validate the challenge token Challenge challengeRes = await httpChallenge.Validate(); // We need to loop, because ACME service might need some time to validate the challenge token int retry = 0; while (((challengeRes.Status == ChallengeStatus.Pending) || (challengeRes.Status == ChallengeStatus.Processing)) && (retry < 10)) { // We sleep 2 seconds between each request, to leave time to ACME service to refresh System.Threading.Thread.Sleep(2000); // We refresh the challenge object from ACME service challengeRes = await httpChallenge.Resource(); retry++; } // Finally we cleanup everything that was needed for validation challengeValidator.CleanupChallengeAfterValidation(httpChallenge.Token); // If challenge is Invalid, Pending or Processing, something went wrong... if (challengeRes.Status != ChallengeStatus.Valid) { return(false); } return(true); }
public static async Task WaitForCompletion(this IChallengeContext challengeContext, TimeSpan pollInterval, ILogger logger = null) { // Get the challenges ressource to check if it's valid var challenge = await challengeContext.Resource(); while (!challenge.HasFinished()) { // If nor finished processing, poll every second challenge = await challengeContext.Resource(); await Task.Delay(pollInterval); } logger?.LogDebug("Http challenge finished with status {status}", challenge.Status?.ToString() ?? "[null]"); if (challenge.Status == ChallengeStatus.Invalid) { // Throw if invalid new AcmeException(challenge.Error?.Detail ?? "ACME http challenge not successful."); } }
async Task <IChalageStatus> ValidateChalage(IChallengeContext challengeCtx) { // Now let's ping the ACME service to validate the challenge token try { Challenge challenge = await challengeCtx.Validate( ); if (challenge.Status == ChallengeStatus.Invalid) { _logger.Write("Error occured while validating acme challenge to {0} :: error==>{1}", domain.ZoneName, challenge.Error.Detail); return(new ChalageStatus { status = false, errorDescription = challenge.Error.Detail }); } // We need to loop, because ACME service might need some time to validate the challenge token int retry = 0; while (((challenge.Status == ChallengeStatus.Pending) || (challenge.Status == ChallengeStatus.Processing)) && (retry < 30)) { // We sleep 2 seconds between each request, to leave time to ACME service to refresh Thread.Sleep(2000); // We refresh the challenge object from ACME service challenge = await challengeCtx.Resource( ); retry++; } if (challenge.Status == ChallengeStatus.Invalid) { _logger.Write("Error occured while validating acme challenge to {0} :: error==>{1}", domain.ZoneName, challenge.Error.Detail); return(new ChalageStatus { status = false, errorDescription = challenge.Error.Detail }); } return(new ChalageStatus { status = true }); } catch (Exception e) { _logger.Write("Error occured while validating acme challenge to {0} :: error==>{1}", domain.ZoneName, e.Message); _logger.Write(e.StackTrace); return(new ChalageStatus { status = false, errorDescription = e.Message }); } }
public static void GetFirstCert(bool staging = true) { ContextAccountBundle bundle; if (staging) { bundle = GetStagingParameters(); } else { bundle = GetNonStagingParameters(); } var account = bundle.Account; var ctx = bundle.Ctx; IOrderListContext orders = account.Orders().Result; List <IOrderContext> orderContexts = new List <IOrderContext>(orders.Orders().Result); IOrderContext currentOrder = null; if (orderContexts.Count == 0) { currentOrder = ctx.NewOrder(new[] { "jcf-ai.com" }).Result; } else { foreach (IOrderContext orderCtx in orderContexts) { if (orderCtx.Resource().Result.Status != OrderStatus.Valid) { currentOrder = orderCtx; break; } } if (currentOrder == null) { currentOrder = ctx.NewOrder(new[] { "jcf-ai.com" }).Result; } } Order order = currentOrder.Resource().Result; var authorizationList = currentOrder.Authorizations().Result; IAuthorizationContext currentAuthContext; IEnumerator <IAuthorizationContext> enumerator = authorizationList.GetEnumerator(); while (enumerator.Current == null) { enumerator.MoveNext(); } currentAuthContext = enumerator.Current; Authorization authResource = currentAuthContext.Resource().Result; IChallengeContext httpContext = currentAuthContext.Http().Result; if (httpContext.Resource().Result.Status != ChallengeStatus.Valid) { if (!System.IO.Directory.Exists("tokens")) { System.IO.Directory.CreateDirectory("tokens"); } StreamWriter writer = new StreamWriter("tokens/" + httpContext.Token); writer.Write(httpContext.KeyAuthz); writer.Close(); Challenge httpChallenge = httpContext.Validate().Result; } //Everything should be good to go on the whole validate everything front. IKey privateKey = KeyFactory.NewKey(KeyAlgorithm.ES384); var cert = currentOrder.Generate( new CsrInfo() { CountryName = "US", State = "Washington", Locality = "Tacoma", CommonName = "jcf-ai.com" }, privateKey).Result; byte[] pfx = cert.ToPfx(privateKey).Build("jcf-ai.com", "pass"); FileStream pfxOutStream = new FileStream("jcf-ai.pfx", FileMode.Create, FileAccess.Write); pfxOutStream.Write(pfx, 0, pfx.Length); pfxOutStream.Close(); ProcessStartInfo psi = new ProcessStartInfo("powershell", "-Command ./bindcert.ps1 jcf-ai.pfx pass"); psi.CreateNoWindow = true; Process.Start(psi); }
/// <summary> /// if not already validate, ask ACME CA to check we have answered the nominated challenges correctly /// </summary> /// <param name="log"> </param> /// <param name="challengeType"> </param> /// <param name="attemptedChallenge"> </param> /// <returns> </returns> public async Task <StatusMessage> SubmitChallenge(ILog log, string challengeType, AuthorizationChallengeItem attemptedChallenge) { if (attemptedChallenge == null) { return(new StatusMessage { IsOK = false, Message = "Challenge could not be submitted. No matching attempted challenge." }); } if (!attemptedChallenge.IsValidated) { try { await _acme.HttpClient.ConsumeNonce(); } catch (Exception) { return(new StatusMessage { IsOK = false, Message = "Failed to resume communication with Certificate Authority API. Try again later." }); } IChallengeContext challenge = (IChallengeContext)attemptedChallenge.ChallengeData; try { var result = await challenge.Validate(); var attempts = 10; while (attempts > 0 && result.Status == ChallengeStatus.Pending || result.Status == ChallengeStatus.Processing) { result = await challenge.Resource(); await Task.Delay(500); } if (result.Status == ChallengeStatus.Valid) { return(new StatusMessage { IsOK = true, Message = "Submitted" }); } else { var challengeError = await challenge.Resource(); return(new StatusMessage { IsOK = false, Message = challengeError.Error?.Detail }); } } catch (AcmeRequestException exp) { var msg = $"Submit Challenge failed: {exp.Error?.Detail}"; log.Error(msg); return(new StatusMessage { IsOK = false, Message = msg }); } } else { return(new StatusMessage { IsOK = true, Message = "Validated" }); } }
/// <exception cref="LetsEncryptMikroTikException"/> private async Task ChallengeAsync(IChallenge challenge, IChallengeContext httpChallenge) { // Запустить asp net. Log.Information("Запускаем веб-сервер."); challenge.Start(); string mtNatId = MtAddDstNatRule(dstPort: challenge.PublicPort, toPorts: challenge.ListenPort); string mtFilterId = MtAllowPortFilter(dstPort: challenge.ListenPort, publicPort: challenge.PublicPort); string mtMangleId = MtAllowMangleRule(dstPort: challenge.ListenPort); // Правило в микротике начинает работать не мгновенно. await Task.Delay(2000).ConfigureAwait(false); //Challenge status; try { // Ask the ACME server to validate our domain ownership. Log.Information("Информируем Let's Encrypt что мы готовы пройти валидацию."); Challenge?status = await httpChallenge.Validate().ConfigureAwait(false); bool waitWithSem = true; while (status.Status == ChallengeStatus.Pending) { if (waitWithSem) { Log.Information("Ожидаем 20 сек. входящий HTTP запрос."); Task t = await Task.WhenAny(Task.Delay(20_000), challenge.Completion).ConfigureAwait(false); if (t == challenge.Completion) { waitWithSem = false; Log.Information("Успешно выполнили входящий запрос. Ждём 15 сек. перед запросом сертификата."); await Task.Delay(15_000).ConfigureAwait(false); } else // Таймаут. { waitWithSem = false; Log.Information("Запрос ещё не поступил. Дополнительно ожидаем ещё 5 сек."); await Task.Delay(5000).ConfigureAwait(false); } } else { Log.Information("Заказ всё ещё в статусе Pending. Делаем дополнительную паузу на 5 сек."); await Task.Delay(5_000).ConfigureAwait(false); } Log.Information("Запрашиваем статус нашего заказа."); status = await httpChallenge.Resource().ConfigureAwait(false); } if (status.Status == ChallengeStatus.Valid) { Log.Information("Статус заказа: Valid. Загружаем сертификат."); } else { Log.Error($"Статус заказа: {status.Status}"); throw new LetsEncryptMikroTikException($"Статус заказа: {status.Status}. Ошибка: {status.Error.Detail}"); } } finally { // Возвращаем NAT ClosePort(mtFilterId); RemoveNatRule(mtNatId); RemoveMangleRule(mtMangleId); } }