Esempio n. 1
0
        private void btnRequestCertificate_Click(object sender, EventArgs e)
        {
            if (lstSites.SelectedItem == null)
            {
                MessageBox.Show("No IIS Site Selected");
                return;
            }

            if (VaultManager == null)
            {
                MessageBox.Show("Vault Manager is null. Please report this problem.");
            }

            //prevent further clicks on request button
            btnRequestCertificate.Enabled = false;
            ShowProgressBar();
            this.Cursor = Cursors.WaitCursor;

            bool certsApproved = false;
            bool certsStored   = false;

            CertRequestConfig config = new CertRequestConfig();
            var selectItem           = (SiteListItem)lstSites.SelectedItem;

            config.Domain = selectItem.Host;
            config.PerformChallengeFileCopy = true;
            config.WebsiteRootPath          = Environment.ExpandEnvironmentVariables(selectItem.PhysicalPath);

            var vaultConfig = VaultManager.GetVaultConfig();

            //check if domain already has an associated identifier
            var identifierAlias = VaultManager.ComputeIdentifierAlias(config.Domain);

            //try alias or DNS name before creating a new identifier
            var identifier = VaultManager.GetIdentifier(identifierAlias);

            if (identifier == null)
            {
                identifier = VaultManager.GetIdentifier(config.Domain);
            }

            if (identifier != null)
            {
                //domain already exists in vault
                //check if has pending authorization challenges
                if (identifier.Authorization != null && identifier.Authorization.Challenges != null)
                {
                    var challenge = identifier.Authorization.Challenges.FirstOrDefault(c => c.Type == "http-01");
                    if (challenge != null)
                    {
                        if (challenge.Status != "invalid")
                        {
                            //update challenge status
                            MessageBox.Show("An existing challenge was already in progress, status will now be updated. " + challenge.Token);
                            VaultManager.UpdateIdentifierStatus(identifierAlias);

                            identifier = VaultManager.GetIdentifier(identifierAlias
                                                                    , true);

                            challenge = identifier.Authorization.Challenges.FirstOrDefault(c => c.Type == "http-01");
                            if (challenge.Status == "valid")
                            {
                                certsApproved = true;
                            }
                        }
                        else
                        {
                            MessageBox.Show("The existing challenge for this identifier failed. We will need to create a new one.");
                            identifierAlias += "_" + Guid.NewGuid().ToString().Substring(0, 6);
                        }
                    }
                }
            }

            if (!certsApproved)
            {
                var authorization = VaultManager.DomainInitAndRegistration(config, identifierAlias);

                if (authorization != null)
                {
                    if (!authorization.ExtensionlessConfigCheckedOK)
                    {
                        MessageBox.Show("Automated checks for extensionless content failed. Authorisations will not be able to complete. Change the web.config in <your site>\\.well-known\\acme-challenge and ensure you can browse to http://<your site>/.well-known/acme-challenge/configcheck before proceeding.");
                        return;
                    }
                    //at this point we can either get the user to manually copy the file to web site folder structure
                    //if file has already been copied we can go ahead and ask the server to verify it

                    //ask server to check our challenge answer is present and correct
                    VaultManager.SubmitChallenge(authorization.Identifier.Alias);

                    //give LE time to check our challenge answer stored on our server
                    Thread.Sleep(2000);

                    VaultManager.UpdateIdentifierStatus(authorization.Identifier.Alias);
                    VaultManager.ReloadVaultConfig();

                    //check status of the challenge
                    var updatedIdentifier = VaultManager.GetIdentifier(authorization.Identifier.Alias);

                    var challenge = updatedIdentifier.Authorization.Challenges.FirstOrDefault(c => c.Type == "http-01");

                    //if all OK, we will be ready to fetch our certificate
                    if (challenge?.Status == "valid")
                    {
                        certsApproved = true;
                    }
                    else
                    {
                        if (challenge != null)
                        {
                            MessageBox.Show("Challenge not yet completed. Check that http://" + config.Domain + "/" + challenge.ToString() + " path/file is present and accessible in your web browser.");
                        }
                        else
                        {
                            if (challenge.Status == "invalid")
                            {
                                MessageBox.Show("Challenge failed to complete. Check that http://" + config.Domain + "/" + challenge.ToString() + " path/file is present and accessible in your web browser. You may require extensionless file type mappings");
                            }
                        }
                    }
                }
                else
                {
                    MessageBox.Show("Could not begin authorization. Check Logs. Ensure the domain being authorized is whitelisted with LetsEncrypt service.");
                }
            }

            //create certs for current authorization
            string certRef = null;
            //if (certsApproved)
            {
                certRef = VaultManager.CreateCertificate(identifierAlias);
                VaultManager.UpdateIdentifierStatus(identifierAlias);
                identifier = VaultManager.GetIdentifier(identifierAlias, true);

                VaultManager.ReloadVaultConfig();
                if (VaultManager.CertExists(identifierAlias))
                {
                    certsStored = true;
                }
            }

            //auto setup/install
            var certInfo = VaultManager.GetCertificate(certRef);

            if (certInfo != null && certInfo.CrtDerFile == null)
            {
                //failed to get cert first time, try again
                certRef = VaultManager.CreateCertificate(identifierAlias);
                VaultManager.UpdateIdentifierStatus(identifierAlias);

                certInfo = VaultManager.GetCertificate(certRef);
            }

            //txtOutput.Text = "To complete this request copy the file " + CurrentAuthorization.TempFilePath + " to the following location under your website root (note: no file extension): " + CurrentAuthorization.Challenge.ChallengeAnswer.Key;
            //ReloadVault();

            this.Cursor = Cursors.Default;

            if (!certsStored)
            {
                if (certsApproved)
                {
                    MessageBox.Show("Certificates approved but not yet stored in vault. Try again later.");
                    CloseParentForm();
                    return;
                }
                else
                {
                    MessageBox.Show("Certificates not approved yet. Authorization challenge may have failed. Try again later.");
                    CloseParentForm();
                    return;
                }
            }
            else
            {
                if (certInfo != null)
                {
                    string certFolderPath = VaultManager.GetCertificateFilePath(certInfo.Id, LocalDiskVault.ASSET);
                    string pfxFile        = certInfo.Id.ToString() + "-all.pfx";
                    string pfxPath        = Path.Combine(certFolderPath, pfxFile);

                    if (!System.IO.Directory.Exists(certFolderPath))
                    {
                        System.IO.Directory.CreateDirectory(certFolderPath);
                    }
                    if (!File.Exists(pfxPath))
                    {
                        //hmm, no pfx, try to create pfx again TODO: shouldn't need this
                        VaultManager.ExportCertificate("=" + certInfo.Id.ToString(), pfxOnly: true);
                    }

                    if (File.Exists(pfxPath))
                    {
                        //VaultManager.UpdateIdentifierStatus(certInfo.IdentifierRef);
                        //identifier = VaultManager.GetIdentifier(certInfo.IdentifierRef, true);

                        IISManager iisManager = new IISManager();
                        if (identifier == null || identifier.Dns == null)
                        {
                            MessageBox.Show("Error: identifier/dns is null. Cannot match domain for binding");
                        }
                        else
                        {
                            if (iisManager.InstallCertForDomain(identifier.Dns, pfxPath, cleanupCertStore: true, skipBindings: !chkAutoBindings.Checked))
                            {
                                //all done
                                MessageBox.Show("Certificate installed and SSL bindings updated for " + identifier.Dns, Properties.Resources.AppName);
                                CloseParentForm();
                                return;
                            }

                            if (chkAutoBindings.Checked)
                            {
                                //auto store and create site bindings
                                MessageBox.Show("Your certificate has been imported and SSL bindings updated for " + config.Domain, Properties.Resources.AppName);
                                CloseParentForm();
                                return;
                            }
                            else
                            {
                                //auto store cert
                                MessageBox.Show("Your certificate has been imported and is ready for you to configure IIS bindings.", Properties.Resources.AppName);
                                CloseParentForm();
                                return;
                            }
                        }
                    }
                    else
                    {
                        MessageBox.Show("Failed to generate PFX file for Certificate.", Properties.Resources.AppName);
                        CloseParentForm();
                        return;
                    }
                }
                else
                {
                    //cert was null
                    MessageBox.Show("Certification was not successful. Certificate not valid or not yet authorized.", Properties.Resources.AppName);
                    CloseParentForm();
                    return;
                }
            }
        }
