Ejemplo n.º 1
0
        public void displayOptions(IConfig config)
        {
            if (!config.isThereConfigParam("accountKey"))
            {
                Console.WriteLine("WinCertes is not configured yet");
                return;
            }
            IDNSChallengeValidator dnsChallengeValidator = DNSChallengeValidatorFactory.GetDNSChallengeValidator(config);

            Console.WriteLine("Service URI:\t" + ((ServiceUri == null) ? Certes.Acme.WellKnownServers.LetsEncryptV2.ToString() : ServiceUri));
            Console.WriteLine("Account Email:\t" + Email);
            Console.WriteLine("Registered:\t" + (config.ReadIntParameter("registered") == 1 ? "yes" : "no"));
            if (dnsChallengeValidator != null)
            {
                Console.WriteLine("Auth. Mode:\tdns-01 validation");
            }
            else
            {
                Console.WriteLine("Auth. Mode:\t" + (Standalone ? "http-01 validation standalone" : "http-01 validation with external web server"));
                if (Standalone)
                {
                    Console.WriteLine("HTTP Port:\t" + HttpPort);
                }
                else
                {
                    Console.WriteLine("Web Root:\t" + WebRoot);
                }
            }
            Console.WriteLine("IIS Bind Name:\t" + (BindName ?? "none"));
            Console.WriteLine("Import in CSP:\t" + (config.isThereConfigParam("noCsp") ? "no" : "yes"));
            Console.WriteLine("PS Script File:\t" + (ScriptFile ?? "none"));
            Console.WriteLine("Renewal Delay:\t" + RenewalDelay + " days");
            Console.WriteLine("Task Scheduled:\t" + (Utils.IsScheduledTaskCreated() ? "yes" : "no"));
            Console.WriteLine("Cert Enrolled:\t" + (config.isThereConfigParam("certSerial") ? "yes" : "no"));
        }
        /// <summary>
        /// Builds the DNS Challenge validator. For now only ACME DNS is supported.
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public static IDNSChallengeValidator GetDNSChallengeValidator(IConfig config)
        {
            IDNSChallengeValidator challengeValidator = null;

            if (config.ReadStringParameter("DNSValidatorType") == null)
            {
                return(null);
            }
            if (config.ReadStringParameter("DNSValidatorType") == "acme-dns")
            {
                challengeValidator = new DNSChallengeAcmeDnsValidator(config);
            }
            if (config.ReadStringParameter("DNSValidatorType") == "win-dns")
            {
                challengeValidator = new DNSChallengeWinDnsValidator(config);
            }
            return(challengeValidator);
        }
        /// <summary>
        /// Builds the DNS Challenge validator. For now only ACME DNS is supported.
        /// </summary>
        /// <returns>challengeValidator instance</returns>
        public static IDNSChallengeValidator GetDNSChallengeValidator()
        {
            string dnsValidatorType = Program._winCertesOptions.DNSValidatorType;

            IDNSChallengeValidator challengeValidator = null;

            if (dnsValidatorType == null || dnsValidatorType.Length < 1)
            {
                return(null);
            }
            if (dnsValidatorType == "acme-dns")
            {
                challengeValidator = new DNSChallengeAcmeDnsValidator();
            }
            if (dnsValidatorType == "win-dns")
            {
                challengeValidator = new DNSChallengeWinDnsValidator();
            }
            return(challengeValidator);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Display the active options/settings for this Certificate as stored in the registry
        /// </summary>
        public void DisplayOptions()
        {
            _logger.Info("Displaying WinCertes current configuration:");
            _logger.Info("[{0}]", Registry.FullRegistryKey);
            IDNSChallengeValidator dnsChallengeValidator = DNSChallengeValidatorFactory.GetDNSChallengeValidator();
            string ui = ServiceUri.ToString();

            _logger.Info("Service URI:\t\t{0}", (ui == null) ? Certes.Acme.WellKnownServers.LetsEncryptV2.ToString() : ui);
            _logger.Info("Domain(s):\t\t{0}", Domains.Count > 0 ? string.Join(",", Domains) : "ERROR none specified");
            _logger.Info("Account Email:\t{0}", AccountEmail == null ? "ERROR not set" : AccountEmail);
            string accountKey = Registry.ReadStringParameter("AccountKey");

            _logger.Info("AccountKey:\t\t{0}", (accountKey == null || accountKey.Length < 1) ? "Account not registered" : "PrivateKey stored in registry");
            _logger.Info("Registered:\t\t{0}", Registry.ReadIntParameter("Registered") == 1 ? "yes" : "no");
            _logger.Info("Generated:\t\t{0}", Registry.ReadIntParameter("Generated") == 1 ? "yes" : "no");
            if (dnsChallengeValidator != null)
            {
                _logger.Info("Auth. Mode:\t\tdns-01 validation");
            }
            else
            {
                _logger.Info("Auth. Mode:\t\t{0}", Standalone ? "http-01 validation standalone" : "http-01 validation with external web server");
                if (Standalone)
                {
                    _logger.Info("HTTP Port:\t\t{0}", HttpPort);
                }
                else
                {
                    _logger.Info("Web Root:\t\t{0}", WebRoot != null ? WebRoot : Standalone ? "NA" : "ERROR: Missing");
                }
            }
            _logger.Info("IIS Bind Name:\t{0}", BindName ?? "none");
            _logger.Info("Import in CSP:\t{0}", Registry.IsThereConfigParam("noCsp") ? "no" : "yes");
            _logger.Info("PS Script File:\t{0}", ScriptFile ?? "none");
            _logger.Info("Renewal Delay:\t{0}", RenewalDelay + " days");
            _logger.Info("Task Scheduled:\t{0}", Utils.IsScheduledTaskCreated() ? "yes" : "no");
            _logger.Info("Cert Enrolled:\t{0}", Registry.IsThereConfigParam("certSerial") ? "yes" : "no");
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Validates a DNS challenge. Similar to HTTP Validation, but different because of DNSChallenge value which is signed by account key
        /// </summary>
        /// <param name="dnsChallenge"></param>
        /// <returns></returns>
        private async Task <bool> ValidateDNSChallenge(String domain, IChallengeContext dnsChallenge, IDNSChallengeValidator dnsChallengeValidator)
        {
            if (dnsChallenge == null)
            {
                throw new Exception("DNS Validation mode setup, but server returned no DNS challenge.");
            }
            // We get the resource fresh
            var dnsChallengeStatus = await dnsChallenge.Resource();

            // If it's invalid, we stop right away. Should not happen, but anyway...
            if (dnsChallengeStatus.Status == ChallengeStatus.Invalid)
            {
                throw new Exception("DNS challenge has an invalid status");
            }

            // Let's prepare for ACME-DNS validation
            var dnsValue = _acme.AccountKey.DnsTxt(dnsChallenge.Token);
            var dnsKey   = $"_acme-challenge.{domain}".Replace("*.", "");

            if (!dnsChallengeValidator.PrepareChallengeForValidation(dnsKey, dnsValue))
            {
                return(false);
            }

            // Now let's ping the ACME service to validate the challenge token
            Challenge challengeRes = await dnsChallenge.Validate();

            // We need to loop, because ACME service might need some time to validate the challenge token
            int retry = 0;

            while (((challengeRes.Status == ChallengeStatus.Pending) || (challengeRes.Status == ChallengeStatus.Processing)) && (retry < 10))
            {
                // We sleep 2 seconds between each request, to leave time to ACME service to refresh
                System.Threading.Thread.Sleep(2000);
                // We refresh the challenge object from ACME service
                challengeRes = await dnsChallenge.Resource();

                retry++;
            }

            // If challenge is Invalid, Pending or Processing, something went wrong...
            if (challengeRes.Status != ChallengeStatus.Valid)
            {
                return(false);
            }

            return(true);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Validates an Authorization, switching between DNS and HTTP challenges
        /// </summary>
        /// <param name="authz"></param>
        /// <param name="httpChallengeValidator"></param>
        /// <returns></returns>
        private async Task ValidateAuthz(IAuthorizationContext authz, IHTTPChallengeValidator httpChallengeValidator, IDNSChallengeValidator dnsChallengeValidator)
        {
            // For each authorization, get the challenges
            var allChallenges = await authz.Challenges();

            var res = await authz.Resource();

            if (dnsChallengeValidator != null)
            {
                // Get the DNS challenge
                var dnsChallenge = await authz.Dns();

                if (dnsChallenge != null)
                {
                    logger.Debug($"Initiating DNS Validation for {res.Identifier.Value}");
                    var resValidation = await ValidateDNSChallenge(res.Identifier.Value, dnsChallenge, dnsChallengeValidator);

                    if (!resValidation)
                    {
                        throw new Exception($"Could not validate DNS challenge:\n {dnsChallenge.Resource().Result.Error.Detail}");
                    }
                }
                else
                {
                    throw new Exception("DNS Challenge Validation set up, but server sent no DNS Challenge");
                }
            }
            else
            {
                // Get the HTTP challenge
                var httpChallenge = await authz.Http();

                if (httpChallenge != null)
                {
                    logger.Debug($"Initiating HTTP Validation for {res.Identifier.Value}");
                    var resValidation = await ValidateHTTPChallenge(httpChallenge, httpChallengeValidator);

                    if (!resValidation)
                    {
                        throw new Exception($"Could not validate HTTP challenge:\n {httpChallenge.Resource().Result.Error.Detail}");
                    }
                }
                else
                {
                    throw new Exception("HTTP Challenge Validation set up, but server sent no HTTP Challenge");
                }
            }
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Register a new order on the ACME service, for the specified domains. Challenges will be automatically verified.
        /// This method manages automatically the creation of necessary directory and files.
        /// </summary>
        /// <remarks>
        /// When using HTTP Validation, the ACME directory will access to http://__domain__/.well-known/acme-challenge/token, that should be served
        /// by a local web server when not using built-in, and translated into local path {challengeVerifyPath}\.well-known\acme-challenge\token.
        /// Important Note: currently WinCertes supports only http-01 validation mode, and dns-01 validation mode with limitations.
        /// </remarks>
        /// <param name="domains">The list of domains to be registered and validated</param>
        /// <param name="httpChallengeValidator">The object used for challenge validation</param>
        /// <returns>True if successful</returns>
        public async Task <bool> RegisterNewOrderAndVerify(IList <string> domains, IHTTPChallengeValidator httpChallengeValidator, IDNSChallengeValidator dnsChallengeValidator)
        {
            try {
                // Re-init to be sure to get a fresh Nonce
                InitCertes();

                // Creating the order
                _orderCtx = await _acme.NewOrder(domains);

                if (_orderCtx == null)
                {
                    throw new Exception("Could not create certificate order.");
                }

                // And fetching authorizations
                var orderAuthz = await _orderCtx.Authorizations();

                // Looping through authorizations
                foreach (IAuthorizationContext authz in orderAuthz)
                {
                    InitCertes();
                    await ValidateAuthz(authz, httpChallengeValidator, dnsChallengeValidator);
                }
                _options.CertificateChallenged = true;
                // If we are here, it means order was properly created, and authorizations & challenges were properly verified.
                logger.Info($"Generated orders and validated challenges for domains: {String.Join(",", domains)}");
                return(true);
            } catch (Exception exp) {
                logger.Debug(exp, "Error while trying to register and validate order");
                logger.Error($"Failed to register and validate order with CA: {ProcessCertesException(exp)}");
                _options.CertificateChallenged = false;
                return(false);
            }
        }
Ejemplo n.º 8
0
        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);
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Main programme
        /// </summary>
        /// <param name="args">WinCertes command line arguments</param>
        /// <returns>Zero if successul, error code otherwise</returns>
        private static int Main(string[] args)
        {
            // WinCertes Certificate path...
            InitWinCertesDirectoryPath();
            Utils.ConfigureLogger(_logPath);

            if (!Utils.IsAdministrator())
            {
                string message = "WinCertes.exe must be launched as Administrator with elevated permissions";
                _logger.Error(message);
                Thread.Sleep(1000);
                Utils.AdminRelauncher();
            }
            // Merge command line parameters with registry defaults
            // TODO: Revamp this completely to use simple command line (like aloopkin\WinCertes) or configuration file
            int result = HandleOptions(args);

            if (result != 0)
            {
                return(MainExit(result));
            }

            // Display settings, don't create or renew the certificate
            if (_show)
            {
                _winCertesOptions.DisplayOptions();
                return(MainExit(SUCCESS));
            }

            // Helper to create the DNS keys
            if (_creatednskeys)
            {
                _winCertesOptions.WriteDnsOptions();
                return(MainExit(SUCCESS));
            }

            // Reset is a full reset!
            if (_reset)
            {
                Console.WriteLine("\nWARNING: You should revoke the certificate before deleting it from the registry\nDelete [{0}]?\nPress Enter when ready...", _winCertesOptions.Registry.FullRegistryKey);
                Console.ReadLine();
                _winCertesOptions.Registry.DeleteAllParameters();
                Utils.DeleteScheduledTasks();
                return(MainExit(SUCCESS));
            }


            _logger.Info("Initialisation successful, processing your request...");
            string taskName = null;

            if (_periodic)
            {
                taskName = Utils.DomainsToFriendlyName(_winCertesOptions.Domains);
            }

            // Initialization and renewal/revocation handling
            try
            {
                InitCertesWrapper(_winCertesOptions);
            }
            catch (Exception e)
            {
                _logger.Error(e.Message);
                return(MainExit(ERROR));
            }
            if (_winCertesOptions.Revoke > -1)
            {
                RevokeCert(_winCertesOptions.Domains, _winCertesOptions.Revoke);
                return(MainExit(SUCCESS));
            }
            // 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(_winCertesOptions.Domains))
            {
                Utils.CreateScheduledTask(taskName, _winCertesOptions.Domains, _extra);
                return(MainExit(SUCCESS));
            }

            // 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();

            if ((httpChallengeValidator == null) && (dnsChallengeValidator == null))
            {
                WriteErrorMessageWithUsage(_options, "Specify either an HTTP or a DNS validation method.");
                return(MainExit(ERROR_MISSING_HTTP_DNS));
            }
            if (!(Task.Run(() => _certesWrapper.RegisterNewOrderAndVerify(_winCertesOptions.Domains, httpChallengeValidator, dnsChallengeValidator)).GetAwaiter().GetResult()))
            {
                if (httpChallengeValidator != null)
                {
                    httpChallengeValidator.EndAllChallengeValidations();
                }
                return(MainExit(ERROR));
            }
            if (httpChallengeValidator != null)
            {
                httpChallengeValidator.EndAllChallengeValidations();
            }

            // We get the certificate from the ACME service
            string pfxFullFileName = _winCertesPath + "\\" + _winCertesOptions.CertificateName;
            var    pfx             = Task.Run(() =>
                                              _certesWrapper.RetrieveCertificate(_winCertesOptions.Domains, pfxFullFileName, Utils.DomainsToFriendlyName(_winCertesOptions.Domains), _winCertesOptions.ExportPem)
                                              ).GetAwaiter().GetResult();

            if (pfx == null)
            {
                return(MainExit(ERROR));
            }
            CertificateStorageManager certificateStorageManager = new CertificateStorageManager(pfx, (_winCertesOptions.Csp == null) && (!_winCertesOptions.noCsp));

            // Let's process the PFX into Windows Certificate object.
            certificateStorageManager.ProcessPFX();
            // and we write its information to the WinCertes configuration
            RegisterCertificateIntoConfiguration(certificateStorageManager.Certificate, _winCertesOptions.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, _winCertesOptions.Domains, _extra);

            // Let's delete the PFX file, if export was not enabled
            if (!_winCertesOptions.ExportPem)
            {
                RemoveFileAndLog(pfx);
            }

            return(MainExit(SUCCESS));
        }