示例#1
0
        private void populateTreeView(VaultInfo vaultConfig)
        {
            if (this.treeView1.Nodes != null)
            {
                this.treeView1.Nodes.Clear();
            }

            CertificateManager certManager = new CertificateManager();
            // start off by adding a base treeview node
            TreeNode mainNode = new TreeNode();

            mainNode.Name               = "Vault";
            mainNode.Text               = "Vault";
            mainNode.ImageIndex         = (int)ImageList.Vault;
            mainNode.SelectedImageIndex = mainNode.ImageIndex;

            this.treeView1.Nodes.Add(mainNode);

            if (vaultConfig.Identifiers != null)
            {
                var domainsNode = new TreeNode("Domains & Certificates (" + vaultConfig.Identifiers.Count + ")");

                domainsNode.ImageIndex         = (int)ImageList.Globe;
                domainsNode.SelectedImageIndex = domainsNode.ImageIndex;

                foreach (var i in vaultConfig.Identifiers)
                {
                    var node = new TreeNode(i.Dns);
                    node.Tag = i;

                    node.ImageIndex         = (int)ImageList.Globe;
                    node.SelectedImageIndex = node.ImageIndex;

                    if (vaultConfig.Certificates != null)
                    {
                        foreach (var c in vaultConfig.Certificates)
                        {
                            if (c.IdentifierRef == i.Id)
                            {
                                //add cert
                                var certNode = new TreeNode(c.Alias);
                                certNode.Tag = c;

                                certNode.ImageIndex         = (int)ImageList.Cert;
                                certNode.SelectedImageIndex = certNode.ImageIndex;

                                //get info from get if possible, use that to style the parent node (expiry warning)

                                string certPath       = VaultManager.GetCertificateFilePath(c.Id);
                                string crtDerFilePath = certPath + "\\" + c.CrtDerFile;

                                if (File.Exists(crtDerFilePath))
                                {
                                    var cert = certManager.GetCertificate(crtDerFilePath);

                                    DateTime expiryDate = DateTime.Parse(cert.GetExpirationDateString());
                                    TimeSpan timeLeft   = expiryDate - DateTime.Now;
                                    node.Text += " (" + timeLeft.Days + " days remaining)";
                                    if (timeLeft.Days < 30)
                                    {
                                        node.ForeColor = Color.Orange;
                                    }
                                    if (timeLeft.Days < 7)
                                    {
                                        node.ForeColor = Color.Red;
                                    }
                                }
                                else
                                {
                                    node.ForeColor = Color.Gray;
                                }
                                node.Nodes.Add(certNode);
                            }
                        }
                    }
                    domainsNode.Nodes.Add(node);
                }

                mainNode.Nodes.Add(domainsNode);
                domainsNode.Expand();
            }

            if (vaultConfig.Registrations != null)
            {
                var contactsNode = new TreeNode("Registered Contacts (" + vaultConfig.Registrations.Count + ")");

                contactsNode.ImageIndex         = (int)ImageList.Person;
                contactsNode.SelectedImageIndex = contactsNode.ImageIndex;

                foreach (var i in vaultConfig.Registrations)
                {
                    var title = i.Registration.Contacts.FirstOrDefault();
                    var node  = new TreeNode(title);
                    node.Tag = i;

                    node.ImageIndex         = (int)ImageList.Person;
                    node.SelectedImageIndex = node.ImageIndex;

                    contactsNode.Nodes.Add(node);
                }

                mainNode.Nodes.Add(contactsNode);

                contactsNode.Expand();
            }

            if (mainNode.Nodes.Count == 0)
            {
                mainNode.Nodes.Add("(Empty)");
            }
            else
            {
                mainNode.Expand();
            }

            // this.treeView1.ExpandAll();
        }
        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;
                }
            }
        }
示例#3
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);
                }
            }
        }