예제 #1
0
        public override async Task CreateRecord(string recordName, string token)
        {
            _input.CreateSpace();
            _input.Show("Domain", _identifier);
            _input.Show("Record", recordName);
            _input.Show("Type", "TXT");
            _input.Show("Content", $"\"{token}\"");
            _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed.");
            await _input.Wait("Please press <Enter> after you've created and verified the record");

            // Pre-pre-validate, allowing the manual user to correct mistakes
            while (true)
            {
                if (await PreValidate(0))
                {
                    break;
                }
                else
                {
                    var retry = await _input.PromptYesNo(
                        "The correct record is not yet found by the local resolver. " +
                        "Check your configuration and/or wait for the name servers to " +
                        "synchronize and press <Enter> to try again. Answer 'N' to " +
                        "try ACME validation anyway.", true);

                    if (!retry)
                    {
                        break;
                    }
                }
            }
        }
예제 #2
0
        public override async Task <bool> CreateRecord(DnsValidationRecord record)
        {
            _input.CreateSpace();
            _input.Show("Domain", record.Context.Identifier);
            _input.Show("Record", record.Authority.Domain);
            _input.Show("Type", "TXT");
            _input.Show("Content", $"\"{record.Value}\"");
            _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed.");
            if (!await _input.Wait("Please press <Enter> after you've created and verified the record"))
            {
                _log.Warning("User aborted");
                return(false);
            }

            if (!_settings.Validation.PreValidateDns)
            {
                return(true);
            }

            // Pre-pre-validate, allowing the manual user to correct mistakes
            while (true)
            {
                if (await PreValidate(record))
                {
                    return(true);
                }
                else
                {
                    _input.CreateSpace();
                    _input.Show(null, value: "The correct record has not yet been found by the local resolver. That means it's likely the validation attempt will fail, or your DNS provider needs a little more time to publish and synchronize the changes.");
                    var options = new List <Choice <bool?> >
                    {
                        Choice.Create <bool?>(null, "Retry check"),
                        Choice.Create <bool?>(true, "Ignore and continue"),
                        Choice.Create <bool?>(false, "Abort")
                    };
                    var chosen = await _input.ChooseFromMenu("How would you like to proceed?", options);

                    if (chosen != null)
                    {
                        return(chosen.Value);
                    }
                }
            }
        }
예제 #3
0
 /// <summary>
 /// Interactive input of a search pattern
 /// </summary>
 /// <param name="input"></param>
 /// <param name="options"></param>
 /// <returns></returns>
 async Task InputPattern(IInputService input, IISOptions options)
 {
     input.CreateSpace();
     input.Show(null, IISArgumentsProvider.PatternExamples);
     string raw;
     do
     {
         raw = await input.RequestString("Pattern");
     }
     while (!ParsePattern(raw, options));
 }
예제 #4
0
        public override async Task <ScriptOptions> Aquire(Target target, IInputService inputService, RunLevel runLevel)
        {
            var ret = new ScriptOptions
            {
                Script = await Script.Interactive(inputService, "File").GetValue(),
            };

            inputService.CreateSpace();
            inputService.Show("{CertCommonName}", "Common name (primary domain name)");
            inputService.Show("{CachePassword}", ".pfx password");
            inputService.Show("{CacheFile}", ".pfx full path");
            inputService.Show("{CertFriendlyName}", "Certificate friendly name");
            inputService.Show("{CertThumbprint}", "Certificate thumbprint");
            inputService.Show("{StoreType}", $"Type of store (e.g. {CentralSslOptions.PluginName}, {CertificateStoreOptions.PluginName}, {PemFilesOptions.PluginName}, ...)");
            inputService.Show("{StorePath}", "Path to the store");
            inputService.Show("{RenewalId}", "Renewal identifier");
            inputService.Show("{OldCertCommonName}", "Common name (primary domain name) of the previously issued certificate");
            inputService.Show("{OldCertFriendlyName}", "Friendly name of the previously issued certificate");
            inputService.Show("{OldCertThumbprint}", "Thumbprint of the previously issued certificate");
            inputService.CreateSpace();
            ret.ScriptParameters = await Parameters.Interactive(inputService, "Parameters").GetValue();

            return(ret);
        }
예제 #5
0
        public override async Task <ScriptOptions> Aquire(Target target, IInputService inputService, RunLevel runLevel)
        {
            inputService.CreateSpace();
            inputService.Show("Full instructions", "https://win-acme.com/reference/plugins/installation/script");
            inputService.CreateSpace();
            inputService.Show("{CertCommonName}", "Common name (primary domain name)");
            inputService.Show("{CachePassword}", ".pfx password");
            inputService.Show("{CacheFile}", ".pfx full path");
            inputService.Show("{CertFriendlyName}", "Certificate friendly name");
            inputService.Show("{CertThumbprint}", "Certificate thumbprint");
            inputService.Show("{StoreType}", $"Type of store ({CentralSslOptions.PluginName}/{CertificateStoreOptions.PluginName}/{PemFilesOptions.PluginName})");
            inputService.Show("{StorePath}", "Path to the store");
            inputService.Show("{RenewalId}", "Renewal identifier");
            inputService.Show("{OldCertCommonName}", "Common name (primary domain name) of the previously issued certificate");
            inputService.Show("{OldCertFriendlyName}", "Friendly name of the previously issued certificate");
            inputService.Show("{OldCertThumbprint}", "Thumbprint of the previously issued certificate");
            inputService.CreateSpace();

            return(new ScriptOptions
            {
                Script = await Script.Interactive(inputService, "File").GetValue(),
                ScriptParameters = await Parameters.Interactive(inputService, "Parameters").GetValue()
            });
        }
