/// <summary>
        /// Generates a random password with a high entropy.
        /// </summary>
        /// <returns>Random ASCII password.</returns>
        public static string GenerateRandomPassword()
        {
            var    passArray = new char[30];
            string password;

            var csprng = new SecureRandom(new DigestRandomGenerator(new Sha256Digest()));

            csprng.SetSeed(DateTime.Now.Ticks); // TODO: is this a good seed value?

            while (true)
            {
                for (var i = 0; i < 30; ++i)
                {
                    // ASCII printable characters are >= SPACE (0x20) and < DEL (0x7e).
                    passArray[i] = (char)csprng.Next(0x20, 0x7f);
                }

                password = new string(passArray);
                //passArray = Enumerable.Repeat('0', passArray.Length).ToArray(); // zeroization

                if (PasswordAdvisor.IsPasswordStrong(password, out var _, false))
                {
                    break;
                }
            }

            return(password);
        }
        /// <summary>
        /// Registers a new user if the register policy are met.
        /// </summary>
        /// <param name="username">Users account username.</param>
        /// <param name="password">Users password.</param>
        /// <param name="certificateFilePath">Path on FS to users certificate.</param>
        public void Register(ref string username, string password, string certificateFilePath, bool skipPasswordStrengthCheck = false)
        {
            if (username.Length > 25)
            {
                throw new Exception("Usernames can't have more than 25 characters.");
            }

            if (!skipPasswordStrengthCheck && password.Contains(username))
            {
                throw new Exception("Password cannot contain your username.");
            }

            // Probability of repetition of this do-while loop is low.
            do
            {
                // Add a random 4-digit number to users username.
                var csprng = new SecureRandom(new DigestRandomGenerator(new Sha256Digest()));
                csprng.SetSeed(DateTime.Now.Ticks); // TODO: is this a good seed value?
                var suffix = "#" + csprng.Next(1_000, 9_999).ToString();

                // If username collision occurs, generate new 4-digit suffix.
                if (data.GetUser(username + suffix) != null)
                {
                    continue;
                }
                else
                {
                    username += suffix;
                    break;
                }
            } while (true);

            // Check if a password used some of the most common passwords discovered in various data breaches.
            if (!skipPasswordStrengthCheck && PasswordAdvisor.CommonPasswordCheck(password, commonPasswordsPath))
            {
                throw new Exception("This password is not allowed. Please try again.");
            }

            // Check password strength.
            if (!skipPasswordStrengthCheck && !PasswordAdvisor.IsPasswordStrong(password, out var passwordStrength, false))
            {
                throw new Exception($"Password is deemed {passwordStrength}. Please try again.");
            }

            var cert = new X509Certificate2(certificateFilePath);

            // Check if key length is >= 2048 bits.
            if (CertificateValidator.VerifyCertificateKeyLength(cert) == false)
            {
                throw new Exception("Key length has to be at least 2048 bits.");
            }

            // Checks if the certificate has expired and if it is issued by a proper root certificate.
            if (CertificateValidator.VerifyCertificate(cert, caTrustListPath, out var errorMsg, true) == false)
            {
                throw new Exception(errorMsg);
            }

            // Check if the certificate is revoked.
            if (CertificateValidator.VerifyCertificateRevocationStatus(cert, crlListPath, caTrustListPath) == true)
            {
                throw new Exception("Certificate has been revoked.");
            }

            // Check if certificate has a proper key usage set.
            if (CertificateValidator.VerifyKeyUsage(cert) == false)
            {
                throw new Exception("Certificate must have 'digitalSignature' and 'keyEncipherment' set as it's key usage.");
            }
        }
        /// <summary>
        /// Generates a random passphrase that contains between 6 and 10 words using a <em>Diceware</em> method (<see href="https://www.eff.org/dice">EFF Dice-Generated Passphrases</see>).
        /// </summary>
        /// <returns>Random ASCII password createad using a <em>Diceware</em> method.</returns>
        public static string GeneratePassphrase(string dicewareWordsPath)
        {
            var    diceRollResult = 0;
            string passphrase;
            var    delimiter = GeneratePassphraseDelimiter();

            var csprng = new SecureRandom(new DigestRandomGenerator(new Sha256Digest()));

            csprng.SetSeed(DateTime.Now.Ticks); // TODO: is this a good seed value?

            var maxNumberOfWords = csprng.Next(6, 10);

            // Loop only repeats if the generated passphrase has low entropy; this loop will never repeat because for the minimum of 6 words passphrase will have a good entropy.
            while (true)
            {
                var    numberOfWords = 0;
                string index;
                passphrase = "";

                // Loop repeats until we create a passphrase with an appropriate number of words.
                do
                {
                    var numberExist = false;

                    // Loop is used if the resulting 5-digit number isn't in the list.
                    do
                    {
                        // five dice rolls
                        for (var i = 0; i < 5; ++i)
                        {
                            diceRollResult += csprng.Next(1, 7) * (int)Math.Pow(10, i);
                        }

                        index          = Convert.ToString(diceRollResult);
                        diceRollResult = 0;

                        // TODO: can this be optimized?
                        using (var file = new StreamReader(dicewareWordsPath))
                        {
                            string line = null;
                            while ((line = file.ReadLine()) != null)
                            {
                                if (line.Contains(index))
                                {
                                    passphrase += line.Split('\t')[1].Trim();
                                    numberOfWords++;
                                    numberExist = true;
                                    break;
                                }
                            }
                        }
                    } while (!numberExist);

                    // Add delimiter between words.
                    if (numberOfWords != maxNumberOfWords - 1)
                    {
                        passphrase += delimiter;
                    }
                } while (numberOfWords < maxNumberOfWords);


                if (PasswordAdvisor.IsPasswordStrong(passphrase, out _, true, maxNumberOfWords))
                {
                    break;
                }
            }

            return(passphrase);
        }