예제 #1
0
        private async void TestScript_Click(object sender, EventArgs e)
        {
            var    button     = (Button)sender;
            string scriptFile = null;
            var    result     = new CertificateRequestResult {
                ManagedItem = MainViewModel.SelectedItem, IsSuccess = true, Message = "Script Testing Message"
            };

            if (button.Name == "Button_TestPreRequest")
            {
                scriptFile       = MainViewModel.SelectedItem.RequestConfig.PreRequestPowerShellScript;
                result.IsSuccess = false; // pre-request messages will always have IsSuccess = false
            }
            else if (button.Name == "Button_TestPostRequest")
            {
                scriptFile = MainViewModel.SelectedItem.RequestConfig.PostRequestPowerShellScript;
            }
            if (string.IsNullOrEmpty(scriptFile))
            {
                return;                                   // don't try to run empty script
            }
            try
            {
                string scriptOutput = await PowerShellManager.RunScript(result, scriptFile);

                MessageBox.Show(scriptOutput, "Powershell Output", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            catch (ArgumentException ex)
            {
                MessageBox.Show(ex.Message, "Script Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
예제 #2
0
        public async Task <CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress <RequestProgressState> progress = null)
        {
            // FIXME: refactor into different concerns, there's way to much being done here

            return(await Task.Run(async() =>
            {
                // start with a failure result, set to success when succeeding
                var result = new CertificateRequestResult {
                    ManagedItem = managedSite, IsSuccess = false, Message = ""
                };

                var config = managedSite.RequestConfig;
                try
                {
                    // run pre-request script, if set
                    if (!string.IsNullOrEmpty(config.PreRequestPowerShellScript))
                    {
                        try
                        {
                            string scriptOutput = await PowerShellManager.RunScript(result, config.PreRequestPowerShellScript);
                            LogMessage(managedSite.Id, $"Pre-Request Script output: \n{scriptOutput}");
                        }
                        catch (Exception ex)
                        {
                            LogMessage(managedSite.Id, $"Pre-Request Script error:\n{ex.Message}");
                        }
                    }

                    // if the script has requested the certificate request to be aborted, skip the request
                    if (result.Abort)
                    {
                        LogMessage(managedSite.Id, $"Certificate Request Aborted: {managedSite.Name}");
                        result.Message = "Certificate Request was aborted by PS script";
                        goto CertRequestAborted;
                    }

                    LogMessage(managedSite.Id, $"Beginning Certificate Request Process: {managedSite.Name}");

                    //enable or disable EFS flag on private key certs based on preference
                    if (Properties.Settings.Default.EnableEFS)
                    {
                        _vaultProvider.EnableSensitiveFileEncryption();
                    }

                    //primary domain and each subject alternative name must now be registered as an identifier with LE and validated
                    ReportProgress(progress, new RequestProgressState {
                        IsRunning = true, CurrentState = RequestState.Running, Message = "Registering Domain Identifiers"
                    });

                    await Task.Delay(200); //allow UI update, we should we using async calls instead

                    List <string> allDomains = new List <string> {
                        config.PrimaryDomain
                    };

                    if (config.SubjectAlternativeNames != null)
                    {
                        allDomains.AddRange(config.SubjectAlternativeNames);
                    }

                    // begin by assuming all identifiers are valid
                    bool allIdentifiersValidated = true;

                    if (config.ChallengeType == null)
                    {
                        config.ChallengeType = "http-01";
                    }

                    List <PendingAuthorization> identifierAuthorizations = new List <PendingAuthorization>();
                    var distinctDomains = allDomains.Distinct();

                    // perform validation process for each domain
                    foreach (var domain in distinctDomains)
                    {
                        //begin authorization process (register identifier, request authorization if not already given)
                        var domainIdentifierId = _vaultProvider.ComputeDomainIdentifierId(domain);

                        LogMessage(managedSite.Id, $"Attempting Domain Validation: {domain}", LogItemType.CertificateRequestStarted);
                        ReportProgress(progress, $"Registering and Validating {domain} ");

                        //TODO: make operations async and yield IO of vault

                        /*var authorization = await Task.Run(() =>
                         * {
                         *  return vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);
                         * });*/

                        // begin authorization by registering the domain identifier. This may return
                        // an already validated authorization or we may still have to complete the
                        // authorization challenge
                        var authorization = _vaultProvider.BeginRegistrationAndValidation(config, domainIdentifierId, challengeType: config.ChallengeType, domain: domain);

                        if (authorization != null && authorization.Identifier != null)
                        {
                            // check if authorization is pending, it may already be valid if an
                            // existing authorization was reused
                            if (authorization.Identifier.IsAuthorizationPending)
                            {
                                if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                                {
                                    ReportProgress(progress, $"Performing Challenge Response via IIS: {domain} ");

                                    //ask LE to check our answer to their authorization challenge (http), LE will then attempt to fetch our answer, if all accessible and correct (authorized) LE will then allow us to request a certificate
                                    //prepare IIS with answer for the LE challenege
                                    authorization = _vaultProvider.PerformIISAutomatedChallengeResponse(_iisManager, managedSite, authorization);

                                    //if we attempted extensionless config checks, report any errors
                                    if (config.PerformAutoConfig && !authorization.ExtensionlessConfigCheckedOK)
                                    {
                                        LogMessage(managedSite.Id, $"Failed prerequisite configuration checks ({ managedSite.ItemType })", LogItemType.CertficateRequestFailed);

                                        _siteManager.StoreSettings();

                                        result.Message = "Automated configuration checks failed. Authorizations will not be able to complete.\nCheck you have http bindings for your site and ensure you can browse to http://" + domain + "/.well-known/acme-challenge/configcheck before proceeding.";
                                        ReportProgress(progress, new RequestProgressState {
                                            CurrentState = RequestState.Error, Message = result.Message, Result = result
                                        });

                                        break;
                                    }
                                    else
                                    {
                                        ReportProgress(progress, new RequestProgressState {
                                            CurrentState = RequestState.Running, Message = $"Requesting Validation from Let's Encrypt: {domain}"
                                        });

                                        //ask LE to validate our challenge response
                                        _vaultProvider.SubmitChallenge(domainIdentifierId, config.ChallengeType);

                                        bool identifierValidated = _vaultProvider.CompleteIdentifierValidationProcess(authorization.Identifier.Alias);

                                        if (!identifierValidated)
                                        {
                                            ReportProgress(progress, new RequestProgressState {
                                                CurrentState = RequestState.Error, Message = "Domain validation failed: " + domain
                                            }, managedSite.Id);

                                            allIdentifiersValidated = false;
                                        }
                                        else
                                        {
                                            ReportProgress(progress, new RequestProgressState {
                                                CurrentState = RequestState.Running, Message = "Domain validation completed: " + domain
                                            }, managedSite.Id);

                                            identifierAuthorizations.Add(authorization);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // we already have a completed authorization, check it's valid
                                if (authorization.Identifier.Status == "valid")
                                {
                                    LogMessage(managedSite.Id, $"Domain already has current authorization, skipping verification: { domain }");

                                    identifierAuthorizations.Add(new PendingAuthorization {
                                        Identifier = authorization.Identifier
                                    });
                                }
                                else
                                {
                                    LogMessage(managedSite.Id, $"Domain authorization failed : { domain } ");

                                    allIdentifiersValidated = false;
                                }
                            }
                        }
                        else
                        {
                            // could not begin authorization
                            LogMessage(managedSite.Id, $"Could not begin authorization for domain with Let's Encrypt: { domain } ");
                            allIdentifiersValidated = false;
                        }

                        // abandon authorization attempts if one of our domains has failed verification
                        if (!allIdentifiersValidated)
                        {
                            break;
                        }
                    }

                    //check if all identifiers have a valid authorization
                    if (identifierAuthorizations.Count != distinctDomains.Count())
                    {
                        allIdentifiersValidated = false;
                    }

                    if (allIdentifiersValidated)
                    {
                        string primaryDnsIdentifier = identifierAuthorizations.First().Identifier.Alias;
                        string[] alternativeDnsIdentifiers = identifierAuthorizations.Select(i => i.Identifier.Alias).ToArray();

                        ReportProgress(progress, new RequestProgressState {
                            CurrentState = RequestState.Running, Message = "Requesting Certificate via Lets Encrypt"
                        }, managedSite.Id);

                        // Perform CSR request
                        // FIXME: make call async
                        var certRequestResult = _vaultProvider.PerformCertificateRequestProcess(primaryDnsIdentifier, alternativeDnsIdentifiers);

                        if (certRequestResult.IsSuccess)
                        {
                            ReportProgress(progress, new RequestProgressState {
                                CurrentState = RequestState.Success, Message = "Completed Certificate Request."
                            }, managedSite.Id);

                            string pfxPath = certRequestResult.Result.ToString();

                            // update managed site summary
                            try
                            {
                                var certInfo = new CertificateManager().GetCertificate(pfxPath);
                                managedSite.DateStart = certInfo.NotBefore;
                                managedSite.DateExpiry = certInfo.NotAfter;
                                managedSite.DateRenewed = DateTime.Now;

                                managedSite.CertificatePath = pfxPath;

                                //ensure certificate contains all the requested domains
                                var subjectNames = certInfo.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.UpnName, false);

                                LogMessage(managedSite.Id, "New certificate contains following domains: " + subjectNames, LogItemType.GeneralInfo);
                            }
                            catch (Exception)
                            {
                                LogMessage(managedSite.Id, "Failed to parse certificate dates", LogItemType.GeneralError);
                            }

                            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS && config.PerformAutomatedCertBinding)
                            {
                                ReportProgress(progress, new RequestProgressState {
                                    CurrentState = RequestState.Running, Message = "Performing Automated Certificate Binding"
                                });

                                // Install certificate into certificate store and bind to IIS site
                                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                                {
                                    //all done
                                    LogMessage(managedSite.Id, "Completed certificate request and automated bindings update (IIS)", LogItemType.CertificateRequestSuccessful);

                                    _siteManager.UpdatedManagedSite(managedSite);

                                    result.IsSuccess = true;
                                    result.Message = $"Certificate installed and SSL bindings updated for {config.PrimaryDomain }";
                                    ReportProgress(progress, new RequestProgressState {
                                        IsRunning = false, CurrentState = RequestState.Success, Message = result.Message
                                    });
                                }
                                else
                                {
                                    result.Message = $"An error occurred installing the certificate. Certificate file may not be valid: {pfxPath}";
                                    LogMessage(managedSite.Id, result.Message, LogItemType.GeneralError);
                                }
                            }
                            else
                            {
                                //user has opted for manual binding of certificate

                                _siteManager.UpdatedManagedSite(managedSite);

                                result.IsSuccess = true;
                                result.Message = $"Certificate created ready for manual binding: {pfxPath}";
                                LogMessage(managedSite.Id, result.Message, LogItemType.CertificateRequestSuccessful);
                            }
                        }
                        else
                        {
                            result.Message = $"The Let's Encrypt service did not issue a valid certificate in the time allowed. {(certRequestResult.ErrorMessage ?? "")}";
                            LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);
                        }
                    }
                    else
                    {
                        result.Message = "Validation of the required challenges did not complete successfully. Please ensure all domains to be referenced in the Certificate can be used to access this site without redirection. ";
                        LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);
                    }

                    // Goto label for aborted certificate request
                    CertRequestAborted : { }
                }
                catch (Exception exp)
                {
                    result.IsSuccess = false;
                    result.Message = managedSite.Name + ": Request failed - " + exp.Message + " " + exp.ToString();
                    LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);

                    System.Diagnostics.Debug.WriteLine(exp.ToString());
                }
                finally
                {
                    // if the request was not aborted, run post-request script, if set
                    if (!result.Abort && !string.IsNullOrEmpty(config.PostRequestPowerShellScript))
                    {
                        try
                        {
                            string scriptOutput = await PowerShellManager.RunScript(result, config.PostRequestPowerShellScript);
                            LogMessage(managedSite.Id, $"Post-Request Script output:\n{scriptOutput}");
                        }
                        catch (Exception ex)
                        {
                            LogMessage(managedSite.Id, $"Post-Request Script error:\n{ex.Message}");
                        }
                    }
                }
                return result;
            }));
        }
예제 #3
0
        public async Task <CertificateRequestResult> PerformCertificateRequest(VaultManager vaultManager, ManagedSite managedSite, IProgress <RequestProgressState> progress = null)
        {
            // FIXME: refactor into different concerns, there's way to much being done here

            return(await Task.Run(async() =>
            {
                try
                {
                    ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                        EventDate = DateTime.UtcNow, LogItemType = LogItemType.GeneralInfo, Message = "Beginning Certificate Request Process: " + managedSite.Name
                    });

                    bool enableIdentifierReuse = false;

                    if (vaultManager == null)
                    {
                        vaultManager = GetVaultManager();
                    }

                    //enable or disable EFS flag on private key certs based on preference
                    vaultManager.UseEFSForSensitiveFiles = Properties.Settings.Default.EnableEFS;

                    //primary domain and each subject alternative name must now be registered as an identifier with LE and validated

                    if (progress != null)
                    {
                        progress.Report(new RequestProgressState {
                            IsRunning = true, CurrentState = RequestState.Running, Message = "Registering Domain Identifiers"
                        });
                    }

                    await Task.Delay(200); //allow UI update

                    var config = managedSite.RequestConfig;

                    List <string> allDomains = new List <string> {
                        config.PrimaryDomain
                    };

                    if (config.SubjectAlternativeNames != null)
                    {
                        allDomains.AddRange(config.SubjectAlternativeNames);
                    }

                    bool allIdentifiersValidated = true;

                    if (config.ChallengeType == null)
                    {
                        config.ChallengeType = "http-01";
                    }

                    List <PendingAuthorization> identifierAuthorizations = new List <PendingAuthorization>();
                    var distinctDomains = allDomains.Distinct();

                    foreach (var domain in distinctDomains)
                    {
                        var identifierAlias = vaultManager.ComputeIdentifierAlias(domain);

                        //check if this domain already has an associated identifier registerd with LetsEncrypt which hasn't expired yet
                        //await Task.Delay(200); //allow UI update

                        ACMESharp.Vault.Model.IdentifierInfo existingIdentifier = null;

                        if (enableIdentifierReuse)
                        {
                            existingIdentifier = vaultManager.GetIdentifier(domain.Trim().ToLower());
                        }

                        bool identifierAlreadyValid = false;
                        if (existingIdentifier != null &&
                            existingIdentifier.Authorization != null &&
                            (existingIdentifier.Authorization.Status == "valid" || existingIdentifier.Authorization.Status == "pending") &&
                            existingIdentifier.Authorization.Expires > DateTime.Now.AddDays(1))
                        {
                            //we have an existing validated identifier, reuse that for this certificate request
                            identifierAlias = existingIdentifier.Alias;

                            if (existingIdentifier.Authorization.Status == "valid")
                            {
                                identifierAlreadyValid = true;
                            }

                            // managedSite.AppendLog(new ManagedSiteLogItem { EventDate =
                            // DateTime.UtcNow, LogItemType = LogItemType.CertificateRequestStarted,
                            // Message = "Attempting Certificate Request: " + managedSite.SiteType });
                            System.Diagnostics.Debug.WriteLine("Reusing existing valid non-expired identifier for the domain " + domain);
                        }

                        ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                            EventDate = DateTime.UtcNow, LogItemType = LogItemType.CertificateRequestStarted, Message = "Attempting Domain Validation: " + domain
                        });

                        //begin authorization process (register identifier, request authorization if not already given)
                        if (progress != null)
                        {
                            progress.Report(new RequestProgressState {
                                Message = "Registering and Validating " + domain
                            });
                        }

                        //TODO: make operations async and yeild IO of vault

                        /*var authorization = await Task.Run(() =>
                         * {
                         *  return vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);
                         * });*/

                        var authorization = vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);

                        if (authorization != null && authorization.Identifier != null && !identifierAlreadyValid)
                        {
                            if (authorization.Identifier.Authorization.IsPending())
                            {
                                if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                                {
                                    if (progress != null)
                                    {
                                        progress.Report(new RequestProgressState {
                                            Message = "Performing Challenge Response via IIS: " + domain
                                        });
                                    }

                                    //ask LE to check our answer to their authorization challenge (http), LE will then attempt to fetch our answer, if all accessible and correct (authorized) LE will then allow us to request a certificate
                                    //prepare IIS with answer for the LE challenege
                                    authorization = vaultManager.PerformIISAutomatedChallengeResponse(config, authorization);

                                    //if we attempted extensionless config checks, report any errors
                                    if (config.PerformAutoConfig && !authorization.ExtensionlessConfigCheckedOK)
                                    {
                                        ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                                            EventDate = DateTime.UtcNow, LogItemType = LogItemType.CertficateRequestFailed, Message = "Failed prerequisite configuration (" + managedSite.ItemType + ")"
                                        });
                                        siteManager.StoreSettings();

                                        var result = new CertificateRequestResult {
                                            ManagedItem = managedSite, IsSuccess = false, Message = "Automated configuration checks failed. Authorizations will not be able to complete.\nCheck you have http bindings for your site and ensure you can browse to http://" + domain + "/.well-known/acme-challenge/configcheck before proceeding."
                                        };
                                        if (progress != null)
                                        {
                                            progress.Report(new RequestProgressState {
                                                CurrentState = RequestState.Error, Message = result.Message, Result = result
                                            });
                                        }

                                        return result;
                                    }
                                    else
                                    {
                                        if (progress != null)
                                        {
                                            progress.Report(new RequestProgressState {
                                                CurrentState = RequestState.Running, Message = "Requesting Validation from Lets Encrypt: " + domain
                                            });
                                        }

                                        //ask LE to validate our challenge response
                                        vaultManager.SubmitChallenge(identifierAlias, config.ChallengeType);

                                        bool identifierValidated = vaultManager.CompleteIdentifierValidationProcess(authorization.Identifier.Alias);

                                        if (!identifierValidated)
                                        {
                                            if (progress != null)
                                            {
                                                progress.Report(new RequestProgressState {
                                                    CurrentState = RequestState.Error, Message = "Domain validation failed: " + domain
                                                });
                                            }

                                            allIdentifiersValidated = false;
                                        }
                                        else
                                        {
                                            if (progress != null)
                                            {
                                                progress.Report(new RequestProgressState {
                                                    CurrentState = RequestState.Running, Message = "Domain validation completed: " + domain
                                                });
                                            }

                                            identifierAuthorizations.Add(authorization);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if (authorization.Identifier.Authorization.Status == "valid")
                                {
                                    identifierAuthorizations.Add(new PendingAuthorization {
                                        Identifier = authorization.Identifier
                                    });
                                }
                            }
                        }
                        else
                        {
                            if (identifierAlreadyValid)
                            {
                                //we have previously validated this identifier and it has not yet expired, so we can just reuse it in our cert request
                                identifierAuthorizations.Add(new PendingAuthorization {
                                    Identifier = existingIdentifier
                                });
                            }
                        }
                    }

                    //check if all identifiers validates
                    if (identifierAuthorizations.Count == distinctDomains.Count())
                    {
                        allIdentifiersValidated = true;
                    }

                    if (allIdentifiersValidated)
                    {
                        string primaryDnsIdentifier = identifierAuthorizations.First().Identifier.Alias;
                        string[] alternativeDnsIdentifiers = identifierAuthorizations.Where(i => i.Identifier.Alias != primaryDnsIdentifier).Select(i => i.Identifier.Alias).ToArray();

                        if (progress != null)
                        {
                            progress.Report(new RequestProgressState {
                                CurrentState = RequestState.Running, Message = "Requesting Certificate via Lets Encrypt"
                            });
                        }
                        //await Task.Delay(200); //allow UI update

                        var certRequestResult = vaultManager.PerformCertificateRequestProcess(primaryDnsIdentifier, alternativeDnsIdentifiers);
                        if (certRequestResult.IsSuccess)
                        {
                            if (progress != null)
                            {
                                progress.Report(new RequestProgressState {
                                    CurrentState = RequestState.Success, Message = "Completed Certificate Request."
                                });
                            }

                            string pfxPath = certRequestResult.Result.ToString();

                            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS && config.PerformAutomatedCertBinding)
                            {
                                if (progress != null)
                                {
                                    progress.Report(new RequestProgressState {
                                        CurrentState = RequestState.Running, Message = "Performing Automated Certificate Binding"
                                    });
                                }
                                //await Task.Delay(200); //allow UI update

                                var iisManager = new IISManager();

                                //Install certificate into certificate store and bind to IIS site
                                if (iisManager.InstallCertForRequest(managedSite.RequestConfig, pfxPath, cleanupCertStore: true))
                                {
                                    //all done
                                    ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                                        EventDate = DateTime.UtcNow, LogItemType = LogItemType.CertificateRequestSuccessful, Message = "Completed certificate request and automated bindings update (IIS)"
                                    });

                                    //udpate managed site summary

                                    try
                                    {
                                        var certInfo = new CertificateManager().GetCertificate(pfxPath);
                                        managedSite.DateStart = certInfo.NotBefore;
                                        managedSite.DateExpiry = certInfo.NotAfter;
                                        managedSite.DateRenewed = DateTime.Now;

                                        managedSite.CertificatePath = pfxPath;
                                    }
                                    catch (Exception)
                                    {
                                        ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                                            EventDate = DateTime.UtcNow, LogItemType = LogItemType.GeneralWarning, Message = "Failed to parse certificate dates"
                                        });
                                    }
                                    siteManager.UpdatedManagedSite(managedSite);

                                    var result = new CertificateRequestResult {
                                        ManagedItem = managedSite, IsSuccess = true, Message = "Certificate installed and SSL bindings updated for " + config.PrimaryDomain
                                    };
                                    if (progress != null)
                                    {
                                        progress.Report(new RequestProgressState {
                                            IsRunning = false, CurrentState = RequestState.Success, Message = result.Message
                                        });
                                    }

                                    return result;
                                }
                                else
                                {
                                    return new CertificateRequestResult {
                                        ManagedItem = managedSite, IsSuccess = false, Message = "An error occurred installing the certificate. Certificate file may not be valid: " + pfxPath
                                    };
                                }
                            }
                            else
                            {
                                //user has opted for manual binding of certificate
                                try
                                {
                                    var certInfo = new CertificateManager().GetCertificate(pfxPath);
                                    managedSite.DateStart = certInfo.NotBefore;
                                    managedSite.DateExpiry = certInfo.NotAfter;
                                    managedSite.DateRenewed = DateTime.Now;

                                    managedSite.CertificatePath = pfxPath;
                                }
                                catch (Exception)
                                {
                                    ManagedSiteLog.AppendLog(managedSite.Id, new ManagedSiteLogItem {
                                        EventDate = DateTime.UtcNow, LogItemType = LogItemType.GeneralWarning, Message = "Failed to parse certificate dates"
                                    });
                                }
                                siteManager.UpdatedManagedSite(managedSite);

                                return new CertificateRequestResult {
                                    ManagedItem = managedSite, IsSuccess = true, Message = "Certificate created ready for manual binding: " + pfxPath
                                };
                            }
                        }
                        else
                        {
                            return new CertificateRequestResult {
                                ManagedItem = managedSite, IsSuccess = false, Message = "The Let's Encrypt service did not issue a valid certificate in the time allowed. " + (certRequestResult.ErrorMessage ?? "")
                            };
                        }
                    }
                    else
                    {
                        return new CertificateRequestResult {
                            ManagedItem = managedSite, IsSuccess = false, Message = "Validation of the required challenges did not complete successfully. Please ensure all domains to be referenced in the Certificate can be used to access this site without redirection. "
                        };
                    }
                }
                catch (Exception exp)
                {
                    System.Diagnostics.Debug.WriteLine(exp.ToString());
                    return new CertificateRequestResult {
                        ManagedItem = managedSite, IsSuccess = false, Message = managedSite.Name + ": Request failed - " + exp.Message
                    };
                }
            }));
        }