예제 #6
0
        public override async Task <bool> CreateRecord(DnsValidationRecord record)
        {
            _input.CreateSpace();
            _input.Show("Domain", record.Context.Identifier);
            _input.Show("Record", record.Authority.Domain);
            _input.Show("Type", "TXT");
            _input.Show("Content", $"\"{record.Value}\"");
            _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed.");
            if (!await _input.Wait("Please press <Enter> after you've created and verified the record"))
            {
                _log.Warning("User aborted");
                return(false);
            }

            // Pre-pre-validate, allowing the manual user to correct mistakes
            while (true)
            {
                if (await PreValidate(record))
                {
                    return(true);
                }
                else
                {
                    var retry = await _input.PromptYesNo(
                        "The correct record is not yet found by the local resolver. " +
                        "Check your configuration and/or wait for the name servers to " +
                        "synchronize and press <Enter> to try again. Answer 'N' to " +
                        "try ACME validation anyway.", true);

                    if (!retry)
                    {
                        return(false);
                    }
                }
            }
        }
예제 #7
0
        /// <summary>
        /// If renewal is already Scheduled, replace it with the new options
        /// </summary>
        /// <param name="target"></param>
        /// <returns></returns>
        private async Task <Renewal> CreateRenewal(Renewal temp, RunLevel runLevel)
        {
            // First check by id
            var existing = _renewalStore.FindByArguments(temp.Id, null).FirstOrDefault();

            // If Id has been specified, we don't consider the Friendlyname anymore
            // So specifying --id becomes a way to create duplicate certificates
            // with the same --friendlyname in unattended mode.
            if (existing == null && string.IsNullOrEmpty(_args.Id))
            {
                existing = _renewalStore.FindByArguments(null, temp.LastFriendlyName?.EscapePattern()).FirstOrDefault();
            }

            // This will be a completely new renewal, no further processing needed
            if (existing == null)
            {
                return(temp);
            }

            // Match found with existing certificate, determine if we want to overwrite
            // it or create it side by side with the current one.
            if (runLevel.HasFlag(RunLevel.Interactive) && (temp.Id != existing.Id) && temp.New)
            {
                _input.CreateSpace();
                _input.Show("Existing renewal", existing.ToString(_dueDate, _input));
                if (!await _input.PromptYesNo($"Overwrite settings?", true))
                {
                    return(temp);
                }
            }

            // Move settings from temporary renewal over to
            // the pre-existing one that we are overwriting
            _log.Warning("Overwriting previously created renewal");
            existing.Updated                   = true;
            existing.TargetPluginOptions       = temp.TargetPluginOptions;
            existing.OrderPluginOptions        = temp.OrderPluginOptions;
            existing.CsrPluginOptions          = temp.CsrPluginOptions;
            existing.StorePluginOptions        = temp.StorePluginOptions;
            existing.ValidationPluginOptions   = temp.ValidationPluginOptions;
            existing.InstallationPluginOptions = temp.InstallationPluginOptions;
            return(existing);
        }
예제 #8
0
        public async Task Import(RunLevel runLevel)
        {
            if (!_legacyRenewal.Renewals.Any())
            {
                _log.Warning("No legacy renewals found");
            }
            _log.Information("Legacy renewals {x}", _legacyRenewal.Renewals.Count().ToString());
            _log.Information("Current renewals {x}", _currentRenewal.Renewals.Count().ToString());
            _log.Information("Step {x}/3: convert renewals", 1);
            foreach (var legacyRenewal in _legacyRenewal.Renewals)
            {
                var converted = Convert(legacyRenewal);
                _currentRenewal.Import(converted);
            }
            _log.Information("Step {x}/3: create new scheduled task", 2);
            await _currentTaskScheduler.EnsureTaskScheduler(runLevel | RunLevel.Import, true);

            _legacyTaskScheduler.StopTaskScheduler();

            _log.Information("Step {x}/3: ensure ACMEv2 account", 3);
            await _acmeClient.GetAccount();

            var listCommand  = "--list";
            var renewCommand = "--renew";

            if (runLevel.HasFlag(RunLevel.Interactive))
            {
                listCommand  = "Manage renewals";
                renewCommand = "Run";
            }
            _input.CreateSpace();
            _input.Show(null,
                        value: $"The renewals have now been imported into this new version " +
                        "of the program. Nothing else will happen until new scheduled task is " +
                        "first run *or* you trigger them manually. It is highly recommended " +
                        $"to review the imported items with '{listCommand}' and to monitor the " +
                        $"results of the first execution with '{renewCommand}'.");
        }
