internal async Task <bool> CreateCertificate(string TabID)
        {
            try
            {
                string        URL = this.customCA ? this.acmeDirectory : "https://acme-v02.api.letsencrypt.org/directory";
                RSAParameters Parameters;
                CspParameters CspParams = new CspParameters()
                {
                    Flags            = CspProviderFlags.UseMachineKeyStore,
                    KeyContainerName = "IoTGateway:" + URL
                };

                try
                {
                    bool Ok;

                    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(4096, CspParams))
                    {
                        Parameters = RSA.ExportParameters(true);

                        if (RSA.KeySize < 4096)
                        {
                            RSA.PersistKeyInCsp = false;
                            RSA.Clear();
                            Ok = false;
                        }
                        else
                        {
                            Ok = true;
                        }
                    }

                    if (!Ok)
                    {
                        using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(4096, CspParams))
                        {
                            Parameters = RSA.ExportParameters(true);
                        }
                    }
                }
                catch (CryptographicException ex)
                {
                    throw new CryptographicException("Unable to get access to cryptographic key for \"IoTGateway:" + URL +
                                                     "\". Was the database created using another user?", ex);
                }

                using (AcmeClient Client = new AcmeClient(new Uri(URL), Parameters))
                {
                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Connecting to directory.", false, "User");

                    AcmeDirectory AcmeDirectory = await Client.GetDirectory();

                    if (AcmeDirectory.ExternalAccountRequired)
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "An external account is required.", false, "User");
                    }

                    if (AcmeDirectory.TermsOfService != null)
                    {
                        URL = AcmeDirectory.TermsOfService.ToString();
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Terms of service available on: " + URL, false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "TermsOfService", URL, false, "User");

                        this.urlToS = URL;

                        if (!this.acceptToS)
                        {
                            ClientEvents.PushEvent(new string[] { TabID }, "CertificateError", "You need to accept the terms of service.", false, "User");
                            return(false);
                        }
                    }

                    if (AcmeDirectory.Website != null)
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Web site available on: " + AcmeDirectory.Website.ToString(), false, "User");
                    }

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Getting account.", false, "User");

                    List <string> Names = new List <string>();

                    if (!string.IsNullOrEmpty(this.domain))
                    {
                        Names.Add(this.domain);
                    }

                    if (this.alternativeDomains != null)
                    {
                        foreach (string Name in this.alternativeDomains)
                        {
                            if (!Names.Contains(Name))
                            {
                                Names.Add(Name);
                            }
                        }
                    }
                    string[] DomainNames = Names.ToArray();

                    AcmeAccount Account;

                    try
                    {
                        Account = await Client.GetAccount();

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Account found.", false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Created: " + Account.CreatedAt.ToString(), false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Initial IP: " + Account.InitialIp, false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Status: " + Account.Status.ToString(), false, "User");

                        if (string.IsNullOrEmpty(this.contactEMail))
                        {
                            if (Account.Contact != null && Account.Contact.Length != 0)
                            {
                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Updating contact URIs in account.", false, "User");
                                Account = await Account.Update(new string[0]);

                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Account updated.", false, "User");
                            }
                        }
                        else
                        {
                            if (Account.Contact is null || Account.Contact.Length != 1 || Account.Contact[0] != "mailto:" + this.contactEMail)
                            {
                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Updating contact URIs in account.", false, "User");
                                Account = await Account.Update(new string[] { "mailto:" + this.contactEMail });

                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Account updated.", false, "User");
                            }
                        }
                    }
                    catch (AcmeAccountDoesNotExistException)
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Account not found.", false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Creating account.", false, "User");

                        Account = await Client.CreateAccount(string.IsNullOrEmpty(this.contactEMail)?new string[0] : new string[] { "mailto:" + this.contactEMail },
                                                             this.acceptToS);

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Account created.", false, "User");
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Status: " + Account.Status.ToString(), false, "User");
                    }

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Generating new key.", false, "User");
                    await Account.NewKey();

                    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(4096, CspParams))
                    {
                        RSA.ImportParameters(Client.ExportAccountKey(true));
                    }

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "New key generated.", false, "User");

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Creating order.", false, "User");
                    AcmeOrder Order = await Account.OrderCertificate(DomainNames, null, null);

                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Order created.", false, "User");

                    foreach (AcmeAuthorization Authorization in await Order.GetAuthorizations())
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Processing authorization for " + Authorization.Value, false, "User");

                        AcmeChallenge Challenge;
                        bool          Acknowledged = false;
                        int           Index        = 1;
                        int           NrChallenges = Authorization.Challenges.Length;

                        for (Index = 1; Index <= NrChallenges; Index++)
                        {
                            Challenge = Authorization.Challenges[Index - 1];

                            if (Challenge is AcmeHttpChallenge HttpChallenge)
                            {
                                this.challenge = "/" + HttpChallenge.Token;
                                this.token     = HttpChallenge.KeyAuthorization;

                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Acknowleding challenge.", false, "User");
                                Challenge = await HttpChallenge.AcknowledgeChallenge();

                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Challenge acknowledged: " + Challenge.Status.ToString(), false, "User");

                                Acknowledged = true;
                            }
                        }

                        if (!Acknowledged)
                        {
                            ClientEvents.PushEvent(new string[] { TabID }, "CertificateError", "No automated method found to respond to any of the authorization challenges.", false, "User");
                            return(false);
                        }

                        AcmeAuthorization Authorization2 = Authorization;

                        do
                        {
                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Waiting to poll authorization status.", false, "User");
                            await Task.Delay(5000);

                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Polling authorization.", false, "User");
                            Authorization2 = await Authorization2.Poll();

                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Authorization polled: " + Authorization2.Status.ToString(), false, "User");
                        }while (Authorization2.Status == AcmeAuthorizationStatus.pending);

                        if (Authorization2.Status != AcmeAuthorizationStatus.valid)
                        {
                            switch (Authorization2.Status)
                            {
                            case AcmeAuthorizationStatus.deactivated:
                                throw new Exception("Authorization deactivated.");

                            case AcmeAuthorizationStatus.expired:
                                throw new Exception("Authorization expired.");

                            case AcmeAuthorizationStatus.invalid:
                                throw new Exception("Authorization invalid.");

                            case AcmeAuthorizationStatus.revoked:
                                throw new Exception("Authorization revoked.");

                            default:
                                throw new Exception("Authorization not validated.");
                            }
                        }
                    }

                    using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(4096))                       // TODO: Make configurable
                    {
                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Finalizing order.", false, "User");

                        SignatureAlgorithm SignAlg = new RsaSha256(RSA);

                        Order = await Order.FinalizeOrder(new CertificateRequest(SignAlg)
                        {
                            CommonName = this.domain,
                            SubjectAlternativeNames = DomainNames,
                            EMailAddress            = this.contactEMail
                        });

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Order finalized: " + Order.Status.ToString(), false, "User");

                        if (Order.Status != AcmeOrderStatus.valid)
                        {
                            switch (Order.Status)
                            {
                            case AcmeOrderStatus.invalid:
                                throw new Exception("Order invalid.");

                            default:
                                throw new Exception("Unable to validate oder.");
                            }
                        }

                        if (Order.Certificate is null)
                        {
                            throw new Exception("No certificate URI provided.");
                        }

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Downloading certificate.", false, "User");

                        X509Certificate2[] Certificates = await Order.DownloadCertificate();

                        X509Certificate2 Certificate = Certificates[0];

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Exporting certificate.", false, "User");

                        this.certificate = Certificate.Export(X509ContentType.Cert);
                        this.privateKey  = RSA.ExportCspBlob(true);
                        this.pfx         = null;
                        this.password    = string.Empty;

                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Adding private key.", false, "User");

                        try
                        {
                            Certificate.PrivateKey = RSA;
                        }
                        catch (PlatformNotSupportedException)
                        {
                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Platform does not support adding of private key.", false, "User");
                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Searching for OpenSSL on machine.", false, "User");

                            string[] Files;
                            string   Password      = Hashes.BinaryToString(Gateway.NextBytes(32));
                            string   CertFileName  = null;
                            string   CertFileName2 = null;
                            string   KeyFileName   = null;

                            if (string.IsNullOrEmpty(this.openSslPath) || !File.Exists(this.openSslPath))
                            {
                                Files = GetFiles(new string(Path.DirectorySeparatorChar, 1), "openssl.exe");
                                if (Files is null)
                                {
                                    List <string> Files2 = new List <string>();

                                    Files = GetFiles(Environment.SpecialFolder.ProgramFiles, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.CommonProgramFilesX86, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.Programs, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.System, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.SystemX86, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.Windows, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.CommonProgramFiles, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.CommonProgramFilesX86, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Environment.SpecialFolder.CommonPrograms, "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Path.DirectorySeparatorChar + "OpenSSL-Win32", "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = GetFiles(Path.DirectorySeparatorChar + "OpenSSL-Win64", "openssl.exe");
                                    if (Files != null)
                                    {
                                        Files2.AddRange(Files);
                                    }

                                    Files = Files2.ToArray();
                                }
                            }
                            else
                            {
                                Files = new string[] { this.openSslPath }
                            };

                            try
                            {
                                if (Files.Length == 0)
                                {
                                    ClientEvents.PushEvent(new string[] { TabID }, "CertificateError", "Unable to join certificate with private key. Try installing <a target=\"_blank\" href=\"https://wiki.openssl.org/index.php/Binaries\">OpenSSL</a> and try again.", false, "User");
                                    return(false);
                                }
                                else
                                {
                                    foreach (string OpenSslFile in Files)
                                    {
                                        if (CertFileName is null)
                                        {
                                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Generating temporary certificate file.", false, "User");

                                            StringBuilder PemOutput = new StringBuilder();
                                            byte[]        Bin       = Certificate.Export(X509ContentType.Cert);

                                            PemOutput.AppendLine("-----BEGIN CERTIFICATE-----");
                                            PemOutput.AppendLine(Convert.ToBase64String(Bin, Base64FormattingOptions.InsertLineBreaks));
                                            PemOutput.AppendLine("-----END CERTIFICATE-----");

                                            CertFileName = Path.Combine(Gateway.AppDataFolder, "Certificate.pem");
                                            File.WriteAllText(CertFileName, PemOutput.ToString(), Encoding.ASCII);

                                            ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Generating temporary key file.", false, "User");

                                            DerEncoder KeyOutput = new DerEncoder();
                                            SignAlg.ExportPrivateKey(KeyOutput);

                                            PemOutput.Clear();
                                            PemOutput.AppendLine("-----BEGIN RSA PRIVATE KEY-----");
                                            PemOutput.AppendLine(Convert.ToBase64String(KeyOutput.ToArray(), Base64FormattingOptions.InsertLineBreaks));
                                            PemOutput.AppendLine("-----END RSA PRIVATE KEY-----");

                                            KeyFileName = Path.Combine(Gateway.AppDataFolder, "Certificate.key");

                                            File.WriteAllText(KeyFileName, PemOutput.ToString(), Encoding.ASCII);
                                        }

                                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Converting to PFX using " + OpenSslFile, false, "User");

                                        Process P = new Process()
                                        {
                                            StartInfo = new ProcessStartInfo()
                                            {
                                                FileName               = OpenSslFile,
                                                Arguments              = "pkcs12 -nodes -export -out Certificate.pfx -inkey Certificate.key -in Certificate.pem -password pass:"******"ShowStatus", "Output: " + P.StandardOutput.ReadToEnd(), false, "User");
                                            }

                                            if (!P.StandardError.EndOfStream)
                                            {
                                                ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Error: " + P.StandardError.ReadToEnd(), false, "User");
                                            }

                                            continue;
                                        }

                                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Loading PFX.", false, "User");

                                        CertFileName2    = Path.Combine(Gateway.AppDataFolder, "Certificate.pfx");
                                        this.pfx         = File.ReadAllBytes(CertFileName2);
                                        this.password    = Password;
                                        this.openSslPath = OpenSslFile;

                                        ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "PFX successfully generated using OpenSSL.", false, "User");
                                        break;
                                    }

                                    if (this.pfx is null)
                                    {
                                        this.openSslPath = string.Empty;
                                        ClientEvents.PushEvent(new string[] { TabID }, "CertificateError", "Unable to convert to PFX using OpenSSL.", false, "User");
                                        return(false);
                                    }
                                }
                            }
                            finally
                            {
                                if (CertFileName != null && File.Exists(CertFileName))
                                {
                                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Deleting temporary certificate file.", false, "User");
                                    File.Delete(CertFileName);
                                }

                                if (KeyFileName != null && File.Exists(KeyFileName))
                                {
                                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Deleting temporary key file.", false, "User");
                                    File.Delete(KeyFileName);
                                }

                                if (CertFileName2 != null && File.Exists(CertFileName2))
                                {
                                    ClientEvents.PushEvent(new string[] { TabID }, "ShowStatus", "Deleting temporary pfx file.", false, "User");
                                    File.Delete(CertFileName2);
                                }
                            }
                        }


                        if (this.Step < 2)
                        {
                            this.Step = 2;
                        }

                        this.Updated = DateTime.Now;
                        await Database.Update(this);

                        ClientEvents.PushEvent(new string[] { TabID }, "CertificateOk", string.Empty, false, "User");

                        Gateway.UpdateCertificate(this);

                        return(true);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Critical(ex);
                ClientEvents.PushEvent(new string[] { TabID }, "CertificateError", "Unable to create certificate: " + XML.HtmlValueEncode(ex.Message), false, "User");
                return(false);
            }
            finally
            {
                this.inProgress = false;
            }
        }