public async Task Run(IAcmeDnsRequest acmeDnsRequest, int renewXNumberOfDaysBeforeExpiration)
        {
            try
            {
                CertificateInstallModel model = null;

                string hostsPlusSeparated = AcmeClient.GetHostsPlusSeparated(acmeDnsRequest.Hosts);
                var    certname           = $"{hostsPlusSeparated}-{acmeDnsRequest.AcmeEnvironment.Name}";
                var    cert = await certificateStore.GetCertificate(certname, acmeDnsRequest.PFXPassword);

                if (cert == null || cert.Certificate.NotAfter < DateTime.UtcNow.AddDays(renewXNumberOfDaysBeforeExpiration)) //Cert doesnt exist or expires in less than renewXNumberOfDaysBeforeExpiration days, lets renew.
                {
                    logger.LogInformation("Certificate store didn't contain certificate or certificate was expired starting renewing");
                    model = await acmeClient.RequestDnsChallengeCertificate(acmeDnsRequest);

                    model.CertificateInfo.Name = certname;
                    await certificateStore.SaveCertificate(model.CertificateInfo);
                }
                else
                {
                    logger.LogInformation("Certificate expires in more than {renewXNumberOfDaysBeforeExpiration} days, reusing certificate from certificate store", renewXNumberOfDaysBeforeExpiration);
                    model = new CertificateInstallModel()
                    {
                        CertificateInfo = cert,
                        Hosts           = acmeDnsRequest.Hosts
                    };
                }
                await certificateConsumer.Install(model);

                logger.LogInformation("Removing expired certificates");
                var expired = await certificateConsumer.CleanUp();

                logger.LogInformation("The following certificates was removed {Thumbprints}", string.Join(", ", expired.ToArray()));
            }
            catch (Exception e)
            {
                logger.LogError(e, "Failed");
                throw;
            }
        }
Example #2
0
        /// <summary>
        /// Request a certificate from lets encrypt using the DNS challenge, placing the challenge record in Azure DNS.
        /// The certifiacte is not assigned, but just returned.
        /// </summary>
        /// <param name="azureDnsEnvironment"></param>
        /// <param name="acmeConfig"></param>
        /// <returns></returns>
        public async Task <CertificateInstallModel> RequestDnsChallengeCertificate(IAcmeDnsRequest acmeConfig)
        {
            logger.LogInformation("Starting request DNS Challenge certificate for {AcmeEnvironment} and {Email}", acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail);
            var acmeContext = await GetOrCreateAcmeContext(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail);

            var idn = new IdnMapping();

            var order = await acmeContext.NewOrder(new[] { "*." + idn.GetAscii(acmeConfig.Host.Substring(2)) });

            var a = await order.Authorizations();

            var authz     = a.First();
            var challenge = await authz.Dns();

            var dnsTxt = acmeContext.AccountKey.DnsTxt(challenge.Token);

            logger.LogInformation("Got DNS challenge token {Token}", dnsTxt);

            ///add dns entry
            await this.dnsProvider.PersistChallenge(String.Concat("_acme-challenge.", DnsLookupService.GetNoneWildcardSubdomain(acmeConfig.Host)), dnsTxt);

            if (!(await this.dnsLookupService.Exists(acmeConfig.Host, dnsTxt, this.dnsProvider.MinimumTtl)))
            {
                throw new TimeoutException($"Unable to validate that _acme-challenge was stored in txt _acme-challenge record after {this.dnsProvider.MinimumTtl} seconds");
            }


            Challenge chalResp = await challenge.Validate();

            while (chalResp.Status == ChallengeStatus.Pending || chalResp.Status == ChallengeStatus.Processing)
            {
                logger.LogInformation("Dns challenge response status {ChallengeStatus} more info at {ChallengeStatusUrl} retrying in 5 sec", chalResp.Status, chalResp.Url.ToString());
                await Task.Delay(5000);

                chalResp = await challenge.Resource();
            }

            logger.LogInformation("Finished validating dns challenge token, response was {ChallengeStatus} more info at {ChallengeStatusUrl}", chalResp.Status, chalResp.Url);

            var privateKey = await GetOrCreateKey(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.Host);

            var cert = await order.Generate(new Certes.CsrInfo
            {
                CountryName      = acmeConfig.CsrInfo.CountryName,
                State            = acmeConfig.CsrInfo.State,
                Locality         = acmeConfig.CsrInfo.Locality,
                Organization     = acmeConfig.CsrInfo.Organization,
                OrganizationUnit = acmeConfig.CsrInfo.OrganizationUnit,
                CommonName       = acmeConfig.CsrInfo.CommonName,
            }, privateKey);

            var certPem = cert.ToPem();

            var pfxBuilder = cert.ToPfx(privateKey);
            var pfx        = pfxBuilder.Build(acmeConfig.Host, acmeConfig.PFXPassword);

            await this.dnsProvider.Cleanup(dnsTxt);

            return(new CertificateInstallModel()
            {
                CertificateInfo = new CertificateInfo()
                {
                    Certificate = new X509Certificate2(pfx, acmeConfig.PFXPassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable),
                    Name = $"{acmeConfig.Host} {DateTime.Now}",
                    Password = acmeConfig.PFXPassword,
                    PfxCertificate = pfx
                },
                Host = acmeConfig.Host
            });
        }