예제 #9
0
        /// <summary>
        /// Single round of aquiring settings
        /// </summary>
        /// <param name="input"></param>
        /// <param name="allBindings"></param>
        /// <param name="visibleBindings"></param>
        /// <param name="allSites"></param>
        /// <param name="visibleSites"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        private async Task<IISOptions?> TryAquireSettings(
            IInputService input, 
            List<IISHelper.IISBindingOption> allBindings,
            List<IISHelper.IISBindingOption> visibleBindings,
            List<IISHelper.IISSiteOption> allSites,
            List<IISHelper.IISSiteOption> visibleSites,
            RunLevel runLevel)
        {
            input.CreateSpace();
            input.Show(null, "Please select which website(s) should be scanned for host names. " +
                "You may input one or more site identifiers (comma-separated) to filter by those sites, " +
                "or alternatively leave the input empty to scan *all* websites.");

            var options = new IISOptions();
            await input.WritePagedList(
                visibleSites.Select(x => Choice.Create(
                    item: x,
                    description: $"{x.Name} ({x.Hosts.Count()} binding{(x.Hosts.Count() == 1 ? "" : "s")})",
                    command: x.Id.ToString(),
                    color: x.Https ? ConsoleColor.DarkGray : (ConsoleColor?)null)));
            var raw = await input.RequestString("Site identifier(s) or <Enter> to choose all");
            if (!ParseSiteOptions(raw, allSites, options))
            {
                return null;
            }

            var filtered = _iisHelper.FilterBindings(visibleBindings, options);
            await ListBindings(input, filtered);
            input.CreateSpace();
            input.Show(null,
                "Listed above are the bindings found on the selected site(s). By default all of " +
                "them will be included, but you may either pick specific ones by typing the host names " +
                "or identifiers (comma-separated) or filter them using one of the options from the " +
                "menu.");
            var askExclude = true;
            var filters = new List<Choice<Func<Task>>>
            {
                Choice.Create<Func<Task>>(() => {
                    return InputPattern(input, options); 
                }, "Pick bindings based on a search pattern", command: "P"),
                Choice.Create<Func<Task>>(() => { 
                    askExclude = false; 
                    return Task.CompletedTask; 
                }, "Pick *all* bindings", @default: true, command: "A")
            };
            if (runLevel.HasFlag(RunLevel.Advanced))
            {
                filters.Insert(1, Choice.Create<Func<Task>>(() => { 
                    askExclude = true; 
                    return InputRegex(input, options);
                }, "Pick bindings based on a regular expression", command: "R"));
            }

            // Handle undefined input
            Choice<Func<Task>> processUnkown(string unknown) {
                return Choice.Create<Func<Task>>(() =>
                {
                    askExclude = false;
                    return ProcessInputHosts(
                        unknown, 
                        allBindings, 
                        filtered, 
                        options,
                        () => options.IncludeHosts, x => options.IncludeHosts = x);
                });
            }

            var chosen = await input.ChooseFromMenu(
                "Binding identifiers(s) or menu option",
                filters,
                processUnkown);
            await chosen.Invoke();

            filtered = _iisHelper.FilterBindings(allBindings, options);

            // Exclude specific bindings
            if (askExclude && filtered.Count > 1 && runLevel.HasFlag(RunLevel.Advanced))
            {
                await ListBindings(input, filtered);
                input.CreateSpace();
                input.Show(null, "The listed bindings match your current filter settings. " +
                    "If you wish to exclude one or more of them from the certificate, please " +
                    "input those bindings now. Press <Enter> to include all listed bindings.");
                await InputHosts("Exclude bindings", 
                    input, allBindings, filtered, options, 
                    () => options.ExcludeHosts, x => options.ExcludeHosts = x);
                if (options.ExcludeHosts != null)
                {
                    filtered = _iisHelper.FilterBindings(allBindings, options);
                }
            } 

            // Now the common name
            if (filtered.Select(x => x.HostUnicode).Distinct().Count() > 1)
            {
                await InputCommonName(input, filtered, options);
            }
            return options;
        }