예제 #4
0
파일: Program.cs 프로젝트: rokx/AutoACME
        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();
                    }
                }
            }
        }
예제 #5
0
        private static ActionResult InvokePowershell(CertificateRequestResult result, string executionPolicy, string scriptFile, Dictionary <string, object> parameters, string scriptContent, PowerShell shell, bool autoConvertBoolean = true)
        {
            // ensure execution policy will allow the script to run, default to system default, default policy is set in service config object

            if (!string.IsNullOrEmpty(executionPolicy))
            {
                shell.AddCommand("Set-ExecutionPolicy")
                .AddParameter("ExecutionPolicy", executionPolicy)
                .AddParameter("Scope", "Process")
                .AddParameter("Force")
                .Invoke();
            }

            // add script command to invoke
            if (scriptFile != null)
            {
                shell.AddCommand(scriptFile);
            }
            else
            {
                shell.AddScript(scriptContent);
            }

            // pass the result to the script if present
            if (result != null)
            {
                shell.AddParameter("result", result);
            }

            // pass parameters to script if present
            if (parameters != null)
            {
                foreach (var a in parameters)
                {
                    var val = a.Value;
                    if (autoConvertBoolean)
                    {
                        if (val != null && val?.ToString().ToLower() == "true")
                        {
                            val = true;
                        }
                        else if (val != null && val?.ToString().ToLower() == "false")
                        {
                            val = false;
                        }
                    }
                    shell.AddParameter(a.Key, val);
                }
            }

            var errors = new List <string>();

            // accumulate output
            var output = new StringBuilder();

            // capture errors

            shell.Streams.Error.DataAdded += (sender, args) =>
            {
                var error = shell.Streams.Error[args.Index];
                var src   = error.InvocationInfo.MyCommand?.ToString() ?? error.InvocationInfo.InvocationName;
                var msg   = $"{src}: {error}\n{error.InvocationInfo.PositionMessage}";

                errors.Add(msg);
            };

            // capture write-* methods (except write-host)

            // TODO: one of these streams may be causing ssh hang when ssh spwaned as part of script..

            shell.Streams.Warning.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Warning[args.Index].Message);
            shell.Streams.Debug.DataAdded   += (sender, args) => output.AppendLine(shell.Streams.Debug[args.Index].Message);
            shell.Streams.Verbose.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Verbose[args.Index].Message);


            var outputData = new PSDataCollection <PSObject>();

            outputData.DataAdded += (sender, args) =>
            {
                // capture all main output
                var data = outputData[args.Index]?.BaseObject;
                if (data != null)
                {
                    output.AppendLine(data.ToString());
                }
            };

            try
            {
                var async = shell.BeginInvoke <PSObject, PSObject>(null, outputData);

                var maxWait     = 60 * 5; // 5 min timeout
                var currentWait = 0;
                var pollSeconds = 5;

                bool timeoutOccurred = false;

                while (!timeoutOccurred && !async.AsyncWaitHandle.WaitOne(pollSeconds * 1000, false))
                {
                    // poll while async task is still running
                    currentWait += pollSeconds;

                    if (currentWait <= maxWait)
                    {
                        output.AppendLine($"Waiting for powershell to complete..{currentWait}s");
                    }
                    else
                    {
                        output.AppendLine($"Timeout waiting for powershell to complete ({currentWait}s)");
                        errors.Add($"Script did not complete in the required time. ({maxWait}s)");
                        timeoutOccurred = true;
                    }
                }

                try
                {
                    if (async.IsCompleted)
                    {
                        shell.EndInvoke(async);
                    }
                }
                catch (Exception ex)
                {
                    errors.Add($"Script invoke failed: {ex}");
                }

                if (errors.Any())
                {
                    foreach (var e in errors)
                    {
                        output.AppendLine("Error: " + e);
                    }
                }
                return(new ActionResult(output.ToString().TrimEnd('\n'), !errors.Any()));
            }
            catch (ParseException ex)
            {
                // this should only happen in case of script syntax errors, otherwise
                // errors would be output via the invoke's error stream
                output.AppendLine($"{ex.Message}");

                return(new ActionResult(output.ToString().TrimEnd('\n'), false));
            }
        }
