public async Task <Data.Models.AcmeOrder> AcquireAcmeCert(long id, bool userRequested = false) { var acmeCert = await _dataContext.AcmeCertificates .Include(x => x.Key) .Include(x => x.AcmeAccount) .ThenInclude(x => x.Key) .SingleAsync(x => x.AcmeCertificateId == id); _logger.LogDebug($"[{acmeCert.Subject}] - starting certificate acquisition"); _certesAcmeProvider.Initialize(acmeCert); _logger.LogDebug($"[{acmeCert.Subject}] - creating order"); var acmeOrder = await _certesAcmeProvider.BeginOrder(); _dataContext.AcmeOrders.Add(acmeOrder); _dataContext.SaveChanges(); _logger.LogDebug($"[{acmeCert.Subject}] - requestion ACME validation"); await _certesAcmeProvider.Validate(); _dataContext.SaveChanges(); _logger.LogDebug($"[{acmeCert.Subject}] - completing order"); await _certesAcmeProvider.Complete(); acmeOrder.AcmeRequests.Clear(); _dataContext.SaveChanges(); if (!acmeOrder.Completed) { _logger.LogError($"[{acmeCert.Subject}] - error obtaining certificate: {acmeOrder.Errors}"); } _logger.LogDebug($"[{acmeCert.Subject}] - done"); if (acmeOrder.Completed || userRequested) { return(acmeOrder); } try { if (string.IsNullOrWhiteSpace(_mailSenderInfo?.Value?.Host)) { _logger.LogWarning("SMTP not configured. Unable to send certificate change notifications."); return(acmeOrder); } var usersToNotify = _dataContext.NotificationSettings .Include(x => x.ApplicationUser) .Where(x => x.AcquisitionFailureAlerts == true); _mailSender.Initialize(_mailSenderInfo.Value); foreach (var user in usersToNotify) { var recipients = new List <string>(); recipients.Add(user.ApplicationUser.Email); if (!string.IsNullOrWhiteSpace(user.AdditionalRecipients)) { recipients.AddRange(user.AdditionalRecipients.Split(',', ';', StringSplitOptions.RemoveEmptyEntries)); } var lastValidAcmeOrder = _dataContext.AcmeOrders .Include(x => x.DomainCertificate) .Where(x => x.AcmeCertificateId == acmeOrder.AcmeCertificateId) .OrderByDescending(x => x.DateCreated) .FirstOrDefault(x => x.Status == AcmeOrderStatus.Completed); string previousCertText = string.Empty; string lastAcquiryText = "Never"; if (lastValidAcmeOrder?.DomainCertificate != null) { lastAcquiryText = lastValidAcmeOrder.DateCreated.ToString(); var thumbprint = lastValidAcmeOrder.DomainCertificate.Thumbprint; var publicKey = lastValidAcmeOrder.DomainCertificate.Certificate.PublicKeyPinningHash(); var validFrom = lastValidAcmeOrder.DomainCertificate.ValidNotBefore.ToShortDateString(); var validTo = lastValidAcmeOrder.DomainCertificate.ValidNotAfter.ToShortDateString(); var sb = new StringBuilder(); sb.AppendLine("<u>Current certificate details</u>"); sb.AppendLine(); sb.AppendLine("<b>Thumbprint</b>"); sb.AppendLine($"{thumbprint}"); sb.AppendLine(); sb.AppendLine("<b>Public Key (hash)</b>"); sb.AppendLine($"{publicKey}"); sb.AppendLine(); sb.AppendLine("<b>Valid</b>"); sb.AppendLine($"{validFrom} to {validTo}"); previousCertText = sb.ToString(); } _logger.LogInformation($"Sending acquiry failure notification email for {acmeOrder.AcmeCertificate.Name}"); _mailSender.Send($"[certera] {acmeOrder.AcmeCertificate.Name} - certificate acquisition failure notification", TemplateManager.BuildTemplate(TemplateManager.NotificationCertificateAcquisitionFailure, new { Domain = acmeOrder.AcmeCertificate.Subject, Error = acmeOrder.Errors, PreviousCertificateDetails = previousCertText, LastAcquiryText = lastAcquiryText }), recipients.ToArray()); } } catch (Exception e) { _logger.LogError(e, "Error sending certificate change notification email"); } return(acmeOrder); }
public async Task <AcmeOrder> AcquireAcmeCert(long id, bool userRequested = false) { var acmeCert = await _dataContext.AcmeCertificates .Include(x => x.Key) .Include(x => x.AcmeAccount) .ThenInclude(x => x.Key) .SingleAsync(x => x.AcmeCertificateId == id); var dnsSettings = _dataContext.GetDnsSettings(); _logger.LogDebug($"[{acmeCert.Subject}] - starting certificate acquisition"); _certesAcmeProvider.Initialize(acmeCert); _logger.LogDebug($"[{acmeCert.Subject}] - creating ACME order"); var acmeOrder = await _certesAcmeProvider.BeginOrder(); _dataContext.AcmeOrders.Add(acmeOrder); _dataContext.SaveChanges(); try { if (acmeCert.IsDnsChallengeType()) { _logger.LogDebug($"[{acmeCert.Subject}] - setting DNS records"); var dnsSetResult = _certesAcmeProvider.SetDnsRecords(dnsSettings); if (dnsSetResult) { _logger.LogDebug($"[{acmeCert.Subject}] - validating DNS records"); var dnsValidateResult = await _certesAcmeProvider.ValidateDnsRecords(); } } _logger.LogDebug($"[{acmeCert.Subject}] - requesting ACME validation"); await _certesAcmeProvider.Validate(); _dataContext.SaveChanges(); _logger.LogDebug($"[{acmeCert.Subject}] - completing order"); await _certesAcmeProvider.Complete(); if (acmeCert.IsDnsChallengeType()) { _logger.LogDebug($"[{acmeCert.Subject}] - cleaning up DNS records"); _certesAcmeProvider.CleanupDnsRecords(dnsSettings); } } catch { throw; } finally { acmeOrder.AcmeRequests.Clear(); _dataContext.SaveChanges(); } if (!acmeOrder.Completed) { _logger.LogError($"[{acmeCert.Subject}] - error obtaining certificate: {acmeOrder.Errors}"); } _logger.LogDebug($"[{acmeCert.Subject}] - done"); if (acmeOrder.Completed || userRequested) { return(acmeOrder); } try { var notificationSettings = _dataContext.NotificationSettings .Include(x => x.ApplicationUser) .Where(x => x.AcquisitionFailureAlerts == true) .ToList(); var lastValidAcmeOrder = _dataContext.AcmeOrders .Include(x => x.DomainCertificate) .Where(x => x.AcmeCertificateId == acmeOrder.AcmeCertificateId) .OrderByDescending(x => x.DateCreated) .FirstOrDefault(x => x.Status == AcmeOrderStatus.Completed); _notificationService.SendCertAcquitionFailureNotification(notificationSettings, acmeOrder, lastValidAcmeOrder); } catch (Exception e) { _logger.LogError(e, "Error sending certificate acquisition failure notification"); } return(acmeOrder); }
public async Task Invoke(HttpContext httpContext, ILogger <SetupAcmeCertMiddleware> logger, CertesAcmeProvider certes, DataContext dataContext, IOptionsSnapshot <HttpServer> httpServerOptions) { if (httpContext.Request.Method != "POST") { httpContext.Response.Redirect("/"); return; } var host = httpServerOptions.Value.SiteHostname; await httpContext.Response.WriteAsync(ConsoleHeader); await httpContext.Response.WriteAsync($@" Starting certificate acquisition for {host}... <br />"); var acmeCert = await dataContext.AcmeCertificates .Include(x => x.Key) .Include(x => x.AcmeAccount) .ThenInclude(x => x.Key) .FirstAsync(x => x.Subject == host); await httpContext.Response.WriteAsync($@" Initializing ACME client and ensuring account... <br />"); certes.Initialize(acmeCert); // Begin the order await httpContext.Response.WriteAsync($@" Creating order... <br />"); var acmeOrder = await certes.BeginOrder(); dataContext.AcmeOrders.Add(acmeOrder); dataContext.SaveChanges(); // Validate (i.e. ask ACME to check via HTTP-01 or DNS-01) await httpContext.Response.WriteAsync($@" Requesting ACME validation... <br />"); await certes.Validate(); dataContext.SaveChanges(); // Complete the order (i.e. obtain the certifiate) await httpContext.Response.WriteAsync($@" Completing order... (this can take up to 30 seconds)<br />"); await certes.Complete(); // Remove old ACME requests because they're irrelevant await httpContext.Response.WriteAsync($@" Cleaning up... <br />"); acmeOrder.AcmeRequests.Clear(); dataContext.SaveChanges(); await httpContext.Response.WriteAsync($@" Done. Status: {acmeOrder.Status}... <br />"); bool restart = false; if (!acmeOrder.Completed) { var errors = acmeOrder.Errors.Replace("\r\n", "<br />"); await httpContext.Response.WriteAsync($@" <div class=""red""><p>Errors:<br />{errors}</p></div><br />"); } else { restart = true; await httpContext.Response.WriteAsync(@" <br /> Setup finished successfully! <br /> <hr /> <br /> Certera is restarting... <br /> <script> var restartFinished = setInterval(checkLoaded, 5000); var tries = 5; function checkLoaded() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { window.location.href=""/""; } }; xhttp.open(""GET"", ""/api/test"", true); xhttp.send(); } </script> <noscript> Please wait 10 seconds for server to restart. Then, <a href=""/"">click here to continue</a>. </noscript>"); } await httpContext.Response.WriteAsync(@" </p> </body> </html>"); if (restart) { Program.Restart(); } }
public async Task <IActionResult> OnPostAsync(long id, string action, string key) { AcmeCertificate = await _context.AcmeCertificates .Include(a => a.AcmeAccount) .ThenInclude(a => a.Key) .Include(a => a.AcmeOrders) .ThenInclude(o => o.DomainCertificate) .FirstOrDefaultAsync(m => m.AcmeCertificateId == id); if (AcmeCertificate == null) { return(NotFound()); } switch (action.ToLower()) { case "keychange": switch (key) { case "apikey1": AcmeCertificate.ApiKey1 = ApiKeyGenerator.CreateApiKey(); break; case "apikey2": AcmeCertificate.ApiKey2 = ApiKeyGenerator.CreateApiKey(); break; } await _context.SaveChangesAsync(); break; case "ocspcheck": try { var order = AcmeCertificate.GetLatestValidAcmeOrder(); if (order?.Certificate != null) { var client = new OcspClient(); var status = client.GetOcspStatus(order.Certificate); OcspStatus = status.ToString(); } else { OcspStatus = "No certificate"; } } catch (Exception e) { _logger.LogWarning($"Error obtaining OCSP status:{e.Message}"); OcspStatus = "Error"; } break; case "revoke": { var order = AcmeCertificate.GetLatestValidAcmeOrder(); if (order?.RawDataPem != null) { _certesAcmeProvider.Initialize(AcmeCertificate); var cert = new Certes.Acme.CertificateChain(order.RawDataPem); var reason = (RevocationReason)Enum.Parse(typeof(RevocationReason), RevocationReason, true); await _certesAcmeProvider.Revoke(cert.Certificate.ToDer(), reason); StatusMessage = "Certificate revocation submitted"; } break; } } return(Page()); }