// all this would do is move the handler to the bottom, which is the last place you want it. //<handlers> // <remove name = "StaticFile" /> // < add name="StaticFile" path="*." verb="*" type="" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" scriptProcessor="" resourceType="Either" requireAccess="Read" allowPathInfo="false" preCondition="" responseBufferLimit="4194304" /> //</handlers> // this can work sometimes //<handlers> // <clear /> // <add name = ""StaticFile"" path=""*."" verb=""*"" type="""" modules=""StaticFileModule,DefaultDocumentModule,DirectoryListingModule"" scriptProcessor="""" resourceType=""Either"" requireAccess=""Read"" allowPathInfo=""false"" preCondition="""" responseBufferLimit=""4194304"" /> //</handlers> public override void BeforeAuthorize(Target target, string answerPath) { var directory = Path.GetDirectoryName(answerPath); var webConfigPath = Path.Combine(directory, "web.config"); Console.WriteLine($" Writing web.config to add extensionless mime type to {webConfigPath}"); File.WriteAllText(webConfigPath, webConfig); }
public override void BeforeAuthorize(Target target, string answerPath, string token) { var directory = Path.GetDirectoryName(answerPath); var webConfigPath = Path.Combine(directory, "web.config"); Console.WriteLine($" Writing web.config to add extensionless mime type to {webConfigPath}"); Log.Information("Writing web.config to add extensionless mime type to {webConfigPath}", webConfigPath); File.Copy(_sourceFilePath, webConfigPath, true); }
public override void OnAuthorizeFail(Target target) { Console.WriteLine(@" This could be caused by IIS not being setup to handle extensionless static files. Here's how to fix that: 1. In IIS manager goto Site/Server->Handler Mappings->View Ordered List 2. Move the StaticFile mapping above the ExtensionlessUrlHandler mappings. (like this http://i.stack.imgur.com/nkvrL.png)"); }
public override void BeforeAuthorize(Target target, string answerPath, string token) { answerPath = answerPath.Remove((answerPath.Length - token.Length), token.Length); var webConfigPath = Path.Combine(answerPath, "web.config"); Console.WriteLine($" Writing web.config to add extensionless mime type to {webConfigPath}"); Log.Information("Writing web.config to add extensionless mime type to {webConfigPath}", webConfigPath); Upload(webConfigPath, File.ReadAllText(_sourceFilePath)); }
public override void PrintMenu() { if (!String.IsNullOrEmpty(Program.Options.ManualHost)) { var target = new Target() { Host = Program.Options.ManualHost, WebRootPath = Program.Options.WebRoot, PluginName = Name }; Program.Auto(target); Environment.Exit(0); } Console.WriteLine(" M: Generate a certificate manually."); }
public override void Renew(Target target) { var auth = Program.Authorize(target); if (auth.Status == "valid") { var pfxFilename = Program.GetCertificate(target); Console.WriteLine(""); Console.WriteLine($"You can find the certificate at {pfxFilename}"); Log.Information("You can find the certificate at {pfxFilename}"); } }
public override void OnAuthorizeFail(Target target) { Console.WriteLine(@" This could be caused by IIS not being setup to handle extensionless static files. Here's how to fix that: 1. In IIS manager goto Site/Server->Handler Mappings->View Ordered List 2. Move the StaticFile mapping above the ExtensionlessUrlHandler mappings. (like this http://i.stack.imgur.com/nkvrL.png) 3. If you need to make changes to your web.config file, update the one at " + sourceFilePath); }
public override void HandleMenuResponse(string response, List<Target> targets) { if (response == "m") { Console.Write("Enter a host name: "); var hostName = Console.ReadLine(); string[] alternativeNames = null; List<string> sanList = null; if (Program.Options.San) { Console.Write("Enter all Alternative Names seperated by a comma "); // Copied from http://stackoverflow.com/a/16638000 int BufferSize = 16384; Stream inputStream = Console.OpenStandardInput(BufferSize); Console.SetIn(new StreamReader(inputStream, Console.InputEncoding, false, BufferSize)); var sanInput = Console.ReadLine(); alternativeNames = sanInput.Split(','); sanList = new List<string>(alternativeNames); } Console.Write("Enter a site path (the web root of the host for http authentication): "); var physicalPath = Console.ReadLine(); if (sanList == null || sanList.Count <= 100) { var target = new Target() { Host = hostName, WebRootPath = physicalPath, PluginName = Name, AlternativeNames = sanList }; Auto(target); } else { Console.WriteLine( $" You entered too many hosts for a SAN certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); Log.Error( "You entered too many hosts for a San certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); } } }
public override void HandleMenuResponse(string response, List<Target> targets) { if (response == "m") { Console.Write("Enter a host name: "); var hostName = Console.ReadLine(); // TODO: pull an existing host from the settings to default this value Console.Write("Enter a site path (the web root of the host for http authentication): "); var physicalPath = Console.ReadLine(); // TODO: make a system where they can execute a program/batch file to update whatever they need after install. var target = new Target() { Host = hostName, WebRootPath = physicalPath, PluginName = Name }; Program.Auto(target); } }
public override void HandleMenuResponse(string response, List<Target> targets) { if (response == "m") { Console.Write("Enter a host name: "); var hostName = Console.ReadLine(); string[] alternativeNames = null; if (Program.Options.San) { Console.Write("Enter all Alternative Names seperated by a comma "); Console.SetIn(new System.IO.StreamReader(Console.OpenStandardInput(8192))); var sanInput = Console.ReadLine(); alternativeNames = sanInput.Split(','); } // TODO: pull an existing host from the settings to default this value Console.Write("Enter a site path (the web root of the host for http authentication): "); var physicalPath = Console.ReadLine(); // TODO: make a system where they can execute a program/batch file to update whatever they need after install. List<string> sanList = new List<string>(alternativeNames); if (sanList.Count <= 100) { var target = new Target() { Host = hostName, WebRootPath = physicalPath, PluginName = Name, AlternativeNames = sanList }; Program.Auto(target); } else { Console.WriteLine( $" You entered too many hosts for a SAN certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); Log.Error( "You entered too many hosts for a San certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); } } }
public override void Install(Target target, string pfxFilename, X509Store store, X509Certificate2 certificate) { if (!string.IsNullOrWhiteSpace(Program.Options.Script) && !string.IsNullOrWhiteSpace(Program.Options.ScriptParameters)) { var parameters = string.Format(Program.Options.ScriptParameters, target.Host, Properties.Settings.Default.PFXPassword, pfxFilename, store.Name, certificate.FriendlyName, certificate.Thumbprint); Console.WriteLine($" Running {Program.Options.Script} with {parameters}"); Log.Information("Running {Script} with {parameters}", Program.Options.Script, parameters); Process.Start(Program.Options.Script, parameters); } else if (!string.IsNullOrWhiteSpace(Program.Options.Script)) { Console.WriteLine($" Running {Program.Options.Script}"); Log.Information("Running {Script}", Program.Options.Script); Process.Start(Program.Options.Script); } else { Console.WriteLine(" WARNING: Unable to configure server software."); } }
public override void Install(Target target, string pfxFilename, X509Store store, X509Certificate2 certificate) { using (var iisManager = new ServerManager()) { var site = GetSite(target, iisManager); var existingBinding = (from b in site.Bindings where b.Host == target.Host && b.Protocol == "https" select b).FirstOrDefault(); if (existingBinding != null) { Console.WriteLine($" Updating Existing https Binding"); existingBinding.CertificateHash = certificate.GetCertHash(); existingBinding.CertificateStoreName = store.Name; } else { Console.WriteLine($" Adding https Binding"); var iisBinding = site.Bindings.Add(":443:" + target.Host, certificate.GetCertHash(), store.Name); iisBinding.Protocol = "https"; } Console.WriteLine($" Commiting binding changes to IIS"); iisManager.CommitChanges(); } }
public override void Install(Target target) { // This method with just the Target paramater is currently only used by Centralized SSL if (!string.IsNullOrWhiteSpace(Program.Options.Script) && !string.IsNullOrWhiteSpace(Program.Options.ScriptParameters)) { var parameters = string.Format(Program.Options.ScriptParameters, target.Host, Properties.Settings.Default.PFXPassword, Program.Options.CentralSslStore); Console.WriteLine($" Running {Program.Options.Script} with {parameters}"); Log.Information("Running {Script} with {parameters}", Program.Options.Script, parameters); Process.Start(Program.Options.Script, parameters); } else if (!string.IsNullOrWhiteSpace(Program.Options.Script)) { Console.WriteLine($" Running {Program.Options.Script}"); Log.Information("Running {Script}", Program.Options.Script); Process.Start(Program.Options.Script); } else { Console.WriteLine(" WARNING: Unable to configure server software."); } }
public override void Install(Target target, string pfxFilename, X509Store store, X509Certificate2 certificate) { Console.WriteLine(" WARNING: Installing is not supported for the Web Dav Plugin."); }
private Site GetSite(Target target, ServerManager iisManager) { foreach (var site in iisManager.Sites) { if (site.Id == target.SiteId) return site; } Log.Error("Unable to find IIS site ID # {SiteId} for binding {this}", target.SiteId, this); throw new System.Exception($"Unable to find IIS site ID #{target.SiteId} for binding {this}"); }
public override void Install(Target target, string pfxFilename, X509Store store, X509Certificate2 certificate) { // TODO: make a system where they can execute a program/batch file to update whatever they need after install. Console.WriteLine(" WARNING: Unable to configure server software."); }
//This doesn't take any certificate info to enable centralized ssl public override void Install(Target target) { try { using (var iisManager = new ServerManager()) { var site = GetSite(target, iisManager); List<string> hosts = new List<string>(); if (!Program.Options.San) { hosts.Add(target.Host); } hosts.AddRange(target.AlternativeNames); foreach (var host in hosts) { var existingBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "https" select b) .FirstOrDefault(); if (!(_iisVersion.Major >= 8)) { Log.Error("You aren't using IIS 8 or greater, so centralized SSL is not supported"); //Not using IIS 8+ so can't set centralized certificates throw new InvalidOperationException( "You aren't using IIS 8 or greater, so centralized SSL is not supported"); } else if (existingBinding != null) { if (!Program.Options.KeepExisting) { Console.WriteLine($" Removing Existing https Binding"); Log.Information("Removing Existing https Binding"); site.Bindings.Remove(existingBinding); Console.WriteLine($" Adding https Binding"); Log.Information("Adding https Binding"); var iisBinding = site.Bindings.Add(existingBinding.BindingInformation, "https"); iisBinding.SetAttributeValue("sslFlags", 3); // Enable Centralized Certificate Store with SNI } else if (existingBinding.GetAttributeValue("sslFlags").ToString() != "3") { Console.WriteLine($" Updating Existing https Binding"); Log.Information("Updating Existing https Binding"); //IIS 8+ and not using centralized SSL with SNI and not replacing binding existingBinding.CertificateStoreName = null; existingBinding.CertificateHash = null; existingBinding.SetAttributeValue("sslFlags", 3); } else { Console.WriteLine("Not updating binding, see log for details"); Log.Information( "You specified Central SSL, have an existing binding, aren't replacing the binding, and the existing binding is using Central SSL with SNI, so there is nothing to update for this binding"); } } else { Console.WriteLine($" Adding Central SSL https Binding"); Log.Information("Adding Central SSL https Binding"); var existingHTTPBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "http" select b) .FirstOrDefault(); if (existingHTTPBinding != null) //This had been a fix for the multiple site San cert, now it's a precaution against erroring out { string HTTPEndpoint = existingHTTPBinding.EndPoint.ToString(); string IP = HTTPEndpoint.Remove(HTTPEndpoint.IndexOf(':'), (HTTPEndpoint.Length - HTTPEndpoint.IndexOf(':'))); if (IP == "0.0.0.0") { IP = ""; //Remove the IP if it is 0.0.0.0 That happens if an IP wasn't set on the HTTP site and it used any available IP } var iisBinding = site.Bindings.Add(IP + ":443:" + host, "https"); iisBinding.SetAttributeValue("sslFlags", 3); // Enable Centralized Certificate Store with SNI } else { Log.Warning("No HTTP binding for {host} on {name}", host, site.Name); } } } Console.WriteLine($" Committing binding changes to IIS"); Log.Information("Committing binding changes to IIS"); iisManager.CommitChanges(); } } catch (Exception ex) { Console.WriteLine("Error Setting Binding: " + ex.Message.ToString()); Log.Error("Error Setting Binding {@ex}", ex); throw new InvalidProgramException(ex.Message.ToString()); } }
public override void Renew(Target target) { // TODO: make a system where they can execute a program/batch file to update whatever they need after install. // This method with just the Target paramater is currently only used by Centralized SSL Console.WriteLine(" WARNING: Unable to renew."); }
public override void Install(Target target, string pfxFilename, X509Store store, X509Certificate2 certificate) { using (var iisManager = new ServerManager()) { var site = GetSite(target, iisManager); List<string> hosts = new List<string>(); if (!Program.Options.San) { hosts.Add(target.Host); } if (target.AlternativeNames != null) { if (target.AlternativeNames.Count > 0) { hosts.AddRange(target.AlternativeNames); } } foreach (var host in hosts) { var existingBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "https" select b).FirstOrDefault(); if (existingBinding != null) { if (!Program.Options.KeepExisting) { Console.WriteLine($" Removing Existing https Binding"); Log.Information("Removing Existing https Binding"); site.Bindings.Remove(existingBinding); Console.WriteLine($" Adding https Binding"); Log.Information("Adding https Binding"); var iisBinding = site.Bindings.Add(existingBinding.BindingInformation, certificate.GetCertHash(), store.Name); iisBinding.Protocol = "https"; if (_iisVersion.Major >= 8) iisBinding.SetAttributeValue("sslFlags", 1); // Enable SNI support } else { Console.WriteLine($" Updating Existing https Binding"); Log.Information("Updating Existing https Binding"); existingBinding.CertificateStoreName = store.Name; existingBinding.CertificateHash = certificate.GetCertHash(); } } else { Console.WriteLine($" Adding https Binding"); Log.Information("Adding https Binding"); var existingHTTPBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "http" select b) .FirstOrDefault(); if (existingHTTPBinding != null) //This had been a fix for the multiple site San cert, now it's just a precaution against erroring out { string HTTPEndpoint = existingHTTPBinding.EndPoint.ToString(); string IP = HTTPEndpoint.Remove(HTTPEndpoint.IndexOf(':'), (HTTPEndpoint.Length - HTTPEndpoint.IndexOf(':'))); if (IP == "0.0.0.0") { IP = ""; //Remove the IP if it is 0.0.0.0 That happens if an IP wasn't set on the HTTP site and it used any available IP } var iisBinding = site.Bindings.Add(IP + ":443:" + host, certificate.GetCertHash(), store.Name); iisBinding.Protocol = "https"; if (_iisVersion.Major >= 8) iisBinding.SetAttributeValue("sslFlags", 1); // Enable SNI support } else { Log.Warning("No HTTP binding for {host} on {name}", host, site.Name); } } } Console.WriteLine($" Committing binding changes to IIS"); Log.Information("Committing binding changes to IIS"); iisManager.CommitChanges(); } }
/// <summary> /// Steps to take on succesful (re)authorization /// </summary> /// <param name="binding"></param> public static RenewResult OnAutoSuccess(Target binding) { RenewResult result = new RenewResult(new Exception("Unknown error after validation")); try { var scheduled = _renewalService.Find(binding); var oldCertificate = FindCertificate(scheduled); var newCertificate = _certificateService.RequestCertificate(binding); var newCertificatePfx = new FileInfo(_certificateService.PfxFilePath(binding)); result = new RenewResult(newCertificate); if (_options.Test && !_options.Renew && !_input.PromptYesNo($"Do you want to install the certificate?")) { return(result); } SaveCertificate(binding.GetHosts(true), newCertificate, newCertificatePfx); if (_options.Renew || !_options.Test || _input.PromptYesNo($"Do you want to add/update the certificate to your server software?")) { _log.Information("Installing SSL certificate in server software"); if (_options.CentralSsl) { binding.Plugin.Install(binding); } else { binding.Plugin.Install(binding, newCertificatePfx.FullName, _certificateStoreService.DefaultStore, newCertificate, oldCertificate); } if (!_options.KeepExisting && oldCertificate != null) { DeleteCertificate(oldCertificate.Thumbprint); } } if (!_options.Renew && (scheduled != null || !_options.Test || _input.PromptYesNo($"Do you want to automatically renew this certificate in {_renewalService.RenewalPeriod} days? This will add a task scheduler task."))) { _renewalService.CreateOrUpdate(binding, result); } return(result); } catch (Exception ex) { // Result might still contain the Thumbprint of the certificate // that was requested and (partially? installed, which might help // with debugging HandleException(ex); result.Success = false; result.ErrorMessage = ex.Message; } return(result); }
public override void Install(Target target) { // This method with just the Target paramater is currently only used by Centralized SSL Console.WriteLine(" WARNING: Central SSL is not supported for the Web Dav Plugin."); }
public override void Renew(Target target) { Console.WriteLine(" WARNING: Renewal is not supported for the FTP Plugin."); }
public static AuthorizationState Authorize(Target target) { List <string> identifiers = target.GetHosts(false); List <AuthorizationState> authStatus = new List <AuthorizationState>(); foreach (var identifier in identifiers) { var authzState = _client.AuthorizeIdentifier(identifier); if (authzState.Status == "valid" && !_options.Test) { _log.Information("Cached authorization result: {Status}", authzState.Status); authStatus.Add(authzState); } else { var validation = target.GetValidationPlugin(); if (validation == null) { return(new AuthorizationState { Status = "invalid" }); } _log.Information("Authorizing {dnsIdentifier} using {challengeType} validation ({name})", identifier, validation.ChallengeType, validation.Name); var challenge = _client.DecodeChallenge(authzState, validation.ChallengeType); var cleanUp = validation.PrepareChallenge(target, challenge, identifier, _options, _input); try { _log.Debug("Submitting answer"); authzState.Challenges = new AuthorizeChallenge[] { challenge }; _client.SubmitChallengeAnswer(authzState, validation.ChallengeType, true); // have to loop to wait for server to stop being pending. // TODO: put timeout/retry limit in this loop while (authzState.Status == "pending") { _log.Debug("Refreshing authorization"); Thread.Sleep(4000); // this has to be here to give ACME server a chance to think var newAuthzState = _client.RefreshIdentifierAuthorization(authzState); if (newAuthzState.Status != "pending") { authzState = newAuthzState; } } _log.Information("Authorization result: {Status}", authzState.Status); authStatus.Add(authzState); } finally { cleanUp(authzState); } } } foreach (var authState in authStatus) { if (authState.Status != "valid") { return(authState); } } return(new AuthorizationState { Status = "valid" }); }
const float renewalPeriod = 60; // can't easily make this a command line option since it would have to be saved public static void ScheduleRenewal(Target target) { EnsureTaskScheduler(); var renewals = settings.LoadRenewals(); foreach (var existing in from r in renewals.ToArray() where r.Binding.Host == target.Host select r) { Console.WriteLine($" Removing existing scheduled renewal {existing}"); Log.Information("Removing existing scheduled renewal {existing}", existing); renewals.Remove(existing); } var result = new ScheduledRenewal() { Binding = target, CentralSSL = Options.CentralSSLStore, SAN = Options.SAN.ToString(), Date = DateTime.UtcNow.AddDays(renewalPeriod) }; renewals.Add(result); settings.SaveRenewals(renewals); Console.WriteLine($" Renewal Scheduled {result}"); Log.Information("Renewal Scheduled {result}", result); }
public static void InstallCertificate(Target binding, string pfxFilename, out X509Store store, out X509Certificate2 certificate) { try { store = new X509Store("WebHosting", StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); } catch (CryptographicException) { store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); } Console.WriteLine($" Opened Certificate Store \"{store.Name}\""); Log.Information("Opened Certificate Store {Name}", store.Name); certificate = null; try { // See http://paulstovell.com/blog/x509certificate2 certificate = new X509Certificate2(pfxFilename, Properties.Settings.Default.PFXPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); certificate.FriendlyName = $"{binding.Host} {DateTime.Now.ToString(Properties.Settings.Default.FileDateFormat)}"; Log.Debug("{FriendlyName}", certificate.FriendlyName); Console.WriteLine($" Adding Certificate to Store"); Log.Information("Adding Certificate to Store"); store.Add(certificate); Console.WriteLine($" Closing Certificate Store"); Log.Information("Closing Certificate Store"); } catch (Exception ex) { Log.Error("Error saving certificate {@ex}", ex); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Error saving certificate: {ex.Message.ToString()}"); Console.ResetColor(); } store.Close(); }
public static void Auto(Target binding) { var auth = Authorize(binding); if (auth.Status == "valid") { var pfxFilename = GetCertificate(binding); if (Options.Test && !Options.Renew) { Console.WriteLine($"\nDo you want to install the .pfx into the Certificate Store/ Central SSL Store? (Y/N) "); if (!PromptYesNo()) return; } X509Store store; X509Certificate2 certificate; if (!CentralSSL) { Log.Information("Installing Non-Central SSL Certificate in the certificate store"); InstallCertificate(binding, pfxFilename, out store, out certificate); if (Options.Test && !Options.Renew) { Console.WriteLine($"\nDo you want to add/update the certificate to your server software? (Y/N) "); if (!PromptYesNo()) return; } Log.Information("Installing Non-Central SSL Certificate in server software"); binding.Plugin.Install(binding, pfxFilename, store, certificate); } else if (!Options.Renew) { //If it is using centralized SSL and renewing, it doesn't need to change the //binding since just the certificate needs to be updated at the central ssl path Log.Information("Updating new Central SSL Certificate"); binding.Plugin.Install(binding); } if (Options.Test && !Options.Renew) { Console.WriteLine($"\nDo you want to automatically renew this certificate in 60 days? This will add a task scheduler task. (Y/N) "); if (!PromptYesNo()) return; } if (!Options.Renew) { Log.Information("Adding renewal for {binding}", binding); ScheduleRenewal(binding); } } }
public static void InstallCertificate(Target binding, string pfxFilename, out X509Store store, out X509Certificate2 certificate) { try { store = new X509Store("WebHosting", StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); } catch (CryptographicException) { store = new X509Store(StoreName.My, StoreLocation.LocalMachine); store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite); } Console.WriteLine($" Opened Certificate Store \"{store.Name}\""); // See http://paulstovell.com/blog/x509certificate2 certificate = new X509Certificate2(pfxFilename, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); certificate.FriendlyName = $"{binding.Host} {DateTime.Now}"; Console.WriteLine($" Adding Certificate to Store"); store.Add(certificate); Console.WriteLine($" Closing Certificate Store"); store.Close(); }
public static string GetCertificate(Target binding) { var dnsIdentifier = binding.Host; var SANList = binding.AlternativeNames; List<string> allDnsIdentifiers = new List<string>(); if (!Options.SAN) { allDnsIdentifiers.Add(binding.Host); } if (binding.AlternativeNames != null) { allDnsIdentifiers.AddRange(binding.AlternativeNames); } var cp = CertificateProvider.GetProvider(); var rsaPkp = new RsaPrivateKeyParams(); try { if (Properties.Settings.Default.RSAKeyBits >= 1024) { rsaPkp.NumBits = Properties.Settings.Default.RSAKeyBits; Log.Debug("RSAKeyBits: {RSAKeyBits}", Properties.Settings.Default.RSAKeyBits); } else { Log.Warning("RSA Key Bits less than 1024 is not secure. Letting ACMESharp default key bits. http://openssl.org/docs/manmaster/crypto/RSA_generate_key_ex.html"); } } catch (Exception ex) { Log.Warning("Unable to set RSA Key Bits, Letting ACMESharp default key bits, Error: {@ex}", ex); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine($"Unable to set RSA Key Bits, Letting ACMESharp default key bits, Error: {ex.Message.ToString()}"); Console.ResetColor(); } var rsaKeys = cp.GeneratePrivateKey(rsaPkp); var csrDetails = new CsrDetails { CommonName = dnsIdentifier, }; if(SANList != null) { if (SANList.Count > 0) { csrDetails.AlternativeNames = SANList; } } var csrParams = new CsrParams { Details = csrDetails, }; var csr = cp.GenerateCsr(csrParams, rsaKeys, Crt.MessageDigest.SHA256); byte[] derRaw; using (var bs = new MemoryStream()) { cp.ExportCsr(csr, EncodingFormat.DER, bs); derRaw = bs.ToArray(); } var derB64u = JwsHelper.Base64UrlEncode(derRaw); Console.WriteLine($"\nRequesting Certificate"); Log.Information("Requesting Certificate"); var certRequ = client.RequestCertificate(derB64u); Log.Debug("certRequ {@certRequ}", certRequ); Console.WriteLine($" Request Status: {certRequ.StatusCode}"); Log.Information("Request Status: {StatusCode}", certRequ.StatusCode); if (certRequ.StatusCode == System.Net.HttpStatusCode.Created) { var keyGenFile = Path.Combine(certificatePath, $"{dnsIdentifier}-gen-key.json"); var keyPemFile = Path.Combine(certificatePath, $"{dnsIdentifier}-key.pem"); var csrGenFile = Path.Combine(certificatePath, $"{dnsIdentifier}-gen-csr.json"); var csrPemFile = Path.Combine(certificatePath, $"{dnsIdentifier}-csr.pem"); var crtDerFile = Path.Combine(certificatePath, $"{dnsIdentifier}-crt.der"); var crtPemFile = Path.Combine(certificatePath, $"{dnsIdentifier}-crt.pem"); string crtPfxFile = null; if (!CentralSSL) { crtPfxFile = Path.Combine(certificatePath, $"{dnsIdentifier}-all.pfx"); } else { crtPfxFile = Path.Combine(Options.CentralSSLStore, $"{dnsIdentifier}.pfx"); } using (var fs = new FileStream(keyGenFile, FileMode.Create)) cp.SavePrivateKey(rsaKeys, fs); using (var fs = new FileStream(keyPemFile, FileMode.Create)) cp.ExportPrivateKey(rsaKeys, EncodingFormat.PEM, fs); using (var fs = new FileStream(csrGenFile, FileMode.Create)) cp.SaveCsr(csr, fs); using (var fs = new FileStream(csrPemFile, FileMode.Create)) cp.ExportCsr(csr, EncodingFormat.PEM, fs); Console.WriteLine($" Saving Certificate to {crtDerFile}"); Log.Information("Saving Certificate to {crtDerFile}", crtDerFile); using (var file = File.Create(crtDerFile)) certRequ.SaveCertificate(file); Crt crt; using (FileStream source = new FileStream(crtDerFile, FileMode.Open), target = new FileStream(crtPemFile, FileMode.Create)) { crt = cp.ImportCertificate(EncodingFormat.DER, source); cp.ExportCertificate(crt, EncodingFormat.PEM, target); } // To generate a PKCS#12 (.PFX) file, we need the issuer's public certificate var isuPemFile = GetIssuerCertificate(certRequ, cp); Log.Debug("CentralSSL {CentralSSL} SAN {SAN}", CentralSSL.ToString(), Options.SAN.ToString()); if(CentralSSL && Options.SAN) { foreach (var host in allDnsIdentifiers) { Console.WriteLine($"Host: {host}"); crtPfxFile = Path.Combine(Options.CentralSSLStore, $"{host}.pfx"); Console.WriteLine($" Saving Certificate to {crtPfxFile}"); Log.Information("Saving Certificate to {crtPfxFile}", crtPfxFile); using (FileStream source = new FileStream(isuPemFile, FileMode.Open), target = new FileStream(crtPfxFile, FileMode.Create)) { try { var isuCrt = cp.ImportCertificate(EncodingFormat.PEM, source); cp.ExportArchive(rsaKeys, new[] { crt, isuCrt }, ArchiveFormat.PKCS12, target, Properties.Settings.Default.PFXPassword); } catch (Exception ex) { Log.Error("Error exporting archive {@ex}", ex); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Error exporting archive: {ex.Message.ToString()}"); Console.ResetColor(); } } } } else //Central SSL and SAN need to save the cert for each hostname { Console.WriteLine($" Saving Certificate to {crtPfxFile}"); Log.Information("Saving Certificate to {crtPfxFile}", crtPfxFile); using (FileStream source = new FileStream(isuPemFile, FileMode.Open), target = new FileStream(crtPfxFile, FileMode.Create)) { try { var isuCrt = cp.ImportCertificate(EncodingFormat.PEM, source); cp.ExportArchive(rsaKeys, new[] { crt, isuCrt }, ArchiveFormat.PKCS12, target, Properties.Settings.Default.PFXPassword); } catch (Exception ex) { Log.Error("Error exporting archive {@ex}", ex); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine($"Error exporting archive: {ex.Message.ToString()}"); Console.ResetColor(); } } } cp.Dispose(); return crtPfxFile; } Log.Error("Request status = {StatusCode}", certRequ.StatusCode); throw new Exception($"Request status = {certRequ.StatusCode}"); }
public static string GetCertificate(Target binding) { var dnsIdentifier = binding.Host; var cp = CertificateProvider.GetProvider(); var rsaPkp = new RsaPrivateKeyParams(); var rsaKeys = cp.GeneratePrivateKey(rsaPkp); var csrDetails = new CsrDetails { CommonName = dnsIdentifier, }; var csrParams = new CsrParams { Details = csrDetails, }; var csr = cp.GenerateCsr(csrParams, rsaKeys, Crt.MessageDigest.SHA256); byte[] derRaw; using (var bs = new MemoryStream()) { cp.ExportCsr(csr, EncodingFormat.DER, bs); derRaw = bs.ToArray(); } var derB64u = JwsHelper.Base64UrlEncode(derRaw); Console.WriteLine($"\nRequesting Certificate"); var certRequ = client.RequestCertificate(derB64u); Console.WriteLine($" Request Status: {certRequ.StatusCode}"); //Console.WriteLine($"Refreshing Cert Request"); //client.RefreshCertificateRequest(certRequ); if (certRequ.StatusCode == System.Net.HttpStatusCode.Created) { var keyGenFile = Path.Combine(configPath, $"{dnsIdentifier}-gen-key.json"); var keyPemFile = Path.Combine(configPath, $"{dnsIdentifier}-key.pem"); var csrGenFile = Path.Combine(configPath, $"{dnsIdentifier}-gen-csr.json"); var csrPemFile = Path.Combine(configPath, $"{dnsIdentifier}-csr.pem"); var crtDerFile = Path.Combine(configPath, $"{dnsIdentifier}-crt.der"); var crtPemFile = Path.Combine(configPath, $"{dnsIdentifier}-crt.pem"); string crtPfxFile = null; if (!CentralSSL) { crtPfxFile = Path.Combine(configPath, $"{dnsIdentifier}-all.pfx"); } else { crtPfxFile = Path.Combine(Options.CentralSSLStore, $"{dnsIdentifier}.pfx"); } using (var fs = new FileStream(keyGenFile, FileMode.Create)) cp.SavePrivateKey(rsaKeys, fs); using (var fs = new FileStream(keyPemFile, FileMode.Create)) cp.ExportPrivateKey(rsaKeys, EncodingFormat.PEM, fs); using (var fs = new FileStream(csrGenFile, FileMode.Create)) cp.SaveCsr(csr, fs); using (var fs = new FileStream(csrPemFile, FileMode.Create)) cp.ExportCsr(csr, EncodingFormat.PEM, fs); Console.WriteLine($" Saving Certificate to {crtDerFile}"); using (var file = File.Create(crtDerFile)) certRequ.SaveCertificate(file); Crt crt; using (FileStream source = new FileStream(crtDerFile, FileMode.Open), target = new FileStream(crtPemFile, FileMode.Create)) { crt = cp.ImportCertificate(EncodingFormat.DER, source); cp.ExportCertificate(crt, EncodingFormat.PEM, target); } // To generate a PKCS#12 (.PFX) file, we need the issuer's public certificate var isuPemFile = GetIssuerCertificate(certRequ, cp); Console.WriteLine($" Saving Certificate to {crtPfxFile} (with no password set)"); using (FileStream source = new FileStream(isuPemFile, FileMode.Open), target = new FileStream(crtPfxFile, FileMode.Create)) { var isuCrt = cp.ImportCertificate(EncodingFormat.PEM, source); cp.ExportArchive(rsaKeys, new[] { crt, isuCrt }, ArchiveFormat.PKCS12, target); } cp.Dispose(); return crtPfxFile; } throw new Exception($"Request status = {certRequ.StatusCode}"); }
public static AuthorizationState Authorize(Target target) { List<string> dnsIdentifiers = new List<string>(); if (!Options.SAN) { dnsIdentifiers.Add(target.Host); } if(target.AlternativeNames != null) { dnsIdentifiers.AddRange(target.AlternativeNames); } List<AuthorizationState> authStatus = new List<AuthorizationState>(); foreach (var dnsIdentifier in dnsIdentifiers) { //var dnsIdentifier = target.Host; var webRootPath = target.WebRootPath; Console.WriteLine($"\nAuthorizing Identifier {dnsIdentifier} Using Challenge Type {AcmeProtocol.CHALLENGE_TYPE_HTTP}"); Log.Information("Authorizing Identifier {dnsIdentifier} Using Challenge Type {CHALLENGE_TYPE_HTTP}", dnsIdentifier, AcmeProtocol.CHALLENGE_TYPE_HTTP); var authzState = client.AuthorizeIdentifier(dnsIdentifier); var challenge = client.DecodeChallenge(authzState, AcmeProtocol.CHALLENGE_TYPE_HTTP); var httpChallenge = challenge.Challenge as HttpChallenge; // We need to strip off any leading '/' in the path var filePath = httpChallenge.FilePath; if (filePath.StartsWith("/", StringComparison.OrdinalIgnoreCase)) filePath = filePath.Substring(1); var answerPath = Environment.ExpandEnvironmentVariables(Path.Combine(webRootPath, filePath)); Console.WriteLine($" Writing challenge answer to {answerPath}"); Log.Information("Writing challenge answer to {answerPath}", answerPath); var directory = Path.GetDirectoryName(answerPath); Directory.CreateDirectory(directory); File.WriteAllText(answerPath, httpChallenge.FileContent); target.Plugin.BeforeAuthorize(target, answerPath); var answerUri = new Uri(httpChallenge.FileUrl); Console.WriteLine($" Answer should now be browsable at {answerUri}"); Log.Information("Answer should now be browsable at {answerUri}", answerUri); try { Console.WriteLine(" Submitting answer"); Log.Information("Submitting answer"); authzState.Challenges = new AuthorizeChallenge[] { challenge }; client.SubmitChallengeAnswer(authzState, AcmeProtocol.CHALLENGE_TYPE_HTTP, true); // have to loop to wait for server to stop being pending. // TODO: put timeout/retry limit in this loop while (authzState.Status == "pending") { Console.WriteLine(" Refreshing authorization"); Log.Information("Refreshing authorization"); Thread.Sleep(4000); // this has to be here to give ACME server a chance to think var newAuthzState = client.RefreshIdentifierAuthorization(authzState); if (newAuthzState.Status != "pending") authzState = newAuthzState; } Console.WriteLine($" Authorization Result: {authzState.Status}"); Log.Information("Auth Result {Status}", authzState.Status); if (authzState.Status == "invalid") { Log.Error("Authorization Failed {Status}", authzState.Status); Log.Debug("Full Error Details {@authzState}", authzState); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("\n******************************************************************************"); Console.WriteLine($"The ACME server was probably unable to reach {answerUri}"); Log.Error("Unable to reach {answerUri}", answerUri); Console.WriteLine("\nCheck in a browser to see if the answer file is being served correctly."); target.Plugin.OnAuthorizeFail(target); Console.WriteLine("\n******************************************************************************"); Console.ResetColor(); } authStatus.Add(authzState); } finally { if (authzState.Status == "valid") { Console.WriteLine(" Deleting answer"); Log.Information("Deleting answer"); File.Delete(answerPath); } } } foreach (var authState in authStatus) { if(authState.Status != "valid") { return authState; } } return new AuthorizationState { Status = "valid" }; }
public static AuthorizationState Authorize(Target target) { var dnsIdentifier = target.Host; var webRootPath = target.WebRootPath; Console.WriteLine($"\nAuthorizing Identifier {dnsIdentifier} Using Challenge Type {AcmeProtocol.CHALLENGE_TYPE_HTTP}"); var authzState = client.AuthorizeIdentifier(dnsIdentifier); var challenge = client.GenerateAuthorizeChallengeAnswer(authzState, AcmeProtocol.CHALLENGE_TYPE_HTTP); var answerPath = Environment.ExpandEnvironmentVariables(Path.Combine(webRootPath, challenge.ChallengeAnswer.Key)); Console.WriteLine($" Writing challenge answer to {answerPath}"); var directory = Path.GetDirectoryName(answerPath); Directory.CreateDirectory(directory); File.WriteAllText(answerPath, challenge.ChallengeAnswer.Value); target.Plugin.BeforeAuthorize(target, answerPath); var answerUri = new Uri(new Uri("http://" + dnsIdentifier), challenge.ChallengeAnswer.Key); Console.WriteLine($" Answer should now be browsable at {answerUri}"); try { Console.WriteLine(" Submitting answer"); authzState.Challenges = new AuthorizeChallenge[] { challenge }; client.SubmitAuthorizeChallengeAnswer(authzState, AcmeProtocol.CHALLENGE_TYPE_HTTP, true); // have to loop to wait for server to stop being pending. // TODO: put timeout/retry limit in this loop while (authzState.Status == "pending") { Console.WriteLine(" Refreshing authorization"); Thread.Sleep(4000); // this has to be here to give ACME server a chance to think var newAuthzState = client.RefreshIdentifierAuthorization(authzState); if (newAuthzState.Status != "pending") authzState = newAuthzState; } Console.WriteLine($" Authorization Result: {authzState.Status}"); if (authzState.Status == "invalid") { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("\n******************************************************************************"); Console.WriteLine($"The ACME server was probably unable to reach {answerUri}"); Console.WriteLine("\nCheck in a browser to see if the answer file is being served correctly."); target.Plugin.OnAuthorizeFail(target); Console.WriteLine("\n******************************************************************************"); Console.ResetColor(); } //if (authzState.Status == "valid") //{ // var authPath = Path.Combine(configPath, dnsIdentifier + ".auth"); // Console.WriteLine($" Saving authorization record to: {authPath}"); // using (var authStream = File.Create(authPath)) // authzState.Save(authStream); //} return authzState; } finally { if (authzState.Status == "valid") { Console.WriteLine(" Deleting answer"); File.Delete(answerPath); } } }
//This doesn't take any certificate info to enable centralized ssl public override void Install(Target target) { try { using (var iisManager = new ServerManager()) { var site = GetSite(target, iisManager); List<string> hosts = new List<string>(); if (!Program.Options.SAN) { hosts.Add(target.Host); } hosts.AddRange(target.AlternativeNames); foreach (var host in hosts) { var existingBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "https" select b).FirstOrDefault(); if (existingBinding != null) { Console.WriteLine($" Updating Existing https Binding"); Log.Information("Updating Existing https Binding"); if (iisVersion.Major >= 8 && existingBinding.GetAttributeValue("sslFlags").ToString() != "2") { //IIS 8+ and not using centralized SSL existingBinding.CertificateStoreName = null; existingBinding.CertificateHash = null; existingBinding.SetAttributeValue("sslFlags", 2); } else if (!(iisVersion.Major >= 8)) { Log.Error("You aren't using IIS 8 or greater, so centralized SSL is not supported"); //Not using IIS 8+ so can't set centralized certificates throw new InvalidOperationException("You aren't using IIS 8 or greater, so centralized SSL is not supported"); } } else { Console.WriteLine($" Adding Central SSL https Binding"); Log.Information("Adding Central SSL https Binding"); var existingHTTPBinding = (from b in site.Bindings where b.Host == host && b.Protocol == "http" select b).FirstOrDefault(); string HTTPEndpoint = existingHTTPBinding.EndPoint.ToString(); string IP = HTTPEndpoint.Remove(HTTPEndpoint.IndexOf(':'), (HTTPEndpoint.Length - HTTPEndpoint.IndexOf(':'))); if (IP == "0.0.0.0") { IP = ""; //Remove the IP if it is 0.0.0.0 That happens if an IP wasn't set on the HTTP site and it used any available IP } var iisBinding = site.Bindings.Add(IP + ":443:" + host, "https"); if (iisVersion.Major >= 8) iisBinding.SetAttributeValue("sslFlags", 2); // Enable Centralized Certificate Store } } Console.WriteLine($" Committing binding changes to IIS"); Log.Information("Committing binding changes to IIS"); iisManager.CommitChanges(); } } catch (Exception ex) { Console.WriteLine("Error Setting Binding: " + ex.Message.ToString()); Log.Error("Error Setting Binding {@ex}", ex); throw new InvalidProgramException(ex.Message.ToString()); } }
public override void HandleMenuResponse(string response, List<Target> targets) { if (response == "w") { Console.Write("Enter a host name: "); var hostName = Console.ReadLine(); string[] alternativeNames = null; if (Program.Options.San) { Console.Write("Enter all Alternative Names seperated by a comma "); Console.SetIn(new System.IO.StreamReader(Console.OpenStandardInput(8192))); var sanInput = Console.ReadLine(); alternativeNames = sanInput.Split(','); } Console.WriteLine("Enter a site path (the web root of the host for http authentication)"); Console.WriteLine("Example, http://domain.com:80/"); Console.WriteLine("Example, https://@domain.com:443/"); Console.Write(": "); var webDavPath = Console.ReadLine(); Console.Write("Enter the WebDAV username: "******"Enter the WebDAV password: "******"valid") { var pfxFilename = Program.GetCertificate(target); Console.WriteLine(""); Console.WriteLine($"You can find the certificate at {pfxFilename}"); Log.Information("You can find the certificate at {pfxFilename}"); } } else { Console.WriteLine( $" You entered too many hosts for a SAN certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); Log.Error( "You entered too many hosts for a San certificate. Let's Encrypt currently has a maximum of 100 alternative names per certificate."); } } }