예제 #6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="powershellExecutionPolicy">Unrestricted etc, </param>
        /// <param name="result"></param>
        /// <param name="scriptFile"></param>
        /// <param name="parameters"></param>
        /// <param name="scriptContent"></param>
        /// <param name="credentials"></param>
        /// <returns></returns>
        public static async Task <ActionResult> RunScript(
            string powershellExecutionPolicy,
            CertificateRequestResult result,
            string scriptFile = null,
            Dictionary <string, object> parameters = null,
            string scriptContent = null,
            Dictionary <string, string> credentials = null,
            string logonType = null
            )
        {
            // argument check for script file existence and .ps1 extension
            FileInfo scriptInfo = null;

            if (scriptContent == null)
            {
                scriptInfo = new FileInfo(scriptFile);
                if (!scriptInfo.Exists)
                {
                    throw new ArgumentException($"File '{scriptFile}' does not exist.");
                }
                if (scriptInfo.Extension.ToLower() != ".ps1")
                {
                    throw new ArgumentException($"File '{scriptFile}' is not a powershell script.");
                }
            }

            try
            {
                // create a new runspace to isolate the scripts
                using (var runspace = RunspaceFactory.CreateRunspace())
                {
                    runspace.Open();

                    // set working directory to the script file's directory
                    if (scriptInfo != null)
                    {
                        runspace.SessionStateProxy.Path.SetLocation(scriptInfo.DirectoryName);
                    }

                    using (var shell = PowerShell.Create())
                    {
                        shell.Runspace = runspace;

                        if (credentials != null && credentials.Any())
                        {
                            // run as windows user
                            UserCredentials windowsCredentials = null;

                            if (credentials != null && credentials.Count > 0)
                            {
                                try
                                {
                                    windowsCredentials = GetWindowsCredentials(credentials);
                                }
                                catch
                                {
                                    var err = "Command with Windows Credentials requires username and password.";

                                    return(new ActionResult(err, false));
                                }
                            }

                            // logon type affects the range of abilities the impersonated user has
                            var _defaultLogonType = LogonType.NewCredentials;

                            if (logonType == "network")
                            {
                                _defaultLogonType = LogonType.Network;
                            }
                            else if (logonType == "batch")
                            {
                                _defaultLogonType = LogonType.Batch;
                            }
                            else if (logonType == "service")
                            {
                                _defaultLogonType = LogonType.Service;
                            }
                            else if (logonType == "interactive")
                            {
                                _defaultLogonType = LogonType.Interactive;
                            }
                            else if (logonType == "newcredentials")
                            {
                                _defaultLogonType = LogonType.NewCredentials;
                            }

                            return(Impersonation.RunAsUser(windowsCredentials, _defaultLogonType, () =>
                            {
                                // run as current user
                                return InvokePowershell(result, powershellExecutionPolicy, scriptFile, parameters, scriptContent, shell);
                            }));
                        }
                        else
                        {
                            // run as current user
                            return(InvokePowershell(result, powershellExecutionPolicy, scriptFile, parameters, scriptContent, shell));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                return(new ActionResult($"Error - {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", false));
            }
        }
예제 #7
0
        private static ActionResult InvokePowershell(CertificateRequestResult result, string executionPolicy, string scriptFile, Dictionary <string, object> parameters, string scriptContent, PowerShell shell)
        {
            // ensure execution policy will allow the script to run, default to "Unrestricted", set in service config as "Default" to skip.

            if (executionPolicy != "Default")
            {
                shell.AddCommand("Set-ExecutionPolicy")
                .AddParameter("ExecutionPolicy", executionPolicy)
                .AddParameter("Scope", "Process")
                .AddParameter("Force")
                .Invoke();
            }

            // add script command to invoke
            if (scriptFile != null)
            {
                shell.AddCommand(scriptFile);
            }
            else
            {
                shell.AddScript(scriptContent);
            }

            // pass the result to the script if present
            if (result != null)
            {
                shell.AddParameter("result", result);
            }

            // pass parameters to script if present
            if (parameters != null)
            {
                foreach (var a in parameters)
                {
                    shell.AddParameter(a.Key, a.Value);
                }
            }


            var errors = new List <string>();

            // accumulate output
            var output = new StringBuilder();

            // capture errors
            shell.Streams.Error.DataAdded += (sender, args) =>
            {
                var error = shell.Streams.Error[args.Index];
                var src   = error.InvocationInfo.MyCommand?.ToString() ?? error.InvocationInfo.InvocationName;
                var msg   = $"{src}: {error}\n{error.InvocationInfo.PositionMessage}";
                output.AppendLine(msg);

                errors.Add(msg);
            };

            // capture write-* methods (except write-host)
            shell.Streams.Warning.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Warning[args.Index].Message);
            shell.Streams.Debug.DataAdded   += (sender, args) => output.AppendLine(shell.Streams.Debug[args.Index].Message);
            shell.Streams.Verbose.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Verbose[args.Index].Message);

            var outputData = new PSDataCollection <PSObject>();

            outputData.DataAdded += (sender, args) =>
            {
                // capture all main output
                var data = outputData[args.Index]?.BaseObject;
                if (data != null)
                {
                    output.AppendLine(data.ToString());
                }
            };


            try
            {
                var async = shell.BeginInvoke <PSObject, PSObject>(null, outputData);
                shell.EndInvoke(async);

                return(new ActionResult(output.ToString().TrimEnd('\n'), !errors.Any()));
            }
            catch (ParseException ex)
            {
                // this should only happen in case of script syntax errors, otherwise
                // errors would be output via the invoke's error stream
                output.AppendLine($"{ex.Message}");

                return(new ActionResult(output.ToString().TrimEnd('\n'), false));
            }
        }
예제 #8
0
        private async Task <List <ActionStep> > PerformTaskList(ILog log, bool isPreviewOnly, bool skipDeferredTasks, CertificateRequestResult result, IEnumerable <DeploymentTaskConfig> taskList, bool forceTaskExecute = false)
        {
            if (taskList == null || !taskList.Any())
            {
                // nothing to do
                return(new List <ActionStep>());
            }

            if (log == null)
            {
                log = ManagedCertificateLog.GetLogger(result.ManagedItem.Id, _loggingLevelSwitch);
            }

            // perform or preview each task

            var deploymentTasks = new List <DeploymentTask>();
            var steps           = new List <ActionStep>();

            foreach (var taskConfig in taskList)
            {
                // add task to execution list unless the task is deferred/manual and we are currently skipping deferred tasks

                if (taskConfig.TaskTrigger != TaskTriggerType.MANUAL || (taskConfig.TaskTrigger == TaskTriggerType.MANUAL && !skipDeferredTasks))
                {
                    try
                    {
                        var provider = DeploymentTaskProviderFactory.Create(taskConfig.TaskTypeId.ToLower(), _pluginManager.DeploymentTaskProviders);

                        Dictionary <string, string> credentials = null;

                        if (!string.IsNullOrEmpty(taskConfig.ChallengeCredentialKey))
                        {
                            credentials = await _credentialsManager.GetUnlockedCredentialsDictionary(taskConfig.ChallengeCredentialKey);
                        }

                        var deploymentTask = new DeploymentTask(provider, taskConfig, credentials);

                        deploymentTasks.Add(deploymentTask);
                    }
                    catch (Exception exp)
                    {
                        steps.Add(new ActionStep {
                            HasError = true, Title = "Task: " + taskConfig.TaskName, Description = "Cannot create task provider for deployment task: " + exp.ToString()
                        });
                    }
                }
            }

            ActionStep previousActionStep   = null;
            bool       shouldRunCurrentTask = true;
            string     taskTriggerReason    = "Task will run for any status";

            foreach (var task in deploymentTasks)
            {
                if (previousActionStep != null && (previousActionStep.HasError && !task.TaskConfig.RunIfLastStepFailed))
                {
                    shouldRunCurrentTask = false;
                    taskTriggerReason    = "Task will not run because previous task failed.";
                }
                else
                {
                    if (task.TaskConfig.TaskTrigger == TaskTriggerType.ANY_STATUS)
                    {
                        shouldRunCurrentTask = true;
                        taskTriggerReason    = "Task will run for any status";
                    }
                    else if (task.TaskConfig.TaskTrigger == TaskTriggerType.NOT_ENABLED)
                    {
                        shouldRunCurrentTask = false;
                        taskTriggerReason    = "Task is not enabled and will be skipped.";
                    }
                    else if (task.TaskConfig.TaskTrigger == TaskTriggerType.ON_SUCCESS)
                    {
                        if (result != null && (!result.Abort && result.IsSuccess))
                        {
                            shouldRunCurrentTask = true;
                            taskTriggerReason    = "Task is enabled and primary request was successful.";
                        }
                        else
                        {
                            shouldRunCurrentTask = false;
                            taskTriggerReason    = "Task is enabled but will not run because primary request unsuccessful.";
                        }
                    }
                    else if (task.TaskConfig.TaskTrigger == TaskTriggerType.ON_ERROR)
                    {
                        if (result != null && (!result.Abort && result.IsSuccess))
                        {
                            shouldRunCurrentTask = false;
                            taskTriggerReason    = "Task is enabled but will not run because primary request was successful.";
                        }
                        else
                        {
                            shouldRunCurrentTask = true;
                            taskTriggerReason    = "Task is enabled and will run because primary request was unsuccessful.";
                        }
                    }
                    else if (task.TaskConfig.TaskTrigger == TaskTriggerType.MANUAL)
                    {
                        if (skipDeferredTasks)
                        {
                            shouldRunCurrentTask = false;
                            taskTriggerReason    = "Task is enabled but will not run because execution is deferred/manual.";
                        }
                        else
                        {
                            shouldRunCurrentTask = true;
                            taskTriggerReason    = "Task is enabled and will run because deferred/manual tasks are not being skipped.";
                        }
                    }
                }

                if (forceTaskExecute == true)
                {
                    if (!shouldRunCurrentTask)
                    {
                        shouldRunCurrentTask = true;
                        taskTriggerReason    = $"Task is being has been forced to run. Normal status would be [{taskTriggerReason}]";
                    }
                }

                var taskResults = new List <ActionResult>();

                if (shouldRunCurrentTask)
                {
                    log.Information($"Task [{task.TaskConfig.TaskName}] :: {taskTriggerReason}");
                    task.TaskConfig.DateLastExecuted = DateTime.Now;
                    taskResults = await task.Execute(log, result, CancellationToken.None, isPreviewOnly : isPreviewOnly);

                    if (!isPreviewOnly)
                    {
                        if (taskResults?.All(t => t.IsSuccess) == true)
                        {
                            _tc?.TrackEvent("TaskCompleted", new Dictionary <string, string> {
                                { "TaskType", task.TaskConfig.TaskTypeId }
                            });
                        }
                        else
                        {
                            if (!forceTaskExecute)
                            {
                                _tc?.TrackEvent("TaskFailed", new Dictionary <string, string> {
                                    { "TaskType", task.TaskConfig.TaskTypeId }
                                });
                            }
                        }
                    }
                }
                else
                {
                    taskResults.Add(new ActionResult($"Task [{task.TaskConfig.TaskName}] :: {taskTriggerReason}", true));
                }

                var subSteps = new List <ActionStep>();

                var stepIndex = 1;

                foreach (var r in taskResults)
                {
                    subSteps.Add(new ActionStep
                    {
                        HasError    = !r.IsSuccess,
                        Description = r.Message,
                        Title       = $"Task Step {stepIndex} of {task.TaskConfig.TaskName}",
                        Key         = task.TaskConfig.Id + "_" + stepIndex,
                        Category    = "Task Step"
                    });


                    if (r.IsSuccess)
                    {
                        log?.Information(r.Message);
                    }
                    else
                    {
                        log?.Error(r.Message);
                    }

                    stepIndex++;
                }


                var overallTaskResult = "Unknown";

                if (taskResults != null && taskResults.Any(t => t.IsSuccess == false))
                {
                    overallTaskResult = taskResults.First(t => t.IsSuccess == false).Message;
                }
                else
                {
                    if (isPreviewOnly)
                    {
                        overallTaskResult = taskTriggerReason;
                    }
                    else
                    {
                        if (shouldRunCurrentTask)
                        {
                            overallTaskResult = "Task Completed OK";
                        }
                        else
                        {
                            overallTaskResult = taskTriggerReason;
                        }
                    }
                }

                var hasError = (taskResults != null && taskResults.Any(t => t.IsSuccess == false) ? true : false);

                var currentStep = new ActionStep
                {
                    Key         = task.TaskConfig.Id,
                    Title       = task.TaskConfig.TaskName,
                    Category    = "Task",
                    HasError    = hasError,
                    Description = overallTaskResult,
                    HasWarning  = !shouldRunCurrentTask,
                    Substeps    = subSteps
                };


                task.TaskConfig.LastRunStatus = hasError ? RequestState.Error : RequestState.Success;
                task.TaskConfig.LastResult    = overallTaskResult;

                steps.Add(currentStep);

                previousActionStep = currentStep;
            }

            return(steps);
        }
예제 #9
0
        public async Task <CertificateRequestResult> PerformCertificateRequest(ManagedSite managedSite, IProgress <RequestProgressState> progress = null)
        {
            // FIXME: refactor into different concerns, there's way too much being done here
            if (managedSite.RequestConfig.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && managedSite.RequestConfig.PerformExtensionlessConfigChecks)
            {
                ReportProgress(progress, new RequestProgressState {
                    IsRunning = true, CurrentState = RequestState.Running, Message = Certify.CoreSR.CertifyManager_PerformingConfigTests
                });

                var testResult = await TestChallenge(managedSite, isPreviewMode : false);

                if (!testResult.IsOK)
                {
                    return(new CertificateRequestResult {
                        ManagedItem = managedSite, IsSuccess = false, Message = String.Join("; ", testResult.FailedItemSummary), Result = testResult.Result
                    });
                }
            }

            return(await Task.Run(async() =>
            {
                // start with a failure result, set to success when succeeding
                var result = new CertificateRequestResult {
                    ManagedItem = managedSite, IsSuccess = false, Message = ""
                };

                var config = managedSite.RequestConfig;
                try
                {
                    // run pre-request script, if set
                    if (!string.IsNullOrEmpty(config.PreRequestPowerShellScript))
                    {
                        try
                        {
                            string scriptOutput = await PowerShellManager.RunScript(result, config.PreRequestPowerShellScript);
                            LogMessage(managedSite.Id, $"Pre-Request Script output: \n{scriptOutput}");
                        }
                        catch (Exception ex)
                        {
                            LogMessage(managedSite.Id, $"Pre-Request Script error:\n{ex.Message}");
                        }
                    }

                    // if the script has requested the certificate request to be aborted, skip the request
                    if (result.Abort)
                    {
                        LogMessage(managedSite.Id, $"Certificate Request Aborted: {managedSite.Name}");
                        result.Message = Certify.CoreSR.CertificateRequestWasAbortedByPSScript;
                        goto CertRequestAborted;
                    }

                    LogMessage(managedSite.Id, $"Beginning Certificate Request Process: {managedSite.Name}");

                    //enable or disable EFS flag on private key certs based on preference
                    if (CoreAppSettings.Current.EnableEFS)
                    {
                        _vaultProvider.EnableSensitiveFileEncryption();
                    }

                    //primary domain and each subject alternative name must now be registered as an identifier with LE and validated
                    ReportProgress(progress, new RequestProgressState {
                        IsRunning = true, CurrentState = RequestState.Running, Message = CoreSR.CertifyManager_RegisterDomainIdentity
                    });

                    await Task.Delay(200); //allow UI update, we should we using async calls instead

                    List <string> allDomains = new List <string> {
                        config.PrimaryDomain
                    };

                    if (config.SubjectAlternativeNames != null)
                    {
                        allDomains.AddRange(config.SubjectAlternativeNames);
                    }

                    // begin by assuming all identifiers are valid
                    bool allIdentifiersValidated = true;

                    if (config.ChallengeType == null)
                    {
                        config.ChallengeType = ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP;
                    }

                    List <PendingAuthorization> identifierAuthorizations = new List <PendingAuthorization>();
                    var distinctDomains = allDomains.Distinct();

                    string failureSummaryMessage = null;

                    // perform validation process for each domain
                    foreach (var domain in distinctDomains)
                    {
                        //begin authorization process (register identifier, request authorization if not already given)
                        var domainIdentifierId = _vaultProvider.ComputeDomainIdentifierId(domain);

                        LogMessage(managedSite.Id, $"Attempting Domain Validation: {domain}", LogItemType.CertificateRequestStarted);
                        ReportProgress(progress, string.Format(Certify.CoreSR.CertifyManager_RegisteringAndValidatingX0, domain));

                        //TODO: make operations async and yield IO of vault

                        /*var authorization = await Task.Run(() =>
                         * {
                         *  return vaultManager.BeginRegistrationAndValidation(config, identifierAlias, challengeType: config.ChallengeType, domain: domain);
                         * });*/

                        // begin authorization by registering the domain identifier. This may return
                        // an already validated authorization or we may still have to complete the
                        // authorization challenge. When rate limits are encountered, this step may fail.
                        var authorization = _vaultProvider.BeginRegistrationAndValidation(config, domainIdentifierId, challengeType: config.ChallengeType, domain: domain);

                        if (authorization != null && authorization.Identifier != null)
                        {
                            // check if authorization is pending, it may already be valid if an
                            // existing authorization was reused
                            if (authorization.Identifier.IsAuthorizationPending)
                            {
                                if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS)
                                {
                                    ReportProgress(progress, string.Format(Certify.CoreSR.CertifyManager_PerformingChallengeResponseViaIISX0, domain));

                                    // ask LE to check our answer to their authorization challenge
                                    // (http-01 or tls-sni-01), LE will then attempt to fetch our
                                    // answer, if all accessible and correct (authorized) LE will
                                    // then allow us to request a certificate
                                    authorization = _vaultProvider.PerformIISAutomatedChallengeResponse(_iisManager, managedSite, authorization);

                                    // pass authorization log items onto main log

                                    /*authorization.LogItems?.ForEach((msg) =>
                                     * {
                                     *  if (msg != null) LogMessage(managedSite.Id, msg, LogItemType.GeneralInfo);
                                     * });*/

                                    if ((config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP && config.PerformExtensionlessConfigChecks && !authorization.ExtensionlessConfigCheckedOK) ||
                                        (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI && config.PerformTlsSniBindingConfigChecks && !authorization.TlsSniConfigCheckedOK))
                                    {
                                        //if we failed the config checks, report any errors
                                        LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_FailedPrerequisiteCheck, managedSite.ItemType), LogItemType.CertficateRequestFailed);

                                        _siteManager.StoreSettings();

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_HTTP)
                                        {
                                            result.Message = string.Format(CoreSR.CertifyManager_AutomateConfigurationCheckFailed_HTTP, domain);
                                        }

                                        if (config.ChallengeType == ACMESharpCompat.ACMESharpUtils.CHALLENGE_TYPE_SNI)
                                        {
                                            result.Message = Certify.CoreSR.CertifyManager_AutomateConfigurationCheckFailed_SNI;
                                        }

                                        ReportProgress(progress, new RequestProgressState {
                                            CurrentState = RequestState.Error, Message = result.Message, Result = result
                                        });

                                        break;
                                    }
                                    else
                                    {
                                        ReportProgress(progress, new RequestProgressState {
                                            CurrentState = RequestState.Running, Message = string.Format(CoreSR.CertifyManager_ReqestValidationFromLetsEncrypt, domain)
                                        });
                                        try
                                        {
                                            //ask LE to validate our challenge response
                                            _vaultProvider.SubmitChallenge(domainIdentifierId, config.ChallengeType);

                                            bool identifierValidated = _vaultProvider.CompleteIdentifierValidationProcess(authorization.Identifier.Alias);

                                            if (!identifierValidated)
                                            {
                                                var identifierInfo = _vaultProvider.GetDomainIdentifier(domain);
                                                var errorMsg = identifierInfo?.ValidationError;
                                                var errorType = identifierInfo?.ValidationErrorType;

                                                failureSummaryMessage = string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain, errorMsg);
                                                ReportProgress(progress, new RequestProgressState {
                                                    CurrentState = RequestState.Error, Message = failureSummaryMessage
                                                }, managedSite.Id);

                                                allIdentifiersValidated = false;
                                            }
                                            else
                                            {
                                                ReportProgress(progress, new RequestProgressState {
                                                    CurrentState = RequestState.Running, Message = string.Format(CoreSR.CertifyManager_DomainValidationCompleted, domain)
                                                }, managedSite.Id);

                                                identifierAuthorizations.Add(authorization);
                                            }
                                        }
                                        finally
                                        {
                                            // clean up challenge answers
                                            // (.well-known/acme-challenge/* files for http-01 or iis
                                            // bindings for tls-sni-01)
                                            authorization.Cleanup();
                                        }
                                    }
                                }
                            }
                            else
                            {
                                // we already have a completed authorization, check it's valid
                                if (authorization.Identifier.Status == "valid")
                                {
                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationSkipVerifed, domain));

                                    identifierAuthorizations.Add(new PendingAuthorization {
                                        Identifier = authorization.Identifier
                                    });
                                }
                                else
                                {
                                    var errorMsg = "";
                                    if (authorization?.Identifier != null)
                                    {
                                        errorMsg = authorization.Identifier.ValidationError;
                                        var errorType = authorization.Identifier.ValidationErrorType;
                                    }

                                    failureSummaryMessage = $"Domain validation failed: {domain} \r\n{errorMsg}";

                                    LogMessage(managedSite.Id, string.Format(CoreSR.CertifyManager_DomainValidationFailed, domain));

                                    allIdentifiersValidated = false;
                                }
                            }
                        }
                        else
                        {
                            // could not begin authorization : TODO: pass error from authorization
                            // step to UI

                            var lastActionLogItem = _vaultProvider.GetLastActionLogItem();
                            var actionLogMsg = "";
                            if (lastActionLogItem != null)
                            {
                                actionLogMsg = lastActionLogItem.ToString();
                            }

                            LogMessage(managedSite.Id, $"Could not begin authorization for domain with Let's Encrypt: { domain } {(authorization?.AuthorizationError != null ? authorization?.AuthorizationError : "Could not register domain identifier")} - {actionLogMsg}");

                            /*if (authorization != null && authorization.LogItems != null)
                             * {
                             *  LogMessage(managedSite.Id, authorization.LogItems);
                             * }*/
                            allIdentifiersValidated = false;
                        }

                        // abandon authorization attempts if one of our domains has failed verification
                        if (!allIdentifiersValidated)
                        {
                            break;
                        }
                    }

                    //check if all identifiers have a valid authorization
                    if (identifierAuthorizations.Count != distinctDomains.Count())
                    {
                        allIdentifiersValidated = false;
                    }

                    if (allIdentifiersValidated)
                    {
                        string primaryDnsIdentifier = identifierAuthorizations.First().Identifier.Alias;
                        string[] alternativeDnsIdentifiers = identifierAuthorizations.Select(i => i.Identifier.Alias).ToArray();

                        ReportProgress(progress, new RequestProgressState {
                            CurrentState = RequestState.Running, Message = CoreSR.CertifyManager_RequestCertificate
                        }, managedSite.Id);

                        // Perform CSR request
                        // FIXME: make call async
                        var certRequestResult = _vaultProvider.PerformCertificateRequestProcess(primaryDnsIdentifier, alternativeDnsIdentifiers);

                        if (certRequestResult.IsSuccess)
                        {
                            ReportProgress(progress, new RequestProgressState {
                                CurrentState = RequestState.Success, Message = CoreSR.CertifyManager_CompleteRequest
                            }, managedSite.Id);

                            string pfxPath = certRequestResult.Result.ToString();

                            // update managed site summary
                            try
                            {
                                var certInfo = CertificateManager.LoadCertificate(pfxPath);
                                managedSite.DateStart = certInfo.NotBefore;
                                managedSite.DateExpiry = certInfo.NotAfter;
                                managedSite.DateRenewed = DateTime.Now;

                                managedSite.CertificatePath = pfxPath;
                                managedSite.CertificateRevoked = false;

                                //ensure certificate contains all the requested domains
                                var subjectNames = certInfo.GetNameInfo(System.Security.Cryptography.X509Certificates.X509NameType.UpnName, false);

                                //FIXME: LogMessage(managedSite.Id, "New certificate contains following domains: " + subjectNames, LogItemType.GeneralInfo);
                            }
                            catch (Exception)
                            {
                                LogMessage(managedSite.Id, "Failed to parse certificate dates", LogItemType.GeneralError);
                            }

                            if (managedSite.ItemType == ManagedItemType.SSL_LetsEncrypt_LocalIIS && config.PerformAutomatedCertBinding)
                            {
                                ReportProgress(progress, new RequestProgressState {
                                    CurrentState = RequestState.Running, Message = CoreSR.CertifyManager_AutoBinding
                                });

                                // Install certificate into certificate store and bind to IIS site
                                if (_iisManager.InstallCertForRequest(managedSite, pfxPath, cleanupCertStore: true))
                                {
                                    //all done
                                    LogMessage(managedSite.Id, CoreSR.CertifyManager_CompleteRequestAndUpdateBinding, LogItemType.CertificateRequestSuccessful);

                                    _siteManager.UpdatedManagedSite(managedSite);

                                    result.IsSuccess = true;
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstalledAndBindingUpdated, config.PrimaryDomain);
                                    ReportProgress(progress, new RequestProgressState {
                                        IsRunning = false, CurrentState = RequestState.Success, Message = result.Message
                                    });
                                }
                                else
                                {
                                    result.Message = string.Format(CoreSR.CertifyManager_CertificateInstallFailed, pfxPath);
                                    LogMessage(managedSite.Id, result.Message, LogItemType.GeneralError);
                                }
                            }
                            else
                            {
                                //user has opted for manual binding of certificate

                                _siteManager.UpdatedManagedSite(managedSite);

                                result.IsSuccess = true;
                                result.Message = string.Format(CoreSR.CertifyManager_CertificateCreatedForBinding, pfxPath);
                                LogMessage(managedSite.Id, result.Message, LogItemType.CertificateRequestSuccessful);
                            }
                        }
                        else
                        {
                            result.Message = string.Format(CoreSR.CertifyManager_LetsEncryptServiceTimeout, certRequestResult.ErrorMessage ?? "");
                            LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);
                        }
                    }
                    else
                    {
                        result.Message = string.Format(CoreSR.CertifyManager_ValidationForChallengeNotSuccess, (failureSummaryMessage != null ? failureSummaryMessage : ""));
                        LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);
                    }

                    // Goto label for aborted certificate request
                    CertRequestAborted : { }
                }
                catch (Exception exp)
                {
                    result.IsSuccess = false;
                    result.Message = string.Format(Certify.CoreSR.CertifyManager_RequestFailed, managedSite.Name, exp.Message, exp);
                    LogMessage(managedSite.Id, result.Message, LogItemType.CertficateRequestFailed);
                    //LogMessage(managedSite.Id, String.Join("\r\n", _vaultProvider.GetActionSummary())); FIXME: needs to be filtered in managed site
                    System.Diagnostics.Debug.WriteLine(exp.ToString());
                }
                finally
                {
                    // if the request was not aborted, perform post-request actions
                    if (!result.Abort)
                    {
                        // run post-request script, if set
                        if (!string.IsNullOrEmpty(config.PostRequestPowerShellScript))
                        {
                            try
                            {
                                string scriptOutput = await PowerShellManager.RunScript(result, config.PostRequestPowerShellScript);
                                LogMessage(managedSite.Id, $"Post-Request Script output:\n{scriptOutput}");
                            }
                            catch (Exception ex)
                            {
                                LogMessage(managedSite.Id, $"Post-Request Script error: {ex.Message}");
                            }
                        }
                        // run webhook triggers, if set
                        if ((config.WebhookTrigger == Webhook.ON_SUCCESS && result.IsSuccess) ||
                            (config.WebhookTrigger == Webhook.ON_ERROR && !result.IsSuccess) ||
                            (config.WebhookTrigger == Webhook.ON_SUCCESS_OR_ERROR))
                        {
                            try
                            {
                                var(success, code) = await Webhook.SendRequest(config, result.IsSuccess);
                                LogMessage(managedSite.Id, $"Webhook invoked: Url: {config.WebhookUrl}, Success: {success}, StatusCode: {code}");
                            }
                            catch (Exception ex)
                            {
                                LogMessage(managedSite.Id, $"Webhook error: {ex.Message}");
                            }
                        }
                    }
                }
                return result;
            }));
        }