Example #3
0
        /// <summary>
        /// Request a certificate from lets encrypt using the DNS challenge, placing the challenge record in Azure DNS.
        /// The certifiacte is not assigned, but just returned.
        /// </summary>
        /// <param name="azureDnsEnvironment"></param>
        /// <param name="acmeConfig"></param>
        /// <returns></returns>
        public async Task <CertificateInstallModel> RequestDnsChallengeCertificate(IAcmeDnsRequest acmeConfig)
        {
            logger.LogInformation("Starting request DNS Challenge certificate for {AcmeEnvironment} and {Email}", acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail);
            var acmeContext = await GetOrCreateAcmeContext(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.RegistrationEmail);

            var idn = new IdnMapping();

            var orderHosts = (from host in acmeConfig.Hosts
                              let asciiHost = idn.GetAscii(host)
                                              let asciiDomain = asciiHost.StartsWith("*.")
                                              ? asciiHost.Substring(2)
                                              : asciiHost
                                                                select(Host: asciiHost, Domain: asciiDomain))
                             .ToImmutableArray();
            var order = await acmeContext.NewOrder(orderHosts.Select(h => h.Host).ToImmutableArray());

            var authorizations = (await order.Authorizations()).ToImmutableArray();
            var tasks          = new List <Task>(authorizations.Length);
            var dnsTxts        = new List <string>(authorizations.Length);

            // TODO: Consider parallelizing
            for (var i = 0; i < authorizations.Length; i++) /*tasks.Add(Task.Factory.StartNew(async state =>*/
            {
                //var (authorization, host) = (Tuple<IAuthorizationContext, string>)state;
                var(authorization, zoneName) = (authorizations[i], orderHosts[i].Domain);
                var challenge = await authorization.Dns();

                var dnsTxt = acmeContext.AccountKey.DnsTxt(challenge.Token);
                logger.LogInformation("Got DNS challenge token {Token}", dnsTxt);

                ///add dns entry
                await this.dnsProvider.PersistChallenge(zoneName, "_acme-challenge", dnsTxt);

                await Task.Delay(500);

                if (!(await this.dnsLookupService.Exists(zoneName, dnsTxt, this.dnsProvider.MinimumTtl)))
                {
                    throw new TimeoutException($"Unable to validate that _acme-challenge was stored in txt _acme-challenge record after {this.dnsProvider.MinimumTtl} seconds");
                }

                Challenge chalResp = await challenge.Validate();

                while (chalResp.Status == ChallengeStatus.Pending || chalResp.Status == ChallengeStatus.Processing)
                {
                    logger.LogInformation("Dns challenge response status {ChallengeStatus} more info at {ChallengeStatusUrl} retrying in 5 sec", chalResp.Status, chalResp.Url.ToString());
                    await Task.Delay(5000);

                    chalResp = await challenge.Resource();
                }

                logger.LogInformation("Finished validating dns challenge token, response was {ChallengeStatus} more info at {ChallengeStatusUrl}", chalResp.Status, chalResp.Url);
            }/*, Tuple.Create(authorizations[i], orderHosts[i].BaseHost), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default).Unwrap());
              * await Task.WhenAll(tasks);
              * tasks.Clear()*/
            ;

            var privateKey = await GetOrCreateKey(acmeConfig.AcmeEnvironment.BaseUri, acmeConfig.Hosts);

            var cert = await order.Generate(new Certes.CsrInfo
            {
                CountryName      = acmeConfig.CsrInfo.CountryName,
                State            = acmeConfig.CsrInfo.State,
                Locality         = acmeConfig.CsrInfo.Locality,
                Organization     = acmeConfig.CsrInfo.Organization,
                OrganizationUnit = acmeConfig.CsrInfo.OrganizationUnit,
                CommonName       = acmeConfig.CsrInfo.CommonName,
            }, privateKey);

            var certPem = cert.ToPem();

            string hostsPlusSeparated = GetHostsPlusSeparated(acmeConfig.Hosts);
            var    pfxBuilder         = cert.ToPfx(privateKey);
            var    pfx = pfxBuilder.Build(hostsPlusSeparated, acmeConfig.PFXPassword);

            for (var i = 0; i < dnsTxts.Count; i++)
            {
                tasks.Add(this.dnsProvider.Cleanup(orderHosts[i].Domain, dnsTxts[i]));
            }
            await Task.WhenAll(tasks);

            tasks.Clear();

            return(new CertificateInstallModel()
            {
                CertificateInfo = new CertificateInfo()
                {
#pragma warning disable DF0100 // Marks return values that hides the IDisposable implementation of return value.
                    Certificate = new X509Certificate2(pfx, acmeConfig.PFXPassword, X509KeyStorageFlags.DefaultKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable),
#pragma warning restore DF0100 // Marks return values that hides the IDisposable implementation of return value.
                    Name = $"{acmeConfig.Hosts} {DateTime.Now}",
                    Password = acmeConfig.PFXPassword,
                    PfxCertificate = pfx
                },
                Hosts = acmeConfig.Hosts
            });
        }