public static void TestHost( [Required(Description = "Host name")] string hostName, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } hostName = hostName.ToAsciiHostName(); using (var challengeManager = AcmeEnvironment.CreateChallengeManager()) { var result = challengeManager.TestAsync(new[] { hostName }).Result; Log.WriteLine(); if (result) { Log.WriteLine("Test authorization was successful. The real verification may still fail,"); Log.WriteLine("ie. when server is not accessible from outside."); } else { Log.WriteLine("Test authorization failed. Examine the above to find out why."); } } }
public static void Purge( [Optional(false, "wi", Description = "What if - only show hosts to be purged")] bool whatIf, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } // Get old expired hosts Log.Write($"Loading hosts expired at least {AcmeEnvironment.CfgStore.PurgeDaysAfterExpiration} days ago..."); var expiredHosts = AcmeEnvironment.CfgStore.Hosts .Where(x => x.NotAfter <= DateTime.Today.AddDays(-AcmeEnvironment.CfgStore.PurgeDaysAfterExpiration)) .OrderBy(x => x.NotAfter); if (!expiredHosts.Any()) { Log.WriteLine("OK, no hosts to purge"); return; } Log.WriteLine($"OK, {expiredHosts.Count()} hosts to purge:"); // List all items to purge Log.Indent(); foreach (var item in expiredHosts) { var dae = Math.Floor(DateTime.Today.Subtract(item.NotAfter).TotalDays); Log.WriteLine($"Host {item.CommonName} expired {dae} days ago ({item.NotAfter:D})"); if (whatIf) { continue; } Log.Indent(); // Delete from config Log.Write("Deleting from database..."); AcmeEnvironment.CfgStore.Hosts.Remove(item); Log.WriteLine("OK"); // Delete files Log.WriteLine("Deleting files:"); Log.Indent(); foreach (var name in item.GetNames()) { DeleteHostFiles(name, AcmeEnvironment.CfgStore.PfxFolder, AcmeEnvironment.CfgStore.PemFolder); } Log.Unindent(); Log.Unindent(); } Log.Unindent(); AcmeEnvironment.SaveConfig(cfgFileName); }
public static void DelHost( [Required(Description = "Host name")] string hostName, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } hostName = hostName.ToAsciiHostName(); // Check if there is host with this name Log.Write($"Finding host {hostName.ExplainHostName()}..."); var host = AcmeEnvironment.CfgStore.Hosts.SingleOrDefault(x => x.GetNames().Any(n => n.Equals(hostName, StringComparison.OrdinalIgnoreCase))); if (host == null) { AcmeEnvironment.CrashExit($"Host '{hostName.ExplainHostName()}' was not found."); } Log.WriteLine("OK"); // Delete files Log.WriteLine("Deleting files:"); Log.Indent(); DeleteHostFiles(hostName, AcmeEnvironment.CfgStore.PfxFolder, AcmeEnvironment.CfgStore.PemFolder); Log.Unindent(); // Delete entry from configuration Log.Write("Deleting configuration entry..."); AcmeEnvironment.CfgStore.Hosts.Remove(host); Log.WriteLine("OK"); // Save configuration AcmeEnvironment.SaveConfig(cfgFileName); }
public static void InitWeb( [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, "y", Description = "Overwrite existing file")] bool overwrite, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } // Check for current web.config file var webConfigName = Path.Combine(AcmeEnvironment.CfgStore.ChallengeFolder, "web.config"); if (!overwrite) { Console.Write($"Checking current {webConfigName}..."); if (File.Exists(webConfigName)) { AcmeEnvironment.CrashExit("File already exists. Use /y to overwrite."); } Console.WriteLine("OK"); } // Write value from resources to web.config file try { Console.Write($"Saving {webConfigName}..."); File.WriteAllText(webConfigName, Resources.WebConfig); Console.WriteLine("OK"); } catch (Exception ex) { AcmeEnvironment.CrashExit(ex); } }
public static void AddHosts( [Optional(false, "ccs", Description = "Add CCS binding to hosts without one and add them as well")] bool addCcsBinding, [Optional(false, "sni", Description = "Require SNI for newly created bindings")] bool requireSni, [Optional("localhost", "s", Description = "IIS server name")] string serverName, [Optional(AcmeEnvironment.DEFAULT_CONFIG_NAME, "cfg", Description = "Configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; AcmeEnvironment.LoadConfig(cfgFileName); using (var sc = new ServerContext(serverName)) { IEnumerable <BindingInfo> bindings = null; try { Log.Write($"Getting bindings from '{serverName}'..."); // Get all bindings bindings = sc.GetBindings(); } catch (Exception ex) { AcmeEnvironment.CrashExit(ex); } // Get only bindings matching the following criteria // - host name specified // - site is running // - site is running on default port bindings = from b in bindings where !string.IsNullOrEmpty(b.Host) && b.SiteStarted && b.IsDefaultPort select b; // Get only CCS enabled sites, unless overriden if (!addCcsBinding) { bindings = bindings.Where(x => x.CentralCertStore); } Log.WriteLine($"OK, {bindings.Count()} bindings found"); // Find new hosts Log.Write("Finding new hosts to add..."); bindings = bindings.Where(x => !AcmeEnvironment.CfgStore.Hosts.SelectMany(h => h.GetNames()).Any(h => h.Equals(x.Host, StringComparison.OrdinalIgnoreCase))); if (!bindings.Any()) { Log.WriteLine("None"); return; } Log.WriteLine($"OK"); using (var ac = new AutoAcmeContext(AcmeEnvironment.CfgStore.ServerUri)) { ac.ChallengeVerificationRetryCount = AcmeEnvironment.CfgStore.ChallengeVerificationRetryCount; ac.ChallengeVerificationWait = TimeSpan.FromSeconds(AcmeEnvironment.CfgStore.ChallengeVerificationWaitSeconds); // Login to Let's Encrypt service if (string.IsNullOrEmpty(AcmeEnvironment.CfgStore.SerializedAccountData)) { AcmeEnvironment.CfgStore.SerializedAccountData = ac.RegisterAndLogin(AcmeEnvironment.CfgStore.EmailAddress); AcmeEnvironment.SaveConfig(cfgFileName); } else { ac.Login(AcmeEnvironment.CfgStore.SerializedAccountData); } // Add new hosts Log.Indent(); using (var challengeManager = AcmeEnvironment.CreateChallengeManager()) { foreach (var binding in bindings.ToArray()) { // Check if was already added before if (AcmeEnvironment.CfgStore.Hosts.SelectMany(h => h.GetNames()).Any(h => h.Equals(binding.Host, StringComparison.OrdinalIgnoreCase))) { continue; } Log.WriteLine($"Adding new host {binding.Host.ExplainHostName()}:"); Log.Indent(); // Request certificate CertificateRequestResult result = null; try { result = ac.GetCertificate(new[] { binding.Host }, AcmeEnvironment.CfgStore.PfxPassword, challengeManager); } catch (Exception ex) { Log.Exception(ex, "Request failed"); continue; } // Export files Log.WriteLine("Exporting files:"); Log.Indent(); result.Export(binding.Host, AcmeEnvironment.CfgStore.PfxFolder, AcmeEnvironment.CfgStore.PemFolder); Log.Unindent(); // Update database entry Log.Write("Updating database entry..."); AcmeEnvironment.CfgStore.Hosts.Add(new Host { CommonName = binding.Host, NotBefore = result.Certificate.NotBefore, NotAfter = result.Certificate.NotAfter, SerialNumber = result.Certificate.SerialNumber, Thumbprint = result.Certificate.Thumbprint }); Log.WriteLine("OK"); AcmeEnvironment.SaveConfig(cfgFileName); // Add HTTPS + CCS binding var alreadyHasHttpsWithCcs = bindings.Any(b => b.Host.Equals(binding.Host, StringComparison.OrdinalIgnoreCase) && b.Protocol.Equals("https", StringComparison.OrdinalIgnoreCase) && b.CentralCertStore); if (addCcsBinding && !alreadyHasHttpsWithCcs) { try { Log.Write($"Adding HTTPS CCS binding for {binding.Host.ExplainHostName()}..."); sc.AddCcsBinding(binding.SiteName, binding.Host, requireSni); Log.WriteLine("OK"); } catch (Exception ex) { AcmeEnvironment.CrashExit(ex); } } Log.Unindent(); } Log.Unindent(); } } } }
public static void Renew( [Optional(false, "xt", Description = "Skip authentication test")] bool skipTest, [Optional(false, "wi", Description = "What if - only show hosts to be renewed")] bool whatIf, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } // Get hosts expiring in near future Log.Write($"Loading hosts expiring in {AcmeEnvironment.CfgStore.RenewDaysBeforeExpiration} days..."); var expiringHosts = AcmeEnvironment.CfgStore.Hosts .Where(x => x.NotAfter <= DateTime.Now.AddDays(AcmeEnvironment.CfgStore.RenewDaysBeforeExpiration)) .OrderBy(x => x.NotAfter); if (!expiringHosts.Any()) { Log.WriteLine("OK, no hosts to renew"); return; } Log.WriteLine($"OK, {expiringHosts.Count()} hosts to renew"); using (var ac = new AutoAcmeContext(AcmeEnvironment.CfgStore.ServerUri)) { try { ac.ChallengeVerificationRetryCount = AcmeEnvironment.CfgStore.ChallengeVerificationRetryCount; ac.ChallengeVerificationWait = TimeSpan.FromSeconds(AcmeEnvironment.CfgStore.ChallengeVerificationWaitSeconds); if (string.IsNullOrEmpty(AcmeEnvironment.CfgStore.SerializedAccountData)) { AcmeEnvironment.CfgStore.SerializedAccountData = ac.RegisterAndLogin(AcmeEnvironment.CfgStore.EmailAddress); AcmeEnvironment.SaveConfig(cfgFileName); } else { ac.Login(AcmeEnvironment.CfgStore.SerializedAccountData); } } catch (Exception ex) { Log.Exception(ex, "Login failed"); AcmeEnvironment.CrashExit("Unable to login or create account."); } // Renew them using (var challengeManager = AcmeEnvironment.CreateChallengeManager()) { foreach (var host in expiringHosts) { // Display info var dte = Math.Floor(host.NotAfter.Subtract(DateTime.Now).TotalDays); if (dte < 0) { Log.WriteLine($"Host {host.CommonName} expired {-dte} days ago ({host.NotAfter:D})"); } else { Log.WriteLine($"Host {host.CommonName} expires in {dte} days ({host.NotAfter:D})"); } if (whatIf) { continue; } Log.Indent(); // Request certificate CertificateRequestResult result = null; try { result = ac.GetCertificate(host.GetNames(), AcmeEnvironment.CfgStore.PfxPassword, challengeManager, skipTest); } catch (Exception ex) { Log.Exception(ex, "Renewal failed"); } if (result != null) { // Display certificate info Log.WriteLine("Certificate information:"); Log.Indent(); Log.WriteLine($"Issuer: {result.Certificate.Issuer}"); Log.WriteLine($"Subject: {result.Certificate.Subject}"); Log.WriteLine($"Serial number: {result.Certificate.SerialNumber}"); Log.WriteLine($"Not before: {result.Certificate.NotBefore:o}"); Log.WriteLine($"Not after: {result.Certificate.NotAfter:o}"); Log.WriteLine($"Thumbprint: {result.Certificate.Thumbprint}"); Log.Unindent(); // Export files Log.WriteLine("Exporting files:"); Log.Indent(); foreach (var name in host.GetNames()) { result.Export(name, AcmeEnvironment.CfgStore.PfxFolder, AcmeEnvironment.CfgStore.PemFolder); } Log.Unindent(); // Update database entry Log.Write("Updating database entry..."); host.NotBefore = result.Certificate.NotBefore; host.NotAfter = result.Certificate.NotAfter; host.SerialNumber = result.Certificate.SerialNumber; host.Thumbprint = result.Certificate.Thumbprint; Log.WriteLine("OK"); // Save configuration AcmeEnvironment.SaveConfig(cfgFileName); } Log.Unindent(); } } } }
public static void List( [Optional(null, "f", Description = "Save to file")] string fileName, [Optional(false, "xh", Description = "Do not list column headers")] bool skipHeaders, [Optional("TAB", "cs", Description = "Column separator")] string columnSeparator, [Optional("o", "df", Description = "Date format string")] string dateFormat, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (columnSeparator.Equals("TAB", StringComparison.OrdinalIgnoreCase)) { columnSeparator = "\t"; } if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } // List hosts Log.Write("Getting hosts..."); var sb = new StringBuilder(); var count = 0; if (!skipHeaders) { sb.AppendLine(string.Join(columnSeparator, "Common Name", "Not Before", "Not After", "Serial Number", "Thumbprint", "DaysToExpire")); } foreach (var item in AcmeEnvironment.CfgStore.Hosts) { sb.AppendLine(string.Join(columnSeparator, item.CommonName, item.NotBefore.ToString(dateFormat), item.NotAfter.ToString(dateFormat), item.SerialNumber, item.Thumbprint, Math.Floor(item.NotAfter.Subtract(DateTime.Now).TotalDays))); count++; } Log.WriteLine($"OK, {count} hosts"); // Print to console or file try { if (string.IsNullOrWhiteSpace(fileName)) { Log.WriteLine(sb.ToString()); } else { Log.Write($"Writing to file '{fileName}'..."); File.WriteAllText(fileName, sb.ToString()); Log.WriteLine("OK"); } } catch (Exception ex) { AcmeEnvironment.CrashExit(ex); } }
public static void AddHost( [Required(Description = "Host name (multiple names allowed)")] string hostNames, [Optional(false, "xt", Description = "Skip authentication test")] bool skipTest, [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName, [Optional(null, "c", Description = "Certificate Country")] string csrCountryName, [Optional(null, "st", Description = "Certificate State")] string csrState, [Optional(null, "l", Description = "Certificate Locality")] string csrLocality, [Optional(null, "o", Description = "Certificate Organization")] string csrOrganization, [Optional(null, "ou", Description = "Certificate Organizational Unit")] string csrOrdganizationUnit, [Optional(false, Description = "Show verbose error messages")] bool verbose) { Log.VerboseMode = verbose; if (AcmeEnvironment.CfgStore == null) { AcmeEnvironment.LoadConfig(cfgFileName); } hostNames = hostNames.ToAsciiHostNames(); // Check if there already is host with this name Log.Write("Checking host..."); var existingHostnames = new HashSet <string>(AcmeEnvironment.CfgStore.Hosts.SelectMany(h => h.GetNames()), StringComparer.OrdinalIgnoreCase); foreach (var hostName in hostNames.SplitNames()) { if (existingHostnames.Contains(hostName)) { AcmeEnvironment.CrashExit($"Host '{hostName.ExplainHostName()}' is already managed."); } } Log.WriteLine("OK"); // Request certificate Log.WriteLine($"Requesting certificate for {hostNames}:"); Log.Indent(); CertificateRequestResult result = null; try { using (var ac = new AutoAcmeContext(AcmeEnvironment.CfgStore.ServerUri)) { ac.ChallengeVerificationRetryCount = AcmeEnvironment.CfgStore.ChallengeVerificationRetryCount; ac.ChallengeVerificationWait = TimeSpan.FromSeconds(AcmeEnvironment.CfgStore.ChallengeVerificationWaitSeconds); if (string.IsNullOrEmpty(AcmeEnvironment.CfgStore.SerializedAccountData)) { AcmeEnvironment.CfgStore.SerializedAccountData = ac.RegisterAndLogin(AcmeEnvironment.CfgStore.EmailAddress); AcmeEnvironment.SaveConfig(cfgFileName); } else { ac.Login(AcmeEnvironment.CfgStore.SerializedAccountData); } using (var challengeManager = AcmeEnvironment.CreateChallengeManager()) { result = ac.GetCertificate(hostNames.SplitNames(), AcmeEnvironment.CfgStore.PfxPassword, challengeManager, skipTest); } } } catch (Exception ex) { Log.Exception(ex, "Request failed"); AcmeEnvironment.CrashExit("Unable to get certificate for new host."); } if (result != null) { // Display certificate info Log.Indent(); Log.WriteLine("Certificate information:"); Log.WriteLine($"Issuer: {result.Certificate.Issuer}"); Log.WriteLine($"Subject: {result.Certificate.Subject}"); Log.WriteLine($"Serial number: {result.Certificate.SerialNumber}"); Log.WriteLine($"Not before: {result.Certificate.NotBefore:o}"); Log.WriteLine($"Not after: {result.Certificate.NotAfter:o}"); Log.WriteLine($"Thumbprint: {result.Certificate.Thumbprint}"); Log.Unindent(); Log.Unindent(); // Export files Log.WriteLine("Exporting files:"); Log.Indent(); foreach (var hostName in hostNames.SplitNames()) { result.Export(hostName, AcmeEnvironment.CfgStore.PfxFolder, AcmeEnvironment.CfgStore.PemFolder); } Log.Unindent(); // Update database entry Log.Write("Updating database entry..."); var host = new Host { CommonName = hostNames, NotBefore = result.Certificate.NotBefore, NotAfter = result.Certificate.NotAfter, SerialNumber = result.Certificate.SerialNumber, Thumbprint = result.Certificate.Thumbprint }; AcmeEnvironment.CfgStore.Hosts.Add(host); Log.WriteLine("OK"); // Save configuration AcmeEnvironment.SaveConfig(cfgFileName); } }