예제 #10
0
        public static async Task <string> RunScript(CertificateRequestResult result, string scriptFile)
        {
            // argument check for script file existance and .ps1 extension
            var scriptInfo = new FileInfo(scriptFile);

            if (!scriptInfo.Exists)
            {
                throw new ArgumentException($"File '{scriptFile}' does not exist.");
            }
            if (scriptInfo.Extension != ".ps1")
            {
                throw new ArgumentException($"File '{scriptFile}' is not a powershell script.");
            }

            var config = SharedUtils.ServiceConfigManager.GetAppServiceConfig();

            try
            {
                // create a new runspace to isolate the scripts
                using (var runspace = RunspaceFactory.CreateRunspace())
                {
                    runspace.Open();

                    // set working directory to the script file's directory
                    runspace.SessionStateProxy.Path.SetLocation(scriptInfo.DirectoryName);

                    using (var shell = PowerShell.Create())
                    {
                        shell.Runspace = runspace;

                        // ensure execution policy will allow the script to run, default to "Unrestricted", set in service config as "Default" to skip.

                        if (config.PowershellExecutionPolicy != "Default")
                        {
                            shell.AddCommand("Set-ExecutionPolicy")
                            .AddParameter("ExecutionPolicy", config.PowershellExecutionPolicy)
                            .AddParameter("Scope", "Process")
                            .AddParameter("Force")
                            .Invoke();
                        }

                        // add script command to invoke
                        shell.AddCommand(scriptFile);

                        // pass the result to the script
                        shell.AddParameter("result", result);

                        // accumulate output
                        var output = new StringBuilder();

                        // capture errors
                        shell.Streams.Error.DataAdded += (sender, args) =>
                        {
                            var error = shell.Streams.Error[args.Index];
                            var src   = error.InvocationInfo.MyCommand?.ToString() ?? error.InvocationInfo.InvocationName;
                            output.AppendLine($"{src}: {error}\n{error.InvocationInfo.PositionMessage}");
                        };

                        // capture write-* methods (except write-host)
                        shell.Streams.Warning.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Warning[args.Index].Message);
                        shell.Streams.Debug.DataAdded   += (sender, args) => output.AppendLine(shell.Streams.Debug[args.Index].Message);
                        shell.Streams.Verbose.DataAdded += (sender, args) => output.AppendLine(shell.Streams.Verbose[args.Index].Message);

                        var outputData = new PSDataCollection <PSObject>();

                        outputData.DataAdded += (sender, args) =>
                        {
                            // capture all main output
                            var data = outputData[args.Index]?.BaseObject;
                            if (data != null)
                            {
                                output.AppendLine(data.ToString());
                            }
                        };

                        await Task.Run(() =>
                        {
                            try
                            {
                                var async = shell.BeginInvoke <PSObject, PSObject>(null, outputData);
                                shell.EndInvoke(async);
                            }
                            catch (ParseException ex)
                            {
                                // this should only happen in case of script syntax errors, otherwise
                                // errors would be output via the invoke's error stream
                                output.AppendLine($"{ex.Message}");
                            }
                        });

                        return(output.ToString().TrimEnd('\n'));
                    }
                }
            }
            catch (Exception ex)
            {
                return($"Error - {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}");
            }
        }
