/// <summary> /// Removes specified files and logs it /// </summary> /// <param name="path"></param> private static void RemoveFileAndLog(AuthenticatedPFX pfx) { File.Delete(pfx.PfxFullPath); File.Delete(pfx.PemCertPath); File.Delete(pfx.PemKeyPath); _logger.Info($"Removed files from filesystem: {pfx.PfxFullPath}, {pfx.PemCertPath}, {pfx.PemKeyPath}"); }
/// <summary> /// Executes powershell script scriptFile /// </summary> /// <param name="scriptFile"></param> /// <param name="pfx"></param> /// <param name="pfxPassword"></param> /// <returns></returns> public static bool ExecutePowerShell(string scriptFile, AuthenticatedPFX pfx) { if (scriptFile == null) { return(false); } try { // First let's create the execution runspace RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create(); Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration); runspace.Open(); // Now we create the pipeline Pipeline pipeline = runspace.CreatePipeline(); // We create the script to execute with its arguments as a Command System.Management.Automation.Runspaces.Command myCommand = new System.Management.Automation.Runspaces.Command(scriptFile); CommandParameter pfxParam = new CommandParameter("pfx", pfx.PfxFullPath); myCommand.Parameters.Add(pfxParam); CommandParameter pfxPassParam = new CommandParameter("pfxPassword", pfx.PfxPassword); myCommand.Parameters.Add(pfxPassParam); // add the created Command to the pipeline pipeline.Commands.Add(myCommand); // and we invoke it var results = pipeline.Invoke(); logger.Info($"Executed script {scriptFile}."); return(true); } catch (Exception e) { logger.Error($"Could not execute {scriptFile}: {e.Message}"); return(false); } }
/// <summary> /// Retrieves the certificate from the ACME service. This method also generates the key and the CSR. /// </summary> /// <param name="commonName">the CN of the certificate to be requested</param> /// <param name="pathForPfx">Path where the resulting PFX/PKCS#12 file will be generated</param> /// <param name="pfxFriendlyName">Friendly name for the resulting PFX/PKCS#12</param> /// <returns>The name of the generated PFX/PKCS#12 file, or null in case of error</returns> public async Task <AuthenticatedPFX> RetrieveCertificate(IList <string> domains, string pathForPfx, string pfxFriendlyName) { try { if (_orderCtx == null) { throw new Exception("Do not call RetrieveCertificate before RegisterNewOrderAndVerify"); } if (!System.IO.Directory.Exists(pathForPfx)) { throw new Exception("Directory for PFX writing do not exists"); } InitCertes(); // Let's generate a new key (RSA is good enough IMHO) IKey certKey = KeyFactory.FromPem(Utils.GenerateRSAKeyAsPEM(_keySize)); // Then let's generate the CSR var csr = await _orderCtx.CreateCsr(certKey); csr.AddName("CN", domains[0]); csr.SubjectAlternativeNames = domains; // and finalize the ACME order var finalOrder = await _orderCtx.Finalize(csr.Generate()); // Now we can fetch the certificate CertificateChain cert = await _orderCtx.Download(); // We build the PFX/PKCS#12 and the cert/key as PEM var pfx = cert.ToPfx(certKey); var cer = cert.ToPem(); var key = certKey.ToPem(); pfx.AddIssuers(GetCACertChainFromStore()); var pfxBytes = pfx.Build(pfxFriendlyName, PfxPassword); var fileName = pathForPfx + "\\" + Guid.NewGuid().ToString(); var pfxName = fileName + ".pfx"; var cerPath = fileName + ".cer"; var keyPath = fileName + ".key"; // We write the PFX/PKCS#12 to file System.IO.File.WriteAllBytes(pfxName, pfxBytes); logger.Info($"Retrieved certificate from the CA. The certificate is in {pfxName}"); // We write the PEMs to corresponding files System.IO.File.WriteAllText(cerPath, cer); System.IO.File.WriteAllText(keyPath, key); AuthenticatedPFX authPFX = new AuthenticatedPFX(pfxName, PfxPassword, cerPath, keyPath); return(authPFX); } catch (Exception exp) { logger.Error($"Failed to retrieve certificate from CA: {ProcessCertesException(exp)}"); return(null); } }
static int Main(string[] args) { // Main parameters with their default values string taskName = null; _winCertesOptions = new WinCertesOptions(); if (!Utils.IsAdministrator()) { Console.WriteLine("WinCertes.exe must be launched as Administrator"); return(ERROR); } // Command line options handling and initialization stuff if (!HandleOptions(args)) { return(ERROR_INCORRECT_PARAMETER); } if (_periodic) { taskName = Utils.DomainsToFriendlyName(_domains); } InitWinCertesDirectoryPath(); Utils.ConfigureLogger(_winCertesPath); _config = new RegistryConfig(_extra); _winCertesOptions.WriteOptionsIntoConfiguration(_config); if (_show) { _winCertesOptions.displayOptions(_config); return(0); } // Reset is a full reset ! if (_reset) { IConfig baseConfig = new RegistryConfig(false); baseConfig.DeleteAllParameters(); Utils.DeleteScheduledTasks(); return(0); } // Initialization and renewal/revocation handling try { InitCertesWrapper(_winCertesOptions.ServiceUri, _winCertesOptions.Email); } catch (Exception e) { _logger.Error(e.Message); return(ERROR); } if (_winCertesOptions.Revoke > -1) { RevokeCert(_domains, _winCertesOptions.Revoke); return(0); } // default mode: enrollment/renewal. check if there's something to be done // note that in any case, we want to be able to set the scheduled task (won't do anything if taskName is null) if (!IsThereCertificateAndIsItToBeRenewed(_domains)) { Utils.CreateScheduledTask(taskName, _domains, _extra); return(0); } // Now the real stuff: we register the order for the domains, and have them validated by the ACME service IHTTPChallengeValidator httpChallengeValidator = HTTPChallengeValidatorFactory.GetHTTPChallengeValidator(_winCertesOptions.Standalone, _winCertesOptions.HttpPort, _winCertesOptions.WebRoot); IDNSChallengeValidator dnsChallengeValidator = DNSChallengeValidatorFactory.GetDNSChallengeValidator(_config); if ((httpChallengeValidator == null) && (dnsChallengeValidator == null)) { WriteErrorMessageWithUsage(_options, "Specify either an HTTP or a DNS validation method."); return(ERROR_INCORRECT_PARAMETER); } if (!(Task.Run(() => _certesWrapper.RegisterNewOrderAndVerify(_domains, httpChallengeValidator, dnsChallengeValidator)).GetAwaiter().GetResult())) { if (httpChallengeValidator != null) { httpChallengeValidator.EndAllChallengeValidations(); } return(ERROR); } if (httpChallengeValidator != null) { httpChallengeValidator.EndAllChallengeValidations(); } // We get the certificate from the ACME service var pfxName = Task.Run(() => _certesWrapper.RetrieveCertificate(_domains, _winCertesPath, Utils.DomainsToFriendlyName(_domains))).GetAwaiter().GetResult(); if (pfxName == null) { return(ERROR); } AuthenticatedPFX pfx = new AuthenticatedPFX(_winCertesPath + "\\" + pfxName, _certesWrapper.PfxPassword); CertificateStorageManager certificateStorageManager = new CertificateStorageManager(pfx, ((_winCertesOptions.Csp == null) && (!_winCertesOptions.noCsp))); // Let's process the PFX into Windows Certificate objet. certificateStorageManager.ProcessPFX(); // and we write its information to the WinCertes configuration RegisterCertificateIntoConfiguration(certificateStorageManager.Certificate, _domains); // Import the certificate into the Windows store if (!_winCertesOptions.noCsp) { certificateStorageManager.ImportCertificateIntoCSP(_winCertesOptions.Csp); } // Bind certificate to IIS Site (won't do anything if option is null) Utils.BindCertificateForIISSite(certificateStorageManager.Certificate, _winCertesOptions.BindName); // Execute PowerShell Script (won't do anything if option is null) Utils.ExecutePowerShell(_winCertesOptions.ScriptFile, pfx); // Create the AT task that will execute WinCertes periodically (won't do anything if taskName is null) Utils.CreateScheduledTask(taskName, _domains, _extra); // Let's delete the PFX file RemoveFileAndLog(pfx.PfxFullPath); return(0); }
/// <summary> /// Class constructor /// </summary> /// <param name="authenticatedPFX">the PFX that we will store</param> /// <param name="defaultCSP">do we use the default CSP to store the certificate?</param> public CertificateStorageManager(AuthenticatedPFX authenticatedPFX, bool defaultCSP) { AuthenticatedPFX = authenticatedPFX; Certificate = null; DefaultCSP = defaultCSP; }