예제 #10
0
        /// <summary>
        /// Setup a new ACME account
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        private async Task SetupAccount(AcmeProtocolClient client)
        {
            // Accept the terms of service, if defined by the server
            try
            {
                var(_, filename, content) = await client.GetTermsOfServiceAsync();

                _log.Verbose("Terms of service downloaded");
                if (!string.IsNullOrEmpty(filename))
                {
                    if (!await AcceptTos(filename, content))
                    {
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Error getting terms of service");
            }

            var contacts = default(string[]);

            var eabKid      = _accountArguments.EabKeyIdentifier;
            var eabKey      = _accountArguments.EabKey;
            var eabAlg      = _accountArguments.EabAlgorithm ?? "HS256";
            var eabFlow     = client.Directory?.Meta?.ExternalAccountRequired == "true";
            var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com");

            // Warn about unneeded EAB
            if (!eabFlow && !string.IsNullOrWhiteSpace(eabKid))
            {
                eabFlow = true;
                _input.CreateSpace();
                _input.Show(null, "You have provided an external account binding key, even though " +
                            "the server does not indicate that this is required. We will attempt to register " +
                            "using this key anyway.");
            }

            if (zeroSslFlow)
            {
                async Task emailRegistration()
                {
                    var registration = await GetContacts(allowMultiple : false, prefix : "");

                    var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? "");

                    if (eab != null)
                    {
                        eabKid = eab.Kid;
                        eabKey = eab.Hmac;
                    }
                    else
                    {
                        _log.Error("Unable to retrieve EAB credentials using the provided email address");
                    }
                }

                async Task apiKeyRegistration()
                {
                    var accessKey = await _input.ReadPassword("API access key");

                    var eab = await _zeroSsl.Obtain(accessKey ?? "");

                    if (eab != null)
                    {
                        eabKid = eab.Kid;
                        eabKey = eab.Hmac;
                    }
                    else
                    {
                        _log.Error("Unable to retrieve EAB credentials using the provided API access key");
                    }
                }

                if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress))
                {
                    await emailRegistration();
                }
                else
                {
                    var instruction = "ZeroSsl can be used either by setting up a new " +
                                      "account using your email address or by connecting it to your existing " +
                                      "account using the API access key or pre-generated EAB credentials, which can " +
                                      "be obtained from the Developer section of the dashboard.";
                    _input.CreateSpace();
                    _input.Show(null, instruction);
                    var chosen = await _input.ChooseFromMenu(
                        "How would you like to create the account?",
                        new List <Choice <Func <Task> > >()
                    {
                        Choice.Create(apiKeyRegistration, "API access key"),
                        Choice.Create(emailRegistration, "Email address"),
                        Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly")
                    });

                    await chosen.Invoke();
                }
            }

            if (eabFlow)
            {
                if (string.IsNullOrWhiteSpace(eabKid))
                {
                    var instruction = "This ACME endpoint requires an external account. " +
                                      "You will need to provide a key identifier and a key to proceed. " +
                                      "Please refer to the providers instructions on how to obtain these.";
                    _input.CreateSpace();
                    _input.Show(null, instruction);
                    eabKid = await _input.RequestString("Key identifier");
                }
                if (string.IsNullOrWhiteSpace(eabKey))
                {
                    eabKey = await _input.ReadPassword("Key (base64url encoded)");
                }
                contacts = await GetContacts(runLevel : RunLevel.Unattended);
            }
            else
            {
                contacts = await GetContacts();
            }

            var signer = _accountManager.DefaultSigner();

            try
            {
                await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey);
            }
            catch (AcmeProtocolException apex)
            {
                // Some non-ACME compliant server may not support ES256 or other
                // algorithms, so attempt fallback to RS256
                if (apex.ProblemType == acme.ProblemType.BadSignatureAlgorithm &&
                    signer.KeyType != "RS256")
                {
                    signer = _accountManager.NewSigner("RS256");
                    await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey);
                }
                else
                {
                    throw;
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Error creating account");
                throw;
            }
        }