예제 #11
0
        private static ActionResult ExecutePowershellAsProcess(CertificateRequestResult result, string executionPolicy, string scriptFile, Dictionary <string, object> parameters, Dictionary <string, string> credentials, string scriptContent, PowerShell shell, bool autoConvertBoolean = true, string[] ignoredCommandExceptions = null, int timeoutMinutes = 5)
        {
            var _log = new StringBuilder();

            var commandExe = GetPowershellExePath();

            if (commandExe == null)
            {
                return(new ActionResult("Failed to locate powershell exe. Cannot launch as new process.", false));
            }

            if (!string.IsNullOrEmpty(scriptContent))
            {
                // script content would need to be run from a file, for that we need to run encrypted script content otherwise credentials would appear in temp file
                return(new ActionResult("Script content is not yet supported when used with launch as new process.", false));
            }

            var arguments = scriptFile;

            if (parameters?.Any(p => p.Key.ToLower() == "executionpolicy") == true)
            {
                executionPolicy = parameters.FirstOrDefault(p => p.Key.ToLower() == "executionpolicy").Value?.ToString();
            }

            if (!string.IsNullOrEmpty(executionPolicy))
            {
                arguments = $"-ExecutionPolicy {executionPolicy} " + arguments;
            }

            if (parameters?.Any() == true)
            {
                foreach (var p in parameters)
                {
                    arguments = arguments += $" -{p.Key}{(p.Value != null ? " " + p.Value : "")}";
                }
            }

            var scriptProcessInfo = new ProcessStartInfo()
            {
                RedirectStandardInput  = false,
                RedirectStandardOutput = true,
                RedirectStandardError  = true,
                UseShellExecute        = false,
                CreateNoWindow         = true,
                FileName  = commandExe,
                Arguments = arguments
            };

            // launch process with user credentials set
            if (credentials != null && credentials.ContainsKey("username") && credentials.ContainsKey("password"))
            {
                var username = credentials["username"];
                var pwd      = credentials["password"];

                credentials.TryGetValue("domain", out var domain);

                if (domain == null && !username.Contains(".\\") && !username.Contains("@"))
                {
                    domain = ".";
                }

                scriptProcessInfo.UserName = username;
                scriptProcessInfo.Domain   = domain;

                var sPwd = new SecureString();
                foreach (char c in pwd)
                {
                    sPwd.AppendChar(c);
                }
                sPwd.MakeReadOnly();

                scriptProcessInfo.Password = sPwd;

                _log.AppendLine($"Launching Process {commandExe} as User: {domain}\\{username}");
            }

            try
            {
                var process = new Process {
                    StartInfo = scriptProcessInfo
                };

                var logMessages = new StringBuilder();

                // capture output streams and add to log
                process.OutputDataReceived += (obj, a) =>
                {
                    if (a.Data != null)
                    {
                        logMessages.AppendLine(a.Data);
                    }
                };

                process.ErrorDataReceived += (obj, a) =>
                {
                    if (!string.IsNullOrWhiteSpace(a.Data))
                    {
                        logMessages.AppendLine($"Error: {a.Data}");
                    }
                };

                try
                {
                    process.Start();

                    process.BeginOutputReadLine();
                    process.BeginErrorReadLine();

                    process.WaitForExit((timeoutMinutes * 60) * 1000);
                }
                catch (Exception exp)
                {
                    _log.AppendLine("Error Running Script: " + exp.ToString());
                }

                // append output to main log
                _log.Append(logMessages.ToString());

                if (!process.HasExited)
                {
                    //process still running, kill task
                    process.CloseMainWindow();

                    _log.AppendLine("Warning: Script ran but took too long to exit and was closed.");
                    return(new ActionResult {
                        IsSuccess = false, Message = _log.ToString()
                    });
                }
                else if (process.ExitCode != 0)
                {
                    _log.AppendLine("Warning: Script exited with the following ExitCode: " + process.ExitCode);
                    return(new ActionResult {
                        IsSuccess = false, Message = _log.ToString()
                    });
                }

                return(new ActionResult {
                    IsSuccess = true, Message = _log.ToString()
                });
            }
            catch (Exception exp)
            {
                _log.AppendLine("Error: " + exp.ToString());
                return(new ActionResult {
                    IsSuccess = false, Message = _log.ToString()
                });
            }
        }
