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 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 InitCfg( [Optional(false, "d", Description = "Don't ask, use default values")] bool useDefaults, [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; // Check if config file already exists if (!overwrite && File.Exists(cfgFileName)) { AcmeEnvironment.CrashExit("Configuration file already exists. Use /y to overwrite."); } // Create default configuration AcmeEnvironment.CfgStore = new Store(); if (!useDefaults) { // Ask some questions Console.WriteLine("-------------------------------------------------------------------------------"); Console.WriteLine(" Please answer the following questions to build configuration: "); Console.WriteLine("-------------------------------------------------------------------------------"); Console.WriteLine("Let's Encrypt needs your e-mail address, ie. [email protected]. This email"); Console.WriteLine("would be used for critical communication, such as certificate expiration when"); Console.WriteLine("no renewed certificate has been issued etc. Type your e-mail and press ENTER."); Console.Write("> "); AcmeEnvironment.CfgStore.EmailAddress = Console.ReadLine(); Console.WriteLine("Enter the folder for challenge verification files. Default path is:"); Console.WriteLine(AcmeEnvironment.CfgStore.ChallengeFolder); Console.WriteLine("To use it, just press ENTER."); Console.Write("> "); var challengePath = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(challengePath)) { AcmeEnvironment.CfgStore.ChallengeFolder = challengePath; } Console.WriteLine("Enter the folder where PFX files are to be stored. Default path is:"); Console.WriteLine(AcmeEnvironment.CfgStore.PfxFolder); Console.WriteLine("To use it, just press ENTER."); Console.Write("> "); var pfxPath = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(pfxPath)) { AcmeEnvironment.CfgStore.PfxFolder = pfxPath; } Console.WriteLine("Enter the password used for encryption of PFX files. The password provides some"); Console.WriteLine("additional protection, but should not be too relied upon. It will be stored in"); Console.WriteLine("the configuration file in plain text."); Console.Write("> "); AcmeEnvironment.CfgStore.PfxPassword = Console.ReadLine(); Console.WriteLine("Enter URL of the ACME server you are going to use:"); Console.WriteLine(" - To use Let's Encrypt production server, just press ENTER"); Console.WriteLine(" - To use Let's Encrypt staging server, type 'staging' and press ENTER"); Console.WriteLine(" - To use other server, type its URL and press ENTER"); Console.Write("> "); var acmeServer = Console.ReadLine(); if (string.IsNullOrWhiteSpace(acmeServer)) { AcmeEnvironment.CfgStore.ServerUri = WellKnownServers.LetsEncryptV2; } else if (acmeServer.Trim().Equals("staging", StringComparison.OrdinalIgnoreCase)) { AcmeEnvironment.CfgStore.ServerUri = WellKnownServers.LetsEncryptStagingV2; } else { AcmeEnvironment.CfgStore.ServerUri = new Uri(acmeServer); } Console.WriteLine(); } // Save to file AcmeEnvironment.SaveConfig(cfgFileName); // Ensure folders are created EnsureFolderExists(AcmeEnvironment.CfgStore.ChallengeFolder); EnsureFolderExists(AcmeEnvironment.CfgStore.PfxFolder); Console.WriteLine(); // Create web.config; InitWeb(cfgFileName, overwrite, verbose); // Create account using (var ac = new AutoAcmeContext(AcmeEnvironment.CfgStore.ServerUri)) { AcmeEnvironment.CfgStore.SerializedAccountData = ac.RegisterAndLogin(AcmeEnvironment.CfgStore.EmailAddress); } AcmeEnvironment.SaveConfig(cfgFileName); // Display farewell message Console.WriteLine("There are some additional options you can set in configuration file directly."); Console.WriteLine("See documentation at www.autoacme.net for reference."); }
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 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); } }