예제 #11
0
        /// <summary>
        /// Renewal management mode
        /// </summary>
        /// <returns></returns>
        internal async Task ManageRenewals()
        {
            IEnumerable <Renewal> originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
            var selectedRenewals = originalSelection;
            var quit             = false;
            var displayAll       = false;

            do
            {
                var all                  = selectedRenewals.Count() == originalSelection.Count();
                var none                 = !selectedRenewals.Any();
                var totalLabel           = originalSelection.Count() != 1 ? "renewals" : "renewal";
                var renewalSelectedLabel = selectedRenewals.Count() != 1 ? "renewals" : "renewal";
                var selectionLabel       =
                    all ? selectedRenewals.Count() == 1 ? "the renewal" : "*all* renewals" :
                    none ? "no renewals" :
                    $"{selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}";

                _input.CreateSpace();
                _input.Show(null,
                            "Welcome to the renewal manager. Actions selected in the menu below will " +
                            "be applied to the following list of renewals. You may filter the list to target " +
                            "your action at a more specific set of renewals, or sort it to make it easier to " +
                            "find what you're looking for.");

                var displayRenewals    = selectedRenewals;
                var displayLimited     = !displayAll && selectedRenewals.Count() >= _settings.UI.PageSize;
                var displayHidden      = 0;
                var displayHiddenLabel = "";
                if (displayLimited)
                {
                    displayRenewals    = displayRenewals.Take(_settings.UI.PageSize - 1);
                    displayHidden      = selectedRenewals.Count() - displayRenewals.Count();
                    displayHiddenLabel = displayHidden != 1 ? "renewals" : "renewal";
                }
                var choices = displayRenewals.Select(x => Choice.Create <Renewal?>(x,
                                                                                   description: x.ToString(_input),
                                                                                   color: x.History.LastOrDefault()?.Success ?? false ?
                                                                                   x.IsDue() ?
                                                                                   ConsoleColor.DarkYellow :
                                                                                   ConsoleColor.Green :
                                                                                   ConsoleColor.Red)).ToList();
                if (displayLimited)
                {
                    choices.Add(Choice.Create <Renewal?>(null,
                                                         command: "More",
                                                         description: $"{displayHidden} additional {displayHiddenLabel} selected but currently not displayed"));
                }
                await _input.WritePagedList(choices);

                displayAll = false;

                var options = new List <Choice <Func <Task> > >();
                if (displayLimited)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            () => { displayAll = true; return(Task.CompletedTask); },
                            "List all selected renewals", "A"));
                }
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => { quit = true; await EditRenewal(selectedRenewals.First()); },
                        "Edit renewal", "E",
                        @disabled: (selectedRenewals.Count() != 1, none ? "No renewals selected." : "Multiple renewals selected.")));
                if (selectedRenewals.Count() > 1)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            async() => selectedRenewals = await FilterRenewalsMenu(selectedRenewals),
                            all ? "Apply filter" : "Apply additional filter", "F",
                            @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to filter.")));
                    options.Add(
                        Choice.Create <Func <Task> >(
                            async() => selectedRenewals = await SortRenewalsMenu(selectedRenewals),
                            "Sort renewals", "S",
                            @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to sort.")));
                }
                if (!all)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            () => { selectedRenewals = originalSelection; return(Task.CompletedTask); },
                            "Reset sorting and filtering", "X",
                            @disabled: (all, "No filters have been applied yet.")));
                }
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    foreach (var renewal in selectedRenewals)
                    {
                        var index = selectedRenewals.ToList().IndexOf(renewal) + 1;
                        _log.Information("Details for renewal {n}/{m}", index, selectedRenewals.Count());
                        await ShowRenewal(renewal);
                        var cont = false;
                        if (index != selectedRenewals.Count())
                        {
                            cont = await _input.Wait("Press <Enter> to continue or <Esc> to abort");
                            if (!cont)
                            {
                                break;
                            }
                        }
                        else
                        {
                            await _input.Wait();
                        }
                    }
                },
                        $"Show details for {selectionLabel}", "D",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    WarnAboutRenewalArguments();
                    foreach (var renewal in selectedRenewals)
                    {
                        var runLevel = RunLevel.Interactive | RunLevel.ForceRenew;
                        if (_args.Force)
                        {
                            runLevel |= RunLevel.IgnoreCache;
                        }
                        await ProcessRenewal(renewal, runLevel);
                    }
                },
                        $"Run {selectionLabel}", "R",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await Analyze(selectedRenewals),
                        $"Analyze duplicates for {selectionLabel}", "U",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to cancel {selectedRenewals.Count()} currently selected {renewalSelectedLabel}?", false);
                    if (confirm)
                    {
                        foreach (var renewal in selectedRenewals)
                        {
                            _renewalStore.Cancel(renewal);
                        }
                        ;
                        originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
                        selectedRenewals  = originalSelection;
                    }
                },
                        $"Cancel {selectionLabel}", "C",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to revoke the most recently issued certificate for {selectedRenewals.Count()} currently selected {renewalSelectedLabel}? This should only be done in case of a (suspected) security breach. Cancel the {renewalSelectedLabel} if you simply don't need the certificates anymore.", false);
                    if (confirm)
                    {
                        await RevokeCertificates(selectedRenewals);
                    }
                },
                        $"Revoke certificate(s) for {selectionLabel}", "V",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        () => { quit = true; return(Task.CompletedTask); },
                        "Back", "Q",
                        @default: !originalSelection.Any()));

                if (selectedRenewals.Count() > 1)
                {
                    _input.CreateSpace();
                    _input.Show(null, $"Currently selected {selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}");
                }
                var chosen = await _input.ChooseFromMenu(
                    "Choose an action or type numbers to select renewals",
                    options,
                    (string unexpected) =>
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await FilterRenewalsById(selectedRenewals, unexpected)));

                await chosen.Invoke();
            }while (!quit);
        }
