Пример #1
0
        /// <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"
                });
            }
        }
Пример #2
0
        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"
                });
            }
        }
Пример #3
0
        /// <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);
        }
Пример #5
0
        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();
            }
        }
Пример #6
0
        /// <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.");
            }
        }
Пример #8
0
        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);
        }
Пример #10
0
        /// <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"
                });
            }
        }
Пример #11
0
        /// <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);
            }
        }