/// <summary> /// Second part of 2FA. /// </summary> /// <param name="user">User information.</param> /// <param name="certificate">User <see cref="X509Certificate2"/> public certificate in raw form.</param> /// <param name="usersDb">Enigma's user database.</param> /// <param name="crlListPath">Path on FS to CRL directory.</param> /// <param name="caTrustListPath">Path on FS to CA trust list.</param> public void LoginPartTwo(User user, byte[] certificate, UserDatabase usersDb, string crlListPath, string caTrustListPath) { var userCert = new X509Certificate2(certificate); var publicKeyFromCertificate = ((RSACryptoServiceProvider)userCert.PublicKey.Key).ExportParameters(false); // compare user public RSA key from x509 public certificate with a public RSA key that was stored when user first registered if (!RsaAlgorithm.CompareKeys(publicKeyFromCertificate, RsaAlgorithm.ExportParametersFromXmlString(user.PublicKey, false))) { throw new Exception("Wrong certificate used."); } // if wrong file is loaded instead of the x509 public certificate in PEM format if (userCert == null) { throw new Exception("Certificate error."); } // update user last login time and reset atttemp count usersDb.UpdateLoginTime(user, DateTime.Now.ToString("dddd, MMM dd yyyy, hh:mm:ss")); // reset login attempt if necessary if (user.LoginAttempt != 0) { usersDb.ResetLoginAttempts(user); } //if (CertificateValidator.VerifyCertificate(userCert, out var errorMsg, false) == false) //{ // throw new Exception(errorMsg); //} // Check if the certificate has been revoked and set Revoked value if necessary. if (CertificateValidator.VerifyCertificateRevocationStatus(userCert, crlListPath, caTrustListPath)) { usersDb.SetCertificateRevokeStatus(user); //throw new Exception("Certificate has been revoked."); } }
/// <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."); } }