예제 #12
0
        /// <summary>
        /// Determine if the renewal should be executes
        /// </summary>
        /// <param name="renewal"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel)
        {
            _input.CreateSpace();
            _log.Reset();
            using var ts = _scopeBuilder.Target(_container, renewal, runLevel);
            using var es = _scopeBuilder.Execution(ts, renewal, runLevel);
            // Generate the target
            var targetPlugin = es.Resolve <ITargetPlugin>();

            var(disabled, disabledReason) = targetPlugin.Disabled;
            if (disabled)
            {
                return(new RenewResult($"Target plugin is not available. {disabledReason}"));
            }
            var target = await targetPlugin.Generate();

            if (target is INull)
            {
                return(new RenewResult($"Target plugin did not generate a target"));
            }
            if (!target.IsValid(_log))
            {
                return(new RenewResult($"Target plugin generated an invalid target"));
            }

            // Check if our validation plugin is (still) up to the task
            var validationPlugin = es.Resolve <IValidationPluginOptionsFactory>();

            if (!validationPlugin.CanValidate(target))
            {
                return(new RenewResult($"Validation plugin is unable to validate the target. A wildcard host was introduced into a HTTP validated renewal."));
            }

            // Create one or more orders based on the target
            var orderPlugin = es.Resolve <IOrderPlugin>();
            var orders      = orderPlugin.Split(renewal, target);

            if (orders == null || orders.Count() == 0)
            {
                return(new RenewResult("Order plugin failed to create order(s)"));
            }
            _log.Verbose("Targeted convert into {n} order(s)", orders.Count());

            // Check if renewal is needed
            if (!runLevel.HasFlag(RunLevel.ForceRenew) && !renewal.Updated)
            {
                _log.Verbose("Checking {renewal}", renewal.LastFriendlyName);
                if (!renewal.IsDue())
                {
                    var cs    = es.Resolve <ICertificateService>();
                    var abort = true;
                    foreach (var order in orders)
                    {
                        var cache = cs.CachedInfo(order);
                        if (cache == null && !renewal.New)
                        {
                            _log.Information(LogType.All, "Renewal for {renewal} running prematurely due to detected target change", renewal.LastFriendlyName);
                            abort = false;
                            break;
                        }
                    }
                    if (abort)
                    {
                        _log.Information("Renewal for {renewal} is due after {date}", renewal.LastFriendlyName, renewal.GetDueDate());
                        return(new RenewResult()
                        {
                            Abort = true
                        });
                    }
                }
                else if (!renewal.New)
                {
                    _log.Information(LogType.All, "Renewing certificate for {renewal}", renewal.LastFriendlyName);
                }
            }
            else if (runLevel.HasFlag(RunLevel.ForceRenew))
            {
                _log.Information(LogType.All, "Force renewing certificate for {renewal}", renewal.LastFriendlyName);
            }

            // If at this point we haven't retured already with an error/abort
            // actually execute the renewal
            var result = await ExecuteRenewal(es, orders.ToList(), runLevel);

            // Configure task scheduler
            if (result.Success && !result.Abort)
            {
                if ((renewal.New || renewal.Updated) && !_args.NoTaskScheduler)
                {
                    if (runLevel.HasFlag(RunLevel.Test) && !await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true))
                    {
                        // Early out for test runs
                        result.Abort = true;
                        return(result);
                    }
                    else
                    {
                        // Make sure the Task Scheduler is configured
                        await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel, false);
                    }
                }
            }
            return(result);
        }
예제 #13
0
        private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer)
        {
            _log.Verbose("Loading ACME account");
            AccountDetails?account = null;

            if (File.Exists(AccountPath))
            {
                if (signer != null)
                {
                    _log.Debug("Loading account information from {registrationPath}", AccountPath);
                    account        = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath));
                    client.Account = account;
                    // Maybe we should update the account details
                    // on every start of the program to figure out
                    // if it hasn't been suspended or cancelled?
                    // UpdateAccount();
                }
                else
                {
                    _log.Error("Account found but no valid signer could be loaded");
                }
            }
            else
            {
                _log.Verbose("No account found at {path}, creating new one", AccountPath);
                try
                {
                    var(_, filename, content) = await client.GetTermsOfServiceAsync();

                    _log.Verbose("Terms of service downloaded");
                    if (!string.IsNullOrEmpty(filename))
                    {
                        if (!await AcceptTos(filename, content))
                        {
                            return(null);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error getting terms of service");
                }
                var contacts        = default(string[]);
                var externalAccount = default(ExternalAccountBinding);

                var kid     = _accountArguments.EabKeyIdentifier;
                var key     = _accountArguments.EabKey;
                var alg     = _accountArguments.EabAlgorithm ?? "HS256";
                var eabFlow = client.Directory?.Meta?.ExternalAccountRequired == "true";
                if (eabFlow)
                {
                    _input.CreateSpace();
                    _input.Show(null, "This ACME endpoint requires an external account. You will " +
                                "need to provide a key identifier and a key to proceed. Please refer to the " +
                                "providers instructions on how to obtain these.");
                }
                else if (!string.IsNullOrWhiteSpace(kid))
                {
                    eabFlow = true;
                    _input.CreateSpace();
                    _input.Show(null, "You have provided external account binding key, but the server" +
                                "claims that is not required. We will attempt to register using this key anyway.");
                }
                if (eabFlow)
                {
                    if (string.IsNullOrWhiteSpace(kid))
                    {
                        kid = await _input.RequestString("Key identifier");
                    }
                    if (string.IsNullOrWhiteSpace(key))
                    {
                        key = await _input.ReadPassword("Key (base64url encoded)");
                    }
                    externalAccount = new ExternalAccountBinding(
                        alg,
                        JsonConvert.SerializeObject(client.Signer.ExportJwk(), Formatting.None),
                        kid,
                        key ?? "",
                        client.Directory?.NewAccount ?? "");
                }
                else
                {
                    contacts = await GetContacts();
                }

                try
                {
                    account = await client.CreateAccountAsync(
                        contacts,
                        termsOfServiceAgreed : true,
                        externalAccountBinding : externalAccount?.Payload() ?? null);
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error creating account");
                }

                if (account != null)
                {
                    try
                    {
                        _log.Debug("Saving account");
                        var accountKey = new AccountSigner
                        {
                            KeyType   = client.Signer.JwsAlg,
                            KeyExport = client.Signer.Export(),
                        };
                        AccountSigner = accountKey;
                        await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account));
                    }
                    catch (Exception ex)
                    {
                        _log.Error(ex, "Error saving account");
                        account = null;
                    }
                }
            }
            return(account);
        }