Esempio n. 2
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
                    };
                }
            }));
        }
Esempio n. 3
0
 public string ComputeDomainIdentifierId(string domain)
 {
     return(_vaultManager.ComputeIdentifierAlias(domain));
 }
Esempio n. 4
0
        private bool PerformCertRequestAndIISBinding(string certDomain, string[] alternativeNames)
        {
            // ACME service requires international domain names in ascii mode
            certDomain = _idnMapping.GetAscii(certDomain);

            //create cert and binding it

            //Typical command sequence for a new certificate

            //Initialize-ACMEVault -BaseURI https://acme-staging.api.letsencrypt.org/

            // Get-Module -ListAvailable ACMESharp
            // New-ACMEIdentifier -Dns test7.examplesite.co.uk -Alias test7_examplesite_co_uk636213616564101276 -Label Identifier:test7.examplesite.co.uk
            // Complete-ACMEChallenge -Ref test7_examplesite_co_uk636213616564101276 -ChallengeType http-01 -Handler manual  -Regenerate
            // Submit-ACMEChallenge -Ref test7_examplesite_co_uk636213616564101276 -Challenge http-01
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // New-ACMECertificate -Identifier test7_examplesite_co_uk636213616564101276 -Alias cert_test7_examplesite_co_uk636213616564101276 -Generate
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Get-ACMECertificate -Ref = ac22dbfe - b75f - 4cac-9247-b40c1d9bf9eb -ExportPkcs12 C:\ProgramData\ACMESharp\sysVault\99-ASSET\ac22dbfe-b75f-4cac-9247-b40c1d9bf9eb-all.pfx -Overwrite

            //get info on existing IIS site we want to create/update SSL binding for
            IISManager iisManager = new IISManager();
            var        iisSite    = iisManager.GetSiteBindingByDomain(certDomain);
            var        certConfig = new CertRequestConfig()
            {
                PrimaryDomain            = certDomain,
                PerformChallengeFileCopy = true,
                WebsiteRootPath          = Environment.ExpandEnvironmentVariables(iisSite.PhysicalPath)
            };

            var certifyManager = new VaultManager(Properties.Settings.Default.VaultPath, LocalDiskVault.VAULT);

            certifyManager.UsePowershell = false;

            //init vault if not already created
            certifyManager.InitVault(staging: true);

            //domain alias is used as an ID in both the vault and the LE server, it's specific to one authorization attempt and cannot be reused for renewal
            var domainIdentifierAlias = certifyManager.ComputeIdentifierAlias(certDomain);

            //NOTE: to support a SAN certificate (multiple alternative domains on one site) the domain validation steps need to be repeat for each name:

            //register identifier with LE, get http challenge spec back
            //create challenge response answer file under site .well-known, auto configure web.config for extenstionless content, mark challenge prep completed
            var authState = certifyManager.BeginRegistrationAndValidation(certConfig, domainIdentifierAlias);

            //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
            if (authState.Identifier.Authorization.IsPending())
            {
                //prepare IIS with answer for the LE challenege
                certifyManager.PerformIISAutomatedChallengeResponse(certConfig, authState);

                //ask LE to validate our challenge response
                certifyManager.SubmitChallenge(domainIdentifierAlias, "http-01");
            }

            //now check if LE has validated our challenge answer
            bool validated = certifyManager.CompleteIdentifierValidationProcess(domainIdentifierAlias);

            if (validated)
            {
                var certRequestResult = certifyManager.PerformCertificateRequestProcess(domainIdentifierAlias, alternativeIdentifierRefs: null);
                if (certRequestResult.IsSuccess)
                {
                    string pfxPath = certRequestResult.Result.ToString();
                    //Install certificate into certificate store and bind to IIS site
                    //TODO, match by site id?
                    if (iisManager.InstallCertForDomain(certDomain, pfxPath, cleanupCertStore: true, skipBindings: false))
                    {
                        //all done
                        System.Diagnostics.Debug.WriteLine("Certificate installed and SSL bindings updated for " + certDomain);
                        return(true);
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Failed to install PFX file for Certificate.");
                        return(false);
                    }
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("LE did not issue a valid certificate in the time allowed.");
                    return(false);
                }
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Validation of the required challenges did not complete successfully.");
                return(false);
            }
        }