예제 #12
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)
        {
            verboseMode = verbose;
            if (cfgStore == null)
            {
                LoadConfig(cfgFileName);
            }

            // Get hosts expiring in near future
            Trace.Write($"Loading hosts expiring in {cfgStore.RenewDaysBeforeExpiration} days...");
            var expiringHosts = cfgStore.Hosts
                                .Where(x => x.NotAfter <= DateTime.Now.AddDays(cfgStore.RenewDaysBeforeExpiration))
                                .OrderBy(x => x.NotAfter);

            if (!expiringHosts.Any())
            {
                Trace.WriteLine("OK, no hosts to renew");
                return;
            }
            Trace.WriteLine($"OK, {expiringHosts.Count()} hosts to renew");

            using (var ac = new AcmeContext(cfgStore.ServerUri)) {
                try {
                    ac.ChallengeVerificationRetryCount = cfgStore.ChallengeVerificationRetryCount;
                    ac.ChallengeVerificationWait       = TimeSpan.FromSeconds(cfgStore.ChallengeVerificationWaitSeconds);

                    if (string.IsNullOrEmpty(cfgStore.SerializedAccountData))
                    {
                        cfgStore.SerializedAccountData = ac.RegisterAndLogin(cfgStore.EmailAddress);
                        SaveConfig(cfgFileName);
                    }
                    else
                    {
                        ac.Login(cfgStore.SerializedAccountData);
                    }
                }
                catch (Exception ex) {
                    Trace.WriteLine($"Login failed: {ex.Message}");
                    if (verboseMode)
                    {
                        Trace.WriteLine(string.Empty);
                        Trace.WriteLine(ex);
                    }
                    CrashExit("Unable to login or create account.");
                }

                // Renew them
                foreach (var item in expiringHosts)
                {
                    // Hack: fix indentation
                    while (Trace.IndentLevel > 0)
                    {
                        Trace.Unindent();
                    }

                    // Display info
                    var dte = Math.Floor(item.NotAfter.Subtract(DateTime.Now).TotalDays);
                    if (dte < 0)
                    {
                        Trace.WriteLine($"Host {item.CommonName} expired {-dte} days ago ({item.NotAfter:D})");
                    }
                    else
                    {
                        Trace.WriteLine($"Host {item.CommonName} expires in {dte} days ({item.NotAfter:D})");
                    }

                    if (whatIf)
                    {
                        continue;
                    }

                    Trace.Indent();

                    // Request certificate
                    CertificateRequestResult result = null;
                    try {
                        result = ac.GetCertificate(
                            hostName: item.CommonName,
                            pfxPassword: cfgStore.PfxPassword,
                            challengeCallback: CreateChallenge,
                            cleanupCallback: CleanupChallenge,
                            skipTest: skipTest);
                    }
                    catch (Exception ex) {
                        Trace.WriteLine($"Renewal failed: {ex.Message}");
                        if (verboseMode)
                        {
                            Trace.WriteLine(string.Empty);
                            Trace.WriteLine(ex);
                        }
                    }

                    if (result != null)
                    {
                        // Display certificate info
                        Trace.WriteLine("Certificate information:");
                        Trace.Indent();
                        Trace.WriteLine($"Issuer:        {result.Certificate.Issuer}");
                        Trace.WriteLine($"Subject:       {result.Certificate.Subject}");
                        Trace.WriteLine($"Serial number: {result.Certificate.SerialNumber}");
                        Trace.WriteLine($"Not before:    {result.Certificate.NotBefore:o}");
                        Trace.WriteLine($"Not after:     {result.Certificate.NotAfter:o}");
                        Trace.WriteLine($"Thumbprint:    {result.Certificate.Thumbprint}");
                        Trace.Unindent();

                        // Export files
                        Trace.WriteLine("Exporting files:");
                        Trace.Indent();
                        result.Export(item.CommonName, cfgStore.PfxFolder, cfgStore.PemFolder);
                        Trace.Unindent();

                        // Update database entry
                        Trace.Write("Updating database entry...");
                        item.NotBefore    = result.Certificate.NotBefore;
                        item.NotAfter     = result.Certificate.NotAfter;
                        item.SerialNumber = result.Certificate.SerialNumber;
                        item.Thumbprint   = result.Certificate.Thumbprint;
                        Trace.WriteLine("OK");

                        // Save configuration
                        SaveConfig(cfgFileName);
                    }

                    Trace.Unindent();
                }
            }
        }