예제 #14
0
        private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer)
        {
            _log.Verbose("Loading ACME account");
            AccountDetails?account = null;

            if (File.Exists(AccountPath))
            {
                if (signer != null)
                {
                    _log.Debug("Loading account information from {registrationPath}", AccountPath);
                    account        = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath));
                    client.Account = account;
                    // Maybe we should update the account details
                    // on every start of the program to figure out
                    // if it hasn't been suspended or cancelled?
                    // UpdateAccount();
                }
                else
                {
                    _log.Error("Account found but no valid signer could be loaded");
                }
            }
            else
            {
                _log.Verbose("No account found at {path}, creating new one", AccountPath);
                try
                {
                    var(_, filename, content) = await client.GetTermsOfServiceAsync();

                    _log.Verbose("Terms of service downloaded");
                    if (!string.IsNullOrEmpty(filename))
                    {
                        if (!await AcceptTos(filename, content))
                        {
                            return(null);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error getting terms of service");
                }
                var contacts        = default(string[]);
                var externalAccount = default(ExternalAccountBinding);

                var kid         = _accountArguments.EabKeyIdentifier;
                var key         = _accountArguments.EabKey;
                var alg         = _accountArguments.EabAlgorithm ?? "HS256";
                var eabFlow     = client.Directory?.Meta?.ExternalAccountRequired == "true";
                var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com");

                // Warn about unneeded EAB
                if (!eabFlow && !string.IsNullOrWhiteSpace(kid))
                {
                    eabFlow = true;
                    _input.CreateSpace();
                    _input.Show(null, "You have provided an external account binding key, even though " +
                                "the server does not indicate that this is required. We will attempt to register " +
                                "using this key anyway.");
                }

                if (zeroSslFlow)
                {
                    async Task emailRegistration()
                    {
                        var registration = await GetContacts(allowMultiple : false, prefix : "");

                        var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? "");

                        if (eab != null)
                        {
                            kid = eab.Kid;
                            key = eab.Hmac;
                        }
                        else
                        {
                            _log.Error("Unable to retrieve EAB credentials using the provided email address");
                        }
                    }

                    async Task apiKeyRegistration()
                    {
                        var accessKey = await _input.ReadPassword("API access key");

                        var eab = await _zeroSsl.Obtain(accessKey ?? "");

                        if (eab != null)
                        {
                            kid = eab.Kid;
                            key = eab.Hmac;
                        }
                        else
                        {
                            _log.Error("Unable to retrieve EAB credentials using the provided API access key");
                        }
                    }

                    if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress))
                    {
                        await emailRegistration();
                    }
                    else
                    {
                        var instruction = "ZeroSsl can be used either by setting up a new " +
                                          "account using your email address or by connecting it to your existing " +
                                          "account using the API access key or pre-generated EAB credentials, which can " +
                                          "be obtained from the Developer section of the dashboard.";
                        _input.CreateSpace();
                        _input.Show(null, instruction);
                        var chosen = await _input.ChooseFromMenu(
                            "How would you like to create the account?",
                            new List <Choice <Func <Task> > >()
                        {
                            Choice.Create((Func <Task>)apiKeyRegistration, "API access key"),
                            Choice.Create((Func <Task>)emailRegistration, "Email address"),
                            Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly")
                        });

                        await chosen.Invoke();
                    }
                }

                if (eabFlow)
                {
                    if (string.IsNullOrWhiteSpace(kid))
                    {
                        var instruction = "This ACME endpoint requires an external account. " +
                                          "You will need to provide a key identifier and a key to proceed. " +
                                          "Please refer to the providers instructions on how to obtain these.";
                        _input.CreateSpace();
                        _input.Show(null, instruction);
                        kid = await _input.RequestString("Key identifier");
                    }
                    if (string.IsNullOrWhiteSpace(key))
                    {
                        key = await _input.ReadPassword("Key (base64url encoded)");
                    }
                    externalAccount = new ExternalAccountBinding(
                        alg,
                        JsonConvert.SerializeObject(
                            client.Signer.ExportJwk(),
                            Formatting.None),
                        kid,
                        key ?? "",
                        client.Directory?.NewAccount ?? "");
                }
                else
                {
                    contacts = await GetContacts();
                }

                try
                {
                    account = await client.CreateAccountAsync(
                        contacts,
                        termsOfServiceAgreed : true,
                        externalAccountBinding : externalAccount?.Payload() ?? null);
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error creating account");
                }

                if (account != null)
                {
                    try
                    {
                        _log.Debug("Saving account");
                        var accountKey = new AccountSigner
                        {
                            KeyType   = client.Signer.JwsAlg,
                            KeyExport = client.Signer.Export(),
                        };
                        AccountSigner = accountKey;
                        await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account));
                    }
                    catch (Exception ex)
                    {
                        _log.Error(ex, "Error saving account");
                        account = null;
                    }
                }
            }
            return(account);
        }
