public Task <bool> ValidateAsync(AutoAcmeContext context, IEnumerable <IAuthorizationContext> authorizationContexts)
        {
            if (this.index >= this.providers.Length)
            {
                return(Task.FromResult(false));
            }
            var provider = this.providers[this.index];

            Log.WriteLine("Validate via " + provider.ChallengeType + "...");
            Log.Indent();
            try {
                return(provider.ValidateAsync(context, authorizationContexts));
            }
            finally {
                Log.Unindent();
            }
        }
        public async Task <bool> ValidateAsync(AutoAcmeContext context, IEnumerable <IAuthorizationContext> authorizationContexts)
        {
            // Get challenge
            Log.WriteLine("Getting challenge...");
            var handlers = new List <IDisposable>();
            var result   = true;

            try {
                // Prepare challenges
                var challenges = new Dictionary <Uri, IChallengeContext>();
                Log.Indent();
                foreach (var authorizationContext in authorizationContexts)
                {
                    var authorization = await authorizationContext.Resource().ConfigureAwait(true);

                    Log.WriteLine("OK, the following is DNS name:");
                    Log.Indent();
                    Log.WriteLine(authorization.Identifier.Value);
                    var ch = await authorizationContext.Challenge(ChallengeType).ConfigureAwait(true);

                    var handler = await CreateChallengeHandler(ch, authorization.Identifier.Value, context.AccountKey).ConfigureAwait(true);

                    Log.Unindent();
                    handlers.Add(handler);
                    challenges.Add(ch.Location, ch);
                }
                Log.Unindent();
                Log.Write("Completing challenge");
                var challengeTasks = challenges.Values.Select(ch => ch.Validate());
                for (var i = 0; i < context.ChallengeVerificationRetryCount; i++)
                {
                    Log.Write(".");
                    foreach (var challenge in await Task.WhenAll(challengeTasks).ConfigureAwait(true))
                    {
                        switch (challenge.Status)
                        {
                        case ChallengeStatus.Invalid:
                            Log.WriteLine($"Challenge {challenge.Status}: {challenge.Url} {challenge.Error?.Detail}");
                            challenges.Remove(challenge.Url);
                            result = false;
                            break;

                        case ChallengeStatus.Valid:
                            challenges.Remove(challenge.Url);
                            break;
                        }
                    }
                    if (challenges.Count == 0)
                    {
                        break;
                    }
                    await Task.Delay(context.ChallengeVerificationWait).ConfigureAwait(true);

                    challengeTasks = challenges.Values.Select(ch => ch.Resource());
                }
                // Complete challenge
                Log.WriteLine(result ? "OK" : "Failed");
                return(result);
            }
            catch (Exception ex) {
                Log.WriteLine("Challenge exception:");
                Log.WriteLine(ex.ToString());
                return(false);
            }
            finally {
                foreach (var handler in handlers)
                {
                    try {
                        handler.Dispose();
                    }
                    catch (Exception ex) {
                        Log.WriteLine("Error on challenge response disposal (maybe requires manual cleanup): " + ex.Message);
                    }
                }
            }
        }
Example #3
0
        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();
                    }
                }
            }
        }
Example #4
0
        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.");
        }
Example #5
0
        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();
                    }
                }
            }
        }
Example #6
0
        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);
            }
        }