/// <summary> /// Pretty format /// </summary> /// <returns></returns> public string ToString(IInputService inputService) { var success = History.FindAll(x => x.Success).Count; var errors = History.AsEnumerable().Reverse().TakeWhile(x => !x.Success); var ret = $"{LastFriendlyName} - renewed {success} time{(success != 1 ? "s" : "")}"; var due = IsDue(); var dueDate = GetDueDate(); if (inputService == null) { ret += due ? ", due now" : dueDate == null ? "" : $", due after {dueDate}"; } else { ret += due ? ", due now" : dueDate == null ? "" : $", due after {inputService.FormatDate(dueDate.Value)}"; } if (errors.Count() > 0) { ret += $", {errors.Count()} error{(errors.Count() != 1 ? "s" : "")} like \"{errors.First().ErrorMessage}\""; } return(ret); }
public void Save(Renewal renewal, RenewResult result) { var renewals = Renewals.ToList(); if (renewal.New) { renewal.History = new List <RenewResult>(); renewals.Add(renewal); _log.Information(LogType.All, "Adding renewal for {friendlyName}", renewal.LastFriendlyName); } // Set next date renewal.History.Add(result); if (result.Success == true) { var date = _dueDateService.DueDate(renewal); if (date != null) { _log.Information(LogType.All, "Next renewal due at {date}", _inputService.FormatDate(date.Value)); } } renewal.Updated = true; Renewals = renewals; }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin?csrPlugin, RunLevel runLevel, Order order) { if (order.Details == null) { throw new InvalidOperationException("No order details found"); } // What are we going to get? var cacheKey = CacheKey(order); var pfxFileInfo = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}{PfxPostFix}")); var friendlyName = $"{order.FriendlyNameIntermediate} @ {_inputService.FormatDate(DateTime.Now)}"; // Generate the CSR here, because we want to save it // in the certificate cache folder even though we might // not need to submit it to the server in case of a // cached order order.Target.CsrBytes = order.Target.UserCsrBytes; if (order.Target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing CsrPlugin"); } if (order.KeyPath == null) { throw new InvalidOperationException("Missing ReusedKeyPath"); } var csr = await csrPlugin.GenerateCsr(order.KeyPath, order.Target); var keySet = await csrPlugin.GetKeys(); order.Target.CsrBytes = csr.GetDerEncoded(); order.Target.PrivateKey = keySet.Private; } if (order.Target.CsrBytes == null) { throw new InvalidOperationException("No CsrBytes found"); } // Store CSR for future reference ClearCache(order, postfix: $"*{CsrPostFix}"); var csrPath = GetPath(order.Renewal, $"-{cacheKey}{CsrPostFix}"); await File.WriteAllTextAsync(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", order.Target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); // Check order status if (order.Details.Payload.Status != AcmeClient.OrderValid) { // Finish the order by sending the CSR to // the server, which can then generate the // certificate. _log.Verbose("Submitting CSR"); order.Details = await _client.SubmitCsr(order.Details, order.Target.CsrBytes); if (order.Details.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Details.Payload.Status); throw new Exception($"Unable to complete order"); } } // Download the certificate from the server _log.Information("Downloading certificate {friendlyName}", order.FriendlyNameIntermediate); var certInfo = default(AcmeCertificate); try { certInfo = await _client.GetCertificate(order.Details); } catch (Exception ex) { throw new Exception($"Unable to get certificate", ex); } if (certInfo == null || certInfo.Certificate == null) { throw new Exception($"Unable to get certificate"); } var alternatives = new List <X509Certificate2Collection> { ParseCertificate(certInfo.Certificate, friendlyName, order.Target.PrivateKey) }; if (certInfo.Links != null) { foreach (var alt in certInfo.Links["alternate"]) { try { var altCertRaw = await _client.GetCertificate(alt); var altCert = ParseCertificate(altCertRaw, friendlyName, order.Target.PrivateKey); alternatives.Add(altCert); } catch (Exception ex) { _log.Warning("Unable to get alternate certificate: {ex}", ex.Message); } } } var selected = Select(alternatives); ClearCache(order, postfix: $"*{PfxPostFix}"); ClearCache($"{order.Renewal.Id}*{PfxPostFixLegacy}"); await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); _log.Debug("Certificate written to cache file {path} in certificate cache folder {folder}. It will be " + "reused when renewing within {x} day(s) as long as the --source and --csr parameters remain the same and " + "the --force switch is not used.", pfxFileInfo.Name, pfxFileInfo.Directory !.FullName, _settings.Cache.ReuseDays); if (csrPlugin != null) { try { var cert = selected. OfType <X509Certificate2>(). Where(x => x.HasPrivateKey). FirstOrDefault(); if (cert != null) { var certIndex = selected.IndexOf(cert); var newVersion = await csrPlugin.PostProcess(cert); if (newVersion != cert) { newVersion.FriendlyName = friendlyName; selected[certIndex] = newVersion; await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); newVersion.Dispose(); } } } catch (Exception ex) { _log.Warning("Private key conversion error: {ex}", ex.Message); } } pfxFileInfo.Refresh(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI order.Renewal.LastFriendlyName = order.FriendlyNameBase; // Recreate X509Certificate2 with correct flags for Store/Install return(FromCache(pfxFileInfo, order.Renewal.PfxPassword?.Value)); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin?csrPlugin, RunLevel runLevel, Order order) { if (order.Details == null) { throw new InvalidOperationException(); } // What are we going to get? var cacheKey = CacheKey(order); var pfxFileInfo = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}{PfxPostFix}")); // Determine/check the common name var identifiers = order.Target.GetHosts(false); var commonNameUni = order.Target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name base (for the renewal) var friendlyNameBase = order.Renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = order.Target.FriendlyName; } if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = commonNameUni; } // Determine the friendly name for this specific certificate var friendlyNameIntermediate = friendlyNameBase; if (!string.IsNullOrEmpty(order.FriendlyNamePart)) { friendlyNameIntermediate += $" [{order.FriendlyNamePart}]"; } var friendlyName = $"{friendlyNameIntermediate} @ {_inputService.FormatDate(DateTime.Now)}"; // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(order); if (cache != null && cache.CacheFile != null) { if (cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available on disk but not used due to --{switch} switch.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force a new request of the " + "certificate within {days} days, run with the --{switch} switch.", friendlyNameIntermediate, _settings.Cache.ReuseDays, nameof(MainArguments.Force).ToLower()); return(cache); } } } if (order.Details.Payload.Status != AcmeClient.OrderValid) { // Clear cache and write new cert ClearCache(order.Renewal, postfix: CsrPostFix); if (order.Target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing csrPlugin"); } // Backwards compatible with existing keys, which are not split per order yet. var keyFile = new FileInfo(GetPath(order.Renewal, $".keys")); if (!keyFile.Exists) { keyFile = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}.keys")); } var csr = await csrPlugin.GenerateCsr(keyFile.FullName, commonNameAscii, identifiers); var keySet = await csrPlugin.GetKeys(); order.Target.CsrBytes = csr.GetDerEncoded(); order.Target.PrivateKey = keySet.Private; var csrPath = GetPath(order.Renewal, $"-{cacheKey}{CsrPostFix}"); File.WriteAllText(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", order.Target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); } _log.Verbose("Submitting CSR"); order.Details = await _client.SubmitCsr(order.Details, order.Target.CsrBytes); if (order.Details.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Details.Payload.Status); throw new Exception($"Unable to complete order"); } } _log.Information("Requesting certificate {friendlyName}", friendlyNameIntermediate); var rawCertificate = await _client.GetCertificate(order.Details); if (rawCertificate == null) { throw new Exception($"Unable to get certificate"); } // Build pfx archive including any intermediates provided var text = Encoding.UTF8.GetString(rawCertificate); var pfx = new bc.Pkcs.Pkcs12Store(); var startIndex = 0; var endIndex = 0; const string startString = "-----BEGIN CERTIFICATE-----"; const string endString = "-----END CERTIFICATE-----"; while (true) { startIndex = text.IndexOf(startString, startIndex); if (startIndex < 0) { break; } endIndex = text.IndexOf(endString, startIndex); if (endIndex < 0) { break; } endIndex += endString.Length; var pem = text[startIndex..endIndex];
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin?csrPlugin, RunLevel runLevel, Order order) { if (order.Details == null) { throw new InvalidOperationException("No order details found"); } // What are we going to get? var cacheKey = CacheKey(order); var pfxFileInfo = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}{PfxPostFix}")); // Determine/check the common name var identifiers = order.Target.GetHosts(false); var commonNameUni = order.Target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name base (for the renewal) var friendlyNameBase = order.Renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = order.Target.FriendlyName; } if (string.IsNullOrEmpty(friendlyNameBase)) { friendlyNameBase = commonNameUni; } // Determine the friendly name for this specific certificate var friendlyNameIntermediate = friendlyNameBase; if (!string.IsNullOrEmpty(order.FriendlyNamePart)) { friendlyNameIntermediate += $" [{order.FriendlyNamePart}]"; } var friendlyName = $"{friendlyNameIntermediate} @ {_inputService.FormatDate(DateTime.Now)}"; // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(order); if (cache != null && cache.CacheFile != null) { if (cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available on disk but not used due to --{switch} switch.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force a new request of the " + "certificate within {days} days, run with the --{switch} switch.", friendlyNameIntermediate, _settings.Cache.ReuseDays, nameof(MainArguments.Force).ToLower()); return(cache); } } } if (order.Details.Payload.Status != AcmeClient.OrderValid) { // Clear cache and write new cert ClearCache(order.Renewal, postfix: CsrPostFix); if (order.Target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing csrPlugin"); } // Backwards compatible with existing keys, which are not split per order yet. var keyFile = new FileInfo(GetPath(order.Renewal, $".keys")); if (!keyFile.Exists) { keyFile = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}.keys")); } var csr = await csrPlugin.GenerateCsr(keyFile.FullName, commonNameAscii, identifiers); var keySet = await csrPlugin.GetKeys(); order.Target.CsrBytes = csr.GetDerEncoded(); order.Target.PrivateKey = keySet.Private; var csrPath = GetPath(order.Renewal, $"-{cacheKey}{CsrPostFix}"); await File.WriteAllTextAsync(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", order.Target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); } _log.Verbose("Submitting CSR"); order.Details = await _client.SubmitCsr(order.Details, order.Target.CsrBytes); if (order.Details.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Details.Payload.Status); throw new Exception($"Unable to complete order"); } } _log.Information("Requesting certificate {friendlyName}", friendlyNameIntermediate); var certInfo = await _client.GetCertificate(order.Details); if (certInfo == null || certInfo.Certificate == null) { throw new Exception($"Unable to get certificate"); } var alternatives = new List <X509Certificate2Collection> { ParseCertificate(certInfo.Certificate, friendlyName, order.Target.PrivateKey) }; if (certInfo.Links != null) { foreach (var alt in certInfo.Links["alternate"]) { try { var altCertRaw = await _client.GetCertificate(alt); var altCert = ParseCertificate(altCertRaw, friendlyName, order.Target.PrivateKey); alternatives.Add(altCert); } catch (Exception ex) { _log.Warning("Unable to get alternate certificate: {ex}", ex.Message); } } } var selected = Select(alternatives); ClearCache(order.Renewal, postfix: $"*{PfxPostFix}"); ClearCache(order.Renewal, postfix: $"*{PfxPostFixLegacy}"); await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); _log.Debug("Certificate written to cache file {path} in certificate cache folder {folder}. It will be " + "reused when renewing within {x} day(s) as long as the Target and Csr parameters remain the same and " + "the --force switch is not used.", pfxFileInfo.Name, pfxFileInfo.Directory !.FullName, _settings.Cache.ReuseDays); if (csrPlugin != null) { try { var cert = selected. OfType <X509Certificate2>(). Where(x => x.HasPrivateKey). FirstOrDefault(); if (cert != null) { var certIndex = selected.IndexOf(cert); var newVersion = await csrPlugin.PostProcess(cert); if (newVersion != cert) { newVersion.FriendlyName = friendlyName; selected[certIndex] = newVersion; await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); newVersion.Dispose(); } } } catch (Exception ex) { _log.Warning("Private key conversion error: {ex}", ex.Message); } } pfxFileInfo.Refresh(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI order.Renewal.LastFriendlyName = friendlyNameBase; // Recreate X509Certificate2 with correct flags for Store/Install return(FromCache(pfxFileInfo, order.Renewal.PfxPassword?.Value)); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="csrPlugin">Plugin used to generate CSR if it has not been provided in the target</param> /// <param name="runLevel"></param> /// <param name="renewal"></param> /// <param name="target"></param> /// <param name="order"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin?csrPlugin, RunLevel runLevel, Order order) { if (order.Details == null) { throw new InvalidOperationException("No order details found"); } // What are we going to get? var cacheKey = CacheKey(order); var pfxFileInfo = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}{PfxPostFix}")); var friendlyName = $"{order.FriendlyNameIntermediate} @ {_inputService.FormatDate(DateTime.Now)}"; // Determine/check the common name var identifiers = order.Target.GetIdentifiers(false); var commonName = order.Target.CommonName; if (!identifiers.Contains(commonName.Unicode(false))) { _log.Warning($"Common name {commonName.Value} provided is invalid."); commonName = identifiers.First(); } if (order.Details.Payload.Status != AcmeClient.OrderValid) { // Clear cache and write new cert ClearCache(order.Renewal, postfix: CsrPostFix); if (order.Target.CsrBytes == null) { if (csrPlugin == null) { throw new InvalidOperationException("Missing csrPlugin"); } // Backwards compatible with existing keys, which are not split per order yet. var keyFile = new FileInfo(GetPath(order.Renewal, $".keys")); if (!keyFile.Exists) { keyFile = new FileInfo(GetPath(order.Renewal, $"-{cacheKey}.keys")); } var csr = await csrPlugin.GenerateCsr(keyFile.FullName, commonName, identifiers); var keySet = await csrPlugin.GetKeys(); order.Target.CsrBytes = csr.GetDerEncoded(); order.Target.PrivateKey = keySet.Private; var csrPath = GetPath(order.Renewal, $"-{cacheKey}{CsrPostFix}"); await File.WriteAllTextAsync(csrPath, _pemService.GetPem("CERTIFICATE REQUEST", order.Target.CsrBytes)); _log.Debug("CSR stored at {path} in certificate cache folder {folder}", Path.GetFileName(csrPath), Path.GetDirectoryName(csrPath)); } _log.Verbose("Submitting CSR"); order.Details = await _client.SubmitCsr(order.Details, order.Target.CsrBytes); if (order.Details.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Details.Payload.Status); throw new Exception($"Unable to complete order"); } } _log.Information("Requesting certificate {friendlyName}", order.FriendlyNameIntermediate); var certInfo = await _client.GetCertificate(order.Details); if (certInfo == null || certInfo.Certificate == null) { throw new Exception($"Unable to get certificate"); } var alternatives = new List <X509Certificate2Collection> { ParseCertificate(certInfo.Certificate, friendlyName, order.Target.PrivateKey) }; if (certInfo.Links != null) { foreach (var alt in certInfo.Links["alternate"]) { try { var altCertRaw = await _client.GetCertificate(alt); var altCert = ParseCertificate(altCertRaw, friendlyName, order.Target.PrivateKey); alternatives.Add(altCert); } catch (Exception ex) { _log.Warning("Unable to get alternate certificate: {ex}", ex.Message); } } } var selected = Select(alternatives); ClearCache(order.Renewal, postfix: $"*{PfxPostFix}"); ClearCache(order.Renewal, postfix: $"*{PfxPostFixLegacy}"); await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); _log.Debug("Certificate written to cache file {path} in certificate cache folder {folder}. It will be " + "reused when renewing within {x} day(s) as long as the --source and --csr parameters remain the same and " + "the --force switch is not used.", pfxFileInfo.Name, pfxFileInfo.Directory !.FullName, _settings.Cache.ReuseDays); if (csrPlugin != null) { try { var cert = selected. OfType <X509Certificate2>(). Where(x => x.HasPrivateKey). FirstOrDefault(); if (cert != null) { var certIndex = selected.IndexOf(cert); var newVersion = await csrPlugin.PostProcess(cert); if (newVersion != cert) { newVersion.FriendlyName = friendlyName; selected[certIndex] = newVersion; await File.WriteAllBytesAsync(pfxFileInfo.FullName, selected.Export(X509ContentType.Pfx, order.Renewal.PfxPassword?.Value) !); newVersion.Dispose(); } } } catch (Exception ex) { _log.Warning("Private key conversion error: {ex}", ex.Message); } } pfxFileInfo.Refresh(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI order.Renewal.LastFriendlyName = order.FriendlyNameBase; // Recreate X509Certificate2 with correct flags for Store/Install return(FromCache(pfxFileInfo, order.Renewal.PfxPassword?.Value)); }
/// <summary> /// Request certificate from the ACME server /// </summary> /// <param name="binding"></param> /// <returns></returns> public async Task <CertificateInfo> RequestCertificate(ICsrPlugin csrPlugin, RunLevel runLevel, Renewal renewal, Target target, OrderDetails order) { // What are we going to get? var pfxFileInfo = new FileInfo(PfxFilePath(renewal)); // Determine/check the common name var identifiers = target.GetHosts(false); var commonNameUni = target.CommonName; var commonNameAscii = string.Empty; if (!string.IsNullOrWhiteSpace(commonNameUni)) { var idn = new IdnMapping(); commonNameAscii = idn.GetAscii(commonNameUni); if (!identifiers.Contains(commonNameAscii, StringComparer.InvariantCultureIgnoreCase)) { _log.Warning($"Common name {commonNameUni} provided is invalid."); commonNameAscii = identifiers.First(); commonNameUni = idn.GetUnicode(commonNameAscii); } } // Determine the friendly name var friendlyName = renewal.FriendlyName; if (string.IsNullOrEmpty(friendlyName)) { friendlyName = target.FriendlyName; } if (string.IsNullOrEmpty(friendlyName)) { friendlyName = commonNameUni; } // Try using cached certificate first to avoid rate limiting during // (initial?) deployment troubleshooting. Real certificate requests // will only be done once per day maximum unless the --force parameter // is used. var cache = CachedInfo(renewal); if (cache != null && cache.CacheFile.LastWriteTime > DateTime.Now.AddDays(_settings.Cache.ReuseDays * -1) && cache.Match(target)) { if (runLevel.HasFlag(RunLevel.IgnoreCache)) { _log.Warning("Cached certificate available but not used with --{switch}. Use 'Renew specific' or " + "'Renew all' in the main menu to run unscheduled renewals without hitting rate limits.", nameof(MainArguments.Force).ToLower()); } else { _log.Warning("Using cached certificate for {friendlyName}. To force issue of a new certificate within " + "24 hours, delete the .pfx file from the CertificatePath or run with the --{switch} switch. " + "Be ware that you might run into rate limits doing so.", friendlyName, nameof(MainArguments.Force).ToLower()); return(cache); } } if (target.CsrBytes == null) { var csr = await csrPlugin.GenerateCsr(GetPath(renewal, ".keys"), commonNameAscii, identifiers); target.CsrBytes = csr.GetDerEncoded(); target.PrivateKey = (await csrPlugin.GetKeys()).Private; File.WriteAllText(GetPath(renewal, "-csr.pem"), _pemService.GetPem("CERTIFICATE REQUEST", target.CsrBytes)); } _log.Verbose("Submitting CSR"); order = await _client.SubmitCsr(order, target.CsrBytes); if (order.Payload.Status != AcmeClient.OrderValid) { _log.Error("Unexpected order status {status}", order.Payload.Status); throw new Exception($"Unable to complete order"); } _log.Information("Requesting certificate {friendlyName}", friendlyName); var rawCertificate = await _client.GetCertificate(order); if (rawCertificate == null) { throw new Exception($"Unable to get certificate"); } byte[] certificateExport; using (var certificate = new X509Certificate2(rawCertificate)) { certificateExport = certificate.Export(X509ContentType.Cert); } var crtPem = _pemService.GetPem("CERTIFICATE", certificateExport); // Get issuer certificate var issuerCertificate = new X509Certificate2(rawCertificate.Skip(certificateExport.Length).ToArray()); var issuerCertificateExport = issuerCertificate.Export(X509ContentType.Cert); var issuerPem = _pemService.GetPem("CERTIFICATE", issuerCertificateExport); issuerCertificate.Dispose(); // Build pfx archive var pfx = new bc.Pkcs.Pkcs12Store(); var bcCertificate = _pemService.ParsePem <bc.X509.X509Certificate>(crtPem); var bcCertificateEntry = new bc.Pkcs.X509CertificateEntry(bcCertificate); var bcCertificateAlias = bcCertificate.SubjectDN.ToString(); pfx.SetCertificateEntry(bcCertificateAlias, bcCertificateEntry); if (target.PrivateKey != null) { var bcPrivateKeyEntry = new bc.Pkcs.AsymmetricKeyEntry(target.PrivateKey); pfx.SetKeyEntry(bcCertificateAlias, bcPrivateKeyEntry, new[] { bcCertificateEntry }); } var bcIssuer = _pemService.ParsePem <bc.X509.X509Certificate>(issuerPem); var bcIssuerEntry = new bc.Pkcs.X509CertificateEntry(bcIssuer); var bcIssuerAlias = bcIssuer.SubjectDN.ToString(); pfx.SetCertificateEntry(bcIssuerAlias, bcIssuerEntry); var pfxStream = new MemoryStream(); pfx.Save(pfxStream, null, new bc.Security.SecureRandom()); pfxStream.Position = 0; using var pfxStreamReader = new BinaryReader(pfxStream); var tempPfx = new X509Certificate2( pfxStreamReader.ReadBytes((int)pfxStream.Length), (string)null, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); tempPfx.FriendlyName = $"{friendlyName} {_inputService.FormatDate(DateTime.Now)}"; File.WriteAllBytes(pfxFileInfo.FullName, tempPfx.Export(X509ContentType.Pfx, renewal.PfxPassword?.Value)); if (csrPlugin != null) { try { var newVersion = await csrPlugin.PostProcess(tempPfx); if (newVersion != tempPfx) { newVersion.FriendlyName = $"{friendlyName} {_inputService.FormatDate(DateTime.Now)}"; File.WriteAllBytes(pfxFileInfo.FullName, newVersion.Export(X509ContentType.Pfx, renewal.PfxPassword?.Value)); newVersion.Dispose(); } } catch (Exception) { _log.Warning("Private key conversion error."); } } pfxFileInfo.Refresh(); tempPfx.Dispose(); // Update LastFriendlyName so that the user sees // the most recently issued friendlyName in // the WACS GUI renewal.LastFriendlyName = friendlyName; // Recreate X509Certificate2 with correct flags for Store/Install return(new CertificateInfo() { Certificate = ReadForUse(pfxFileInfo, renewal.PfxPassword?.Value), CacheFile = pfxFileInfo, CacheFilePassword = renewal.PfxPassword?.Value }); }
/// <summary> /// Return abort result /// </summary> /// <param name="renewal"></param> /// <returns></returns> internal RenewResult Abort(Renewal renewal) { var dueDate = _dueDate.DueDate(renewal); if (dueDate != null) { // For sure now that we don't need to run so abort this execution _log.Information("Renewal {renewal} is due after {date}", renewal.LastFriendlyName, _input.FormatDate(dueDate.Value)); } return(new RenewResult() { Abort = true }); }
/// <summary> /// Show certificate details /// </summary> private async Task ShowRenewal(Renewal renewal) { try { _input.CreateSpace(); _input.Show("Id", renewal.Id); _input.Show("File", $"{renewal.Id}.renewal.json"); _input.Show("FriendlyName", string.IsNullOrEmpty(renewal.FriendlyName) ? $"[Auto] {renewal.LastFriendlyName}" : renewal.FriendlyName); _input.Show(".pfx password", renewal.PfxPassword?.Value); var expires = renewal.History.Where(x => x.Success == true).FirstOrDefault()?.ExpireDate; if (expires == null) { _input.Show("Expires", "Unknown"); } else { _input.Show("Expires", _input.FormatDate(expires.Value)); } var dueDate = _dueDate.DueDate(renewal); if (dueDate == null) { _input.Show("Renewal due", "Now"); } else { _input.Show("Renewal due", _input.FormatDate(dueDate.Value)); } _input.Show("Renewed", $"{renewal.History.Where(x => x.Success == true).Count()} times"); _input.CreateSpace(); renewal.TargetPluginOptions.Show(_input); renewal.ValidationPluginOptions.Show(_input); if (renewal.OrderPluginOptions != null) { renewal.OrderPluginOptions.Show(_input); } if (renewal.CsrPluginOptions != null) { renewal.CsrPluginOptions.Show(_input); } foreach (var ipo in renewal.StorePluginOptions) { ipo.Show(_input); } foreach (var ipo in renewal.InstallationPluginOptions) { ipo.Show(_input); } _input.CreateSpace(); var historyLimit = 10; if (renewal.History.Count <= historyLimit) { _input.Show(null, "[History]"); } else { _input.Show($"History (most recent {historyLimit} of {renewal.History.Count} entries)"); } await _input.WritePagedList( renewal.History. AsEnumerable(). Reverse(). Take(historyLimit). Reverse(). Select(x => Choice.Create(x))); } catch (Exception ex) { _log.Error(ex, "Unable to list details for target"); } }