예제 #15
0
        /// <summary>
        /// Determine if the renewal should be executes
        /// </summary>
        /// <param name="renewal"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel)
        {
            _input.CreateSpace();
            _log.Reset();
            using var ts = _scopeBuilder.Target(_container, renewal, runLevel);
            using var es = _scopeBuilder.Execution(ts, renewal, runLevel);

            // Generate the target
            var targetPlugin = es.Resolve <ITargetPlugin>();

            var(disabled, disabledReason) = targetPlugin.Disabled;
            if (disabled)
            {
                return(new RenewResult($"Source plugin is not available. {disabledReason}"));
            }
            var target = await targetPlugin.Generate();

            if (target is INull)
            {
                return(new RenewResult($"Source plugin did not generate source"));
            }

            // Create one or more orders based on the target
            var orderPlugin = es.Resolve <IOrderPlugin>();
            var orders      = orderPlugin.Split(renewal, target);

            if (orders == null || !orders.Any())
            {
                return(new RenewResult("Order plugin failed to create order(s)"));
            }
            _log.Verbose("Source converted into {n} order(s)", orders.Count());
            foreach (var order in orders)
            {
                if (!order.Target.IsValid(_log))
                {
                    return(new RenewResult($"Source plugin generated invalid source"));
                }
            }

            /// Start to check the renewal
            var result = await HandleOrders(es, renewal, orders.ToList(), runLevel);

            // Configure task scheduler
            var setupTaskScheduler = _args.SetupTaskScheduler;

            if (!setupTaskScheduler && !_args.NoTaskScheduler)
            {
                setupTaskScheduler = result.Success == true && !result.Abort && (renewal.New || renewal.Updated);
            }
            if (setupTaskScheduler && runLevel.HasFlag(RunLevel.Test))
            {
                setupTaskScheduler = await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true);

                if (!setupTaskScheduler)
                {
                    result.Abort = true;
                }
            }
            if (setupTaskScheduler)
            {
                var taskLevel = runLevel;
                if (_args.SetupTaskScheduler)
                {
                    taskLevel |= RunLevel.ForceRenew;
                }
                await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel);
            }
            return(result);
        }
예제 #16
0
        /// <summary>
        /// Determine if the renewal should be executes
        /// </summary>
        /// <param name="renewal"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        public async Task <RenewResult> HandleRenewal(Renewal renewal, RunLevel runLevel)
        {
            _input.CreateSpace();
            _log.Reset();
            using var ts = _scopeBuilder.Target(_container, renewal, runLevel);
            using var es = _scopeBuilder.Execution(ts, renewal, runLevel);
            // Generate the target
            var targetPlugin = es.Resolve <ITargetPlugin>();

            var(disabled, disabledReason) = targetPlugin.Disabled;
            if (disabled)
            {
                return(new RenewResult($"Source plugin is not available. {disabledReason}"));
            }
            var target = await targetPlugin.Generate();

            if (target is INull)
            {
                return(new RenewResult($"Source plugin did not generate source"));
            }
            if (!target.IsValid(_log))
            {
                return(new RenewResult($"Source plugin generated invalid source"));
            }

            // Check if our validation plugin is (still) up to the task
            var validationPlugin = es.Resolve <IValidationPluginOptionsFactory>();

            if (!validationPlugin.CanValidate(target))
            {
                return(new RenewResult($"Validation plugin is unable to validate the source. A wildcard host was introduced into a HTTP validated renewal."));
            }

            // Create one or more orders based on the target
            var orderPlugin = es.Resolve <IOrderPlugin>();
            var orders      = orderPlugin.Split(renewal, target);

            if (orders == null || !orders.Any())
            {
                return(new RenewResult("Order plugin failed to create order(s)"));
            }
            _log.Verbose("Targeted convert into {n} order(s)", orders.Count());

            // Test if this renewal should run right now
            if (!ShouldRun(es, orders, renewal, runLevel))
            {
                return(new RenewResult()
                {
                    Abort = true
                });
            }

            // If at this point we haven't retured already with an error/abort
            // actually execute the renewal
            var preScript    = _settings.Execution?.DefaultPreExecutionScript;
            var scriptClient = es.Resolve <ScriptClient>();

            if (!string.IsNullOrWhiteSpace(preScript))
            {
                await scriptClient.RunScript(preScript, $"{renewal.Id}");
            }
            var result = await ExecuteRenewal(es, orders.ToList(), runLevel);

            var postScript = _settings.Execution?.DefaultPostExecutionScript;

            if (!string.IsNullOrWhiteSpace(postScript))
            {
                await scriptClient.RunScript(postScript, $"{renewal.Id}");
            }

            // Configure task scheduler
            var setupTaskScheduler = _args.SetupTaskScheduler;

            if (!setupTaskScheduler && !_args.NoTaskScheduler)
            {
                setupTaskScheduler = result.Success && !result.Abort && (renewal.New || renewal.Updated);
            }
            if (setupTaskScheduler && runLevel.HasFlag(RunLevel.Test))
            {
                setupTaskScheduler = await _input.PromptYesNo($"[--test] Do you want to automatically renew with these settings?", true);

                if (!setupTaskScheduler)
                {
                    result.Abort = true;
                }
            }
            if (setupTaskScheduler)
            {
                var taskLevel = runLevel;
                if (_args.SetupTaskScheduler)
                {
                    taskLevel |= RunLevel.ForceRenew;
                }
                await es.Resolve <TaskSchedulerService>().EnsureTaskScheduler(runLevel);
            }
            return(result);
        }