private static async Task <bool> CheckCertAddition( RenewalParameters renewalParams, AzureWebAppEnvironment webAppEnvironment, AcmeConfig acmeConfig, bool staging) { if (renewalParams.RenewXNumberOfDaysBeforeExpiration <= 0) { return(true); } var letsEncryptHostNames = await CertificateHelper.GetLetsEncryptHostNames(webAppEnvironment, staging).ConfigureAwait(false); Trace.TraceInformation("Let's Encrypt host names (staging: {0}): {1}", staging, String.Join(", ", letsEncryptHostNames)); ICollection <string> missingHostNames = acmeConfig.Hostnames.Except(letsEncryptHostNames, StringComparer.OrdinalIgnoreCase).ToArray(); if (missingHostNames.Count > 0) { Trace.TraceInformation( "Detected host name(s) with no associated Let's Encrypt certificates, will add a new certificate: {0}", String.Join(", ", missingHostNames)); return(true); } Trace.TraceInformation("All host names associated with Let's Encrypt certificates, will perform cert renewal"); return(false); }
private async Task <bool> HandleDns(AcmeContext acme, AcmeConfig config, IEnumerable <IChallengeContext> authorizations, List <INotifyConfig> notificationsList) { bool isValid = true; Dictionary <string, string> dnsValidation = new Dictionary <string, string>(); var index = -1; foreach (var challenge in authorizations) { index++; var domainName = config.DNS.DomainNames[index].Replace("*.", ""); var acmeDomain = "_acme-challenge." + domainName; var dnsText = acme.AccountKey.DnsTxt(challenge.Token); dnsValidation.Add(acmeDomain, dnsText); _log.LogInfo($"Add TXT dns for {acmeDomain} to '{dnsText}'"); } await AwaitDnsChanges(dnsValidation, notificationsList); Task.WaitAll(authorizations.Select(c => Task.Run(async() => { var validation = await c.Validate(); do { if (validation.Status == Certes.Acme.Resource.ChallengeStatus.Pending) { System.Threading.Thread.Sleep(2000); validation = await c.Resource(); } } while (validation.Status == Certes.Acme.Resource.ChallengeStatus.Pending); isValid = isValid && (validation.Status != Certes.Acme.Resource.ChallengeStatus.Invalid); })).ToArray()); return(isValid); }
public async Task <List <CertificateInstallModel> > RenewCertificate(bool skipInstallCertificate = false, int renewXNumberOfDaysBeforeExpiration = 0) { Trace.TraceInformation("Checking certificate"); var ss = SettingsStore.Instance.Load(); using (var client = await ArmHelper.GetWebSiteManagementClient(settings)) using (var httpClient = await ArmHelper.GetHttpClient(settings)) { var retryPolicy = ArmHelper.ExponentialBackoff(); var body = string.Empty; //Cant just get certificates by resource group, because sites that have been moved, have their certs sitting in the old RG. //Also cant use client.Certificates.List() due to bug in the nuget var response = await retryPolicy.ExecuteAsync(async() => { return(await httpClient.GetAsync($"/subscriptions/{settings.SubscriptionId}/providers/Microsoft.Web/certificates?api-version=2016-03-01")); }); response.EnsureSuccessStatusCode(); body = await response.Content.ReadAsStringAsync(); IEnumerable <Certificate> certs = ExtractCertificates(body); var expiringCerts = certs.Where(s => s.ExpirationDate < DateTime.UtcNow.AddDays(renewXNumberOfDaysBeforeExpiration) && (s.Issuer.Contains("Let's Encrypt") || s.Issuer.Contains("Fake LE"))); if (expiringCerts.Count() == 0) { Trace.TraceInformation(string.Format("No certificates installed issued by Let's Encrypt that are about to expire within the next {0} days. Skipping.", renewXNumberOfDaysBeforeExpiration)); } var res = new List <CertificateInstallModel>(); foreach (var toExpireCert in expiringCerts) { Trace.TraceInformation("Starting renew of certificate " + toExpireCert.Name + " expiration date " + toExpireCert.ExpirationDate); var site = client.WebApps.GetSiteOrSlot(settings.ResourceGroupName, settings.WebAppName, settings.SiteSlotName); var sslStates = site.HostNameSslStates.Where(s => s.Thumbprint == toExpireCert.Thumbprint); if (!sslStates.Any()) { Trace.TraceInformation(String.Format("Certificate {0} was not assigned any hostname, skipping update", toExpireCert.Thumbprint)); continue; } var target = new AcmeConfig() { RegistrationEmail = this.acmeConfig.RegistrationEmail ?? ss.FirstOrDefault(s => s.Name == "email").Value, Host = sslStates.First().Name, BaseUri = this.acmeConfig.BaseUri, UseProduction = !bool.Parse(ss.FirstOrDefault(s => s.Name == "useStaging")?.Value ?? false.ToString()), AlternateNames = sslStates.Skip(1).Select(s => s.Name).ToList(), PFXPassword = this.acmeConfig.PFXPassword, RSAKeyLength = this.acmeConfig.RSAKeyLength }; if (!skipInstallCertificate) { res.Add(await RequestAndInstallInternalAsync(target)); } } return(res); } }
public async Task <AcmeContext> CreateContext(AcmeConfig config) { try { return(await Login(config)); } catch (Exception ex) { throw; } }
public async Task <bool> Validate(AcmeContext acme, AcmeConfig config, List <INotifyConfig> notificationsList, Func <string[], Task <IOrderContext> > getOrder) { _log.LogInfo("Acme: Validating DNS order"); var order = await getOrder(config.DNS.DomainNames); _log.LogInfo("Acme: getting authorizations"); var authz = await order.Authorizations(); var authorizations = authz.Select(a => a.Dns().Result); return(await HandleDns(acme, config, authorizations, notificationsList)); }
private async Task <AcmeContext> Login(AcmeConfig config) { _log.LogInfo("Logging in to Acme"); AcmeContext acme = default(AcmeContext); if (string.IsNullOrWhiteSpace(config.AccountKey) || !IsKeyValid(config.AccountKey)) { acme = new AcmeContext(WellKnownServers.LetsEncryptV2); await acme.NewAccount(config.Email, true); var accountKey = acme.AccountKey.ToPem(); _log.LogWarning($"New accountkey for '{config.Email}':"); _log.LogWarning(accountKey); } else { string accountKey = File.ReadAllText(config.AccountKey); var key = KeyFactory.FromPem(accountKey); acme = new AcmeContext(WellKnownServers.LetsEncryptV2, key); await acme.Account(); } return(acme); }
public async Task <ActionResult> Install(RequestAndInstallModel model) { if (ModelState.IsValid) { var s = SettingsStore.Instance.Load(); s.Clear(); s.Add(new SettingEntry() { Name = "email", Value = model.Email }); var baseUri = model.UseStaging == false ? "https://acme-v01.api.letsencrypt.org/" : "https://acme-staging.api.letsencrypt.org/"; s.Add(new SettingEntry() { Name = "baseUri", Value = baseUri }); SettingsStore.Instance.Save(s); var settings = new AppSettingsAuthConfig(); var target = new AcmeConfig() { RegistrationEmail = model.Email, Host = model.Hostnames.First(), BaseUri = baseUri, AlternateNames = model.Hostnames.Skip(1).ToList(), PFXPassword = settings.PFXPassword, RSAKeyLength = settings.RSAKeyLength, }; var thumbprint = await new CertificateManager(settings).RequestAndInstallInternalAsync(target); if (thumbprint != null) { return(RedirectToAction("Hostname", new { id = thumbprint })); } } SetViewBagHostnames(); return(View(model)); }
internal async Task <bool> Renew(AcmeConfig config, List <INotifyConfig> notificationsList) { Model.Config = config; Model.Notifications = notificationsList; _log.LogInfo("Renewing certificate"); var acme = _contextFactory(config); IOrderContext order = default(IOrderContext); bool validated = await _validators[config.Validation].Validate(acme, config, notificationsList, async(domainNames) => { _log.LogInfo("Acme: getting order"); order = await acme.NewOrder(domainNames); return(order); }); if (validated) { var privateKey = KeyFactory.NewKey(KeyAlgorithm.ES256); var cert = await order.Generate(new CsrInfo { CountryName = "BE", State = "Antwerp", Locality = "Belgium", Organization = "stovem", OrganizationUnit = "stovem", CommonName = config.DNS.DomainNames.First(), }, privateKey); var certPem = cert.ToPem(); var privatePem = privateKey.ToPem(); Model.Certificate = privatePem + certPem; } return(validated); }
public async Task <ActionResult> Install(RequestAndInstallModel model) { if (ModelState.IsValid) { var s = SettingsStore.Instance.Load(); s.Clear(); s.Add(new SettingEntry() { Name = "email", Value = model.Email }); s.Add(new SettingEntry() { Name = "useStaging", Value = model.UseStaging.ToString() }); SettingsStore.Instance.Save(s); var settings = new AppSettingsAuthConfig(); var target = new AcmeConfig() { RegistrationEmail = model.Email, Host = model.Hostnames.First(), UseProduction = !model.UseStaging, AlternateNames = model.Hostnames.Skip(1).ToList(), PFXPassword = settings.PFXPassword, RSAKeyLength = settings.RSAKeyLength, }; var certModel = await new CertificateManager(settings).RequestAndInstallInternalAsync(target); if (certModel != null) { return(RedirectToAction("Hostname", new { id = certModel.CertificateInfo.Certificate.Thumbprint })); } } SetViewBagHostnames(); return(View(model)); }
public async Task <bool> Validate(AcmeContext acme, AcmeConfig config, List <INotifyConfig> notificationsList, Func <string[], Task <IOrderContext> > getOrder) { return(await Task.FromResult(true)); }