예제 #13
0
        public static void AddHost(
            [Required(Description = "Host name")] string hostName,
            [Optional(false, "xt", Description = "Skip authentication test")] bool skipTest,
            [Optional(null, "cfg", Description = "Custom configuration file name")] string cfgFileName,
            [Optional(false, Description = "Show verbose error messages")] bool verbose)
        {
            verboseMode = verbose;
            if (cfgStore == null)
            {
                LoadConfig(cfgFileName);
            }
            hostName = hostName.Trim().ToLower();

            // Check if there already is host with this name
            Trace.Write("Checking host...");
            if (cfgStore.Hosts.Any(x => x.CommonName.Equals(hostName)))
            {
                CrashExit($"Host '{hostName}' is already managed.");
            }
            Trace.WriteLine("OK");

            // Request certificate
            Trace.WriteLine($"Requesting cerificate for {hostName}:");
            Trace.Indent();
            CertificateRequestResult result = null;

            try {
                using (var ac = new AcmeContext(cfgStore.ServerUri)) {
                    ac.ChallengeVerificationRetryCount = cfgStore.ChallengeVerificationRetryCount;
                    ac.ChallengeVerificationWait       = TimeSpan.FromSeconds(cfgStore.ChallengeVerificationWaitSeconds);

                    if (string.IsNullOrEmpty(cfgStore.SerializedAccountData))
                    {
                        cfgStore.SerializedAccountData = ac.RegisterAndLogin(cfgStore.EmailAddress);
                        SaveConfig(cfgFileName);
                    }
                    else
                    {
                        ac.Login(cfgStore.SerializedAccountData);
                    }

                    result = ac.GetCertificate(
                        hostName: hostName,
                        pfxPassword: cfgStore.PfxPassword,
                        challengeCallback: CreateChallenge,
                        cleanupCallback: CleanupChallenge,
                        skipTest: skipTest);
                }
            }
            catch (Exception ex) {
                Trace.WriteLine($"Request failed: {ex.Message}");
                if (verboseMode)
                {
                    Trace.WriteLine(string.Empty);
                    Trace.WriteLine(ex);
                }
                CrashExit("Unable to get certificate for new host.");
            }

            if (result != null)
            {
                // Display certificate info
                Trace.Indent();
                Trace.WriteLine("Certificate information:");
                Trace.WriteLine($"Issuer:        {result.Certificate.Issuer}");
                Trace.WriteLine($"Subject:       {result.Certificate.Subject}");
                Trace.WriteLine($"Serial number: {result.Certificate.SerialNumber}");
                Trace.WriteLine($"Not before:    {result.Certificate.NotBefore:o}");
                Trace.WriteLine($"Not after:     {result.Certificate.NotAfter:o}");
                Trace.WriteLine($"Thumbprint:    {result.Certificate.Thumbprint}");
                Trace.Unindent();
                Trace.Unindent();

                // Export files
                Trace.WriteLine("Exporting files:");
                Trace.Indent();
                result.Export(hostName, cfgStore.PfxFolder, cfgStore.PemFolder);
                Trace.Unindent();

                // Update database entry
                Trace.Write("Updating database entry...");
                cfgStore.Hosts.Add(new Host {
                    CommonName   = hostName,
                    NotBefore    = result.Certificate.NotBefore,
                    NotAfter     = result.Certificate.NotAfter,
                    SerialNumber = result.Certificate.SerialNumber,
                    Thumbprint   = result.Certificate.Thumbprint
                });
                Trace.WriteLine("OK");

                // Save configuration
                SaveConfig(cfgFileName);
            }
        }
예제 #14
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();
                    }
                }
            }
        }
예제 #15
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);
            }
        }