Esempio n. 5
0
        private bool PerformCertRequestAndIISBinding(string certDomain)
        {
            // ACME service requires international domain names in ascii mode
            certDomain = _idnMapping.GetAscii(certDomain);

            //create cert and binding it

            //Typical command sequence for a new certificate

            //Initialize-ACMEVault -BaseURI https://acme-staging.api.letsencrypt.org/

            // Get-Module -ListAvailable ACMESharp
            // New-ACMEIdentifier -Dns test7.examplesite.co.uk -Alias test7_examplesite_co_uk636213616564101276 -Label Identifier:test7.examplesite.co.uk
            // Complete-ACMEChallenge -Ref test7_examplesite_co_uk636213616564101276 -ChallengeType http-01 -Handler manual  -Regenerate
            // Submit-ACMEChallenge -Ref test7_examplesite_co_uk636213616564101276 -Challenge http-01
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // New-ACMECertificate -Identifier test7_examplesite_co_uk636213616564101276 -Alias cert_test7_examplesite_co_uk636213616564101276 -Generate
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Update-ACMEIdentifier -Ref test7_examplesite_co_uk636213616564101276
            // Get-ACMECertificate -Ref = ac22dbfe - b75f - 4cac-9247-b40c1d9bf9eb -ExportPkcs12 C:\ProgramData\ACMESharp\sysVault\99-ASSET\ac22dbfe-b75f-4cac-9247-b40c1d9bf9eb-all.pfx -Overwrite

            var vaultManager = new VaultManager(Properties.Settings.Default.VaultPath, LocalDiskVault.VAULT);

            //init vault if not already created
            vaultManager.InitVault(staging: true);

            var certifyManager = vaultManager.PowershellManager;

            //domain alias is used as an ID in both the vault and the LE server, it's specific to one authorization attempt and cannot be reused for renewal

            var domainIdentifierAlias = vaultManager.ComputeIdentifierAlias(certDomain);
            //get info on existing IIS site we want to create/update SSL binding for
            IISManager iisManager = new IISManager();
            var        iisSite    = iisManager.GetSiteBindingByDomain(certDomain);
            var        certConfig = new CertRequestConfig()
            {
                Domain = certDomain,
                PerformChallengeFileCopy = true,
                WebsiteRootPath          = Environment.ExpandEnvironmentVariables(iisSite.PhysicalPath)
            };

            //NOTE: to support a SAN certificate (multiple alternative domains on one site) the domain validation steps need to be repeat for each name:

            //register identifier with LE, get http challenge spec back
            //create challenge response answer file under site .well-known, auto configure web.config for extenstionless content, mark challenge prep completed
            var authState = vaultManager.BeginRegistrationAndValidation(certConfig, domainIdentifierAlias);

            //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
            certifyManager.SubmitChallenge(domainIdentifierAlias, "http-01");

            //
            certifyManager.UpdateIdentifier(domainIdentifierAlias);
            var identiferStatus = vaultManager.GetIdentifier(domainIdentifierAlias, true);
            var attempts        = 0;
            var maxAttempts     = 3;

            while (identiferStatus.Authorization.Status == "pending" && attempts < maxAttempts)
            {
                System.Threading.Thread.Sleep(2000); //wait a couple of seconds before checking again
                certifyManager.UpdateIdentifier(domainIdentifierAlias);
                identiferStatus = vaultManager.GetIdentifier(domainIdentifierAlias, true);
                attempts++;
            }

            if (identiferStatus.Authorization.Status != "valid")
            {
                //still pending or failed
                System.Diagnostics.Debug.WriteLine("LE Authorization problem: " + identiferStatus.Authorization.Status);
                return(false);
            }
            else
            {
                //all good, we can request a certificate
                //if authorizing a SAN we would need to repeat the above until all domains are valid, then we can request cert
                var certAlias = "cert_" + domainIdentifierAlias;

                //register cert placeholder in vault
                certifyManager.NewCertificate(domainIdentifierAlias, certAlias, subjectAlternativeNameIdentifiers: null);

                //ask LE to issue a certificate for our domain(s)
                certifyManager.SubmitCertificate(certAlias);

                //LE may now have issued a certificate, this process may not be immediate
                var certDetails = vaultManager.GetCertificate(certAlias, reloadVaultConfig: true);
                attempts = 0;
                //cert not issued yet, wait and try again
                while ((certDetails == null || String.IsNullOrEmpty(certDetails.IssuerSerialNumber)) && attempts < maxAttempts)
                {
                    System.Threading.Thread.Sleep(2000); //wait a couple of seconds before checking again
                    certifyManager.UpdateCertificate(certAlias);
                    certDetails = vaultManager.GetCertificate(certAlias, reloadVaultConfig: true);
                    attempts++;
                }

                if (certDetails != null && !String.IsNullOrEmpty(certDetails.IssuerSerialNumber))
                {
                    //we have an issued certificate, we can go ahead and install it as required
                    System.Diagnostics.Debug.WriteLine("Received certificate issued by LE." + JsonConvert.SerializeObject(certDetails));

                    //if using cert in IIS, we need to export the certificate PFX file, install it as a certificate and setup the site binding to map to this cert
                    string certFolderPath = vaultManager.GetCertificateFilePath(certDetails.Id, LocalDiskVault.ASSET);
                    string pfxFile        = certAlias + "-all.pfx";
                    string pfxPath        = System.IO.Path.Combine(certFolderPath, pfxFile);

                    //create folder to export PFX to, if required
                    if (!System.IO.Directory.Exists(certFolderPath))
                    {
                        System.IO.Directory.CreateDirectory(certFolderPath);
                    }

                    //if file already exists we want to delet the old one
                    if (System.IO.File.Exists(pfxPath))
                    {
                        //delete existing PFX (if any)
                        System.IO.File.Delete(pfxPath);
                    }

                    //export the PFX file
                    vaultManager.ExportCertificate(certAlias, pfxOnly: true);

                    if (!System.IO.File.Exists(pfxPath))
                    {
                        System.Diagnostics.Debug.WriteLine("Failed to export PFX. " + pfxPath);
                        return(false);
                    }

                    //Install certificate into certificate store and bind to IIS site
                    //TODO, match by site id?
                    if (iisManager.InstallCertForDomain(certDomain, pfxPath, cleanupCertStore: true, skipBindings: false))
                    {
                        //all done
                        System.Diagnostics.Debug.WriteLine("Certificate installed and SSL bindings updated for " + certDomain);
                        return(true);
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Failed to install PFX file for Certificate.");
                        return(false);
                    }
                }
                else
                {
                    System.Diagnostics.Debug.WriteLine("LE did not issue a valid certificate in the time allowed." + JsonConvert.SerializeObject(certDetails));
                    return(false);
                }
            }
        }