Merge pull request #2257 from jjlin/email-token

Increase length limit for email token generation
This commit is contained in:
Daniel García 2022-01-29 00:05:38 +01:00 committed by GitHub
commit 0876d4a5fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 15 additions and 43 deletions

View File

@ -185,7 +185,7 @@
# EMAIL_EXPIRATION_TIME=600 # EMAIL_EXPIRATION_TIME=600
## Email token size ## Email token size
## Number of digits in an email token (min: 6, max: 19). ## Number of digits in an email 2FA token (min: 6, max: 255).
## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting! ## Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting!
# EMAIL_TOKEN_SIZE=6 # EMAIL_TOKEN_SIZE=6

View File

@ -381,7 +381,7 @@ fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, conn: Db
err!("Email domain not allowed"); err!("Email domain not allowed");
} }
let token = crypto::generate_token(6)?; let token = crypto::generate_email_token(6);
if CONFIG.mail_enabled() { if CONFIG.mail_enabled() {
if let Err(e) = mail::send_change_email(&data.NewEmail, &token) { if let Err(e) = mail::send_change_email(&data.NewEmail, &token) {

View File

@ -58,7 +58,7 @@ pub fn send_token(user_uuid: &str, conn: &DbConn) -> EmptyResult {
let type_ = TwoFactorType::Email as i32; let type_ = TwoFactorType::Email as i32;
let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).map_res("Two factor not found")?; let mut twofactor = TwoFactor::find_by_user_and_type(user_uuid, type_, conn).map_res("Two factor not found")?;
let generated_token = crypto::generate_token(CONFIG.email_token_size())?; let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?; let mut twofactor_data = EmailTokenData::from_json(&twofactor.data)?;
twofactor_data.set_token(generated_token); twofactor_data.set_token(generated_token);
@ -123,7 +123,7 @@ fn send_email(data: JsonUpcase<SendEmailData>, headers: Headers, conn: DbConn) -
tf.delete(&conn)?; tf.delete(&conn)?;
} }
let generated_token = crypto::generate_token(CONFIG.email_token_size())?; let generated_token = crypto::generate_email_token(CONFIG.email_token_size());
let twofactor_data = EmailTokenData::new(data.Email, generated_token); let twofactor_data = EmailTokenData::new(data.Email, generated_token);
// Uses EmailVerificationChallenge as type to show that it's not verified yet. // Uses EmailVerificationChallenge as type to show that it's not verified yet.
@ -309,18 +309,4 @@ mod tests {
// If it's smaller than 3 characters it should only show asterisks. // If it's smaller than 3 characters it should only show asterisks.
assert_eq!(result, "***@example.ext"); assert_eq!(result, "***@example.ext");
} }
#[test]
fn test_token() {
let result = crypto::generate_token(19).unwrap();
assert_eq!(result.chars().count(), 19);
}
#[test]
fn test_token_too_large() {
let result = crypto::generate_token(20);
assert!(result.is_err(), "too large token should give an error");
}
} }

View File

@ -593,8 +593,8 @@ make_config! {
email_2fa: _enable_email_2fa { email_2fa: _enable_email_2fa {
/// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured /// Enabled |> Disabling will prevent users from setting up new email 2FA and using existing email 2FA configured
_enable_email_2fa: bool, true, auto, |c| c._enable_smtp && c.smtp_host.is_some(); _enable_email_2fa: bool, true, auto, |c| c._enable_smtp && c.smtp_host.is_some();
/// Email token size |> Number of digits in an email token (min: 6, max: 19). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting. /// Email token size |> Number of digits in an email 2FA token (min: 6, max: 255). Note that the Bitwarden clients are hardcoded to mention 6 digit codes regardless of this setting.
email_token_size: u32, true, def, 6; email_token_size: u8, true, def, 6;
/// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token. /// Token expiration time |> Maximum time in seconds a token is valid. The time the user has to open email client and copy token.
email_expiration_time: u64, true, def, 600; email_expiration_time: u64, true, def, 600;
/// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent /// Maximum attempts |> Maximum attempts before an email token is reset and a new email will need to be sent
@ -668,10 +668,6 @@ fn validate_config(cfg: &ConfigItems) -> Result<(), Error> {
if cfg._enable_email_2fa && cfg.email_token_size < 6 { if cfg._enable_email_2fa && cfg.email_token_size < 6 {
err!("`EMAIL_TOKEN_SIZE` has a minimum size of 6") err!("`EMAIL_TOKEN_SIZE` has a minimum size of 6")
} }
if cfg._enable_email_2fa && cfg.email_token_size > 19 {
err!("`EMAIL_TOKEN_SIZE` has a maximum size of 19")
}
} }
// Check if the icon blacklist regex is valid // Check if the icon blacklist regex is valid

View File

@ -6,8 +6,6 @@ use std::num::NonZeroU32;
use data_encoding::HEXLOWER; use data_encoding::HEXLOWER;
use ring::{digest, hmac, pbkdf2}; use ring::{digest, hmac, pbkdf2};
use crate::error::Error;
static DIGEST_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256; static DIGEST_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256;
const OUTPUT_LEN: usize = digest::SHA256_OUTPUT_LEN; const OUTPUT_LEN: usize = digest::SHA256_OUTPUT_LEN;
@ -65,6 +63,12 @@ pub fn get_random_string(alphabet: &[u8], num_chars: usize) -> String {
.collect() .collect()
} }
/// Generates a random numeric string.
pub fn get_random_string_numeric(num_chars: usize) -> String {
const ALPHABET: &[u8] = b"0123456789";
get_random_string(ALPHABET, num_chars)
}
/// Generates a random alphanumeric string. /// Generates a random alphanumeric string.
pub fn get_random_string_alphanum(num_chars: usize) -> String { pub fn get_random_string_alphanum(num_chars: usize) -> String {
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
@ -87,23 +91,9 @@ pub fn generate_attachment_id() -> String {
generate_id(10) // 80 bits generate_id(10) // 80 bits
} }
pub fn generate_token(token_size: u32) -> Result<String, Error> { /// Generates a numeric token for email-based verifications.
// A u64 can represent all whole numbers up to 19 digits long. pub fn generate_email_token(token_size: u8) -> String {
if token_size > 19 { get_random_string_numeric(token_size as usize)
err!("Token size is limited to 19 digits")
}
let low: u64 = 0;
let high: u64 = 10u64.pow(token_size);
// Generate a random number in the range [low, high), then format it as a
// token of fixed width, left-padding with 0 as needed.
use rand::{thread_rng, Rng};
let mut rng = thread_rng();
let number: u64 = rng.gen_range(low..high);
let token = format!("{:0size$}", number, size = token_size as usize);
Ok(token)
} }
/// Generates a personal API key. /// Generates a personal API key.