Skip to content

Commit

Permalink
Adds wasm32 support (using wasm-bindgen) (#38)
Browse files Browse the repository at this point in the history
* Added wasm32 support.

Co-authored-by: Nick McGuire <nick@nickmcguire.com>

* Fixed: Casting a usize as u64 in a 32-bit (WASM) environment created arithmetic error.

Co-authored-by: Nick McGuire <nick@nickmcguire.com>

* Consistant cfg targets

* Updated quick error to use display. Description removed in 2.0

Co-authored-by: Nick McGuire <nick@nickmcguire.com>
  • Loading branch information
beyera and pudgeball committed Dec 9, 2020
1 parent 009b906 commit e48a226
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 24 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ itertools = "0.9.0"
lazy_static = "1.3"
quick-error = "2.0"
regex = "1"
chrono = "0.4.7"
chrono = { version = "0.4.7", features = ["wasmbind"] }

[dependencies.serde]
optional = true
Expand All @@ -35,6 +35,9 @@ quickcheck = "0.9.0"
serde_json = "1"
criterion = "0.3"

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3"

[features]
default = ["builder"]
ser = ["serde", "serde_derive"]
Expand Down
41 changes: 30 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extern crate serde;
#[cfg(feature = "ser")]
#[macro_use]
extern crate serde_derive;
use std::time::{Duration, Instant};
use chrono::Utc;
use std::time::Duration;

#[cfg(test)]
#[macro_use]
Expand Down Expand Up @@ -111,6 +112,10 @@ quick_error! {
BlankPassword {
display("Zxcvbn cannot evaluate a blank password")
}
/// Indicates an error converting Duration to/from the standard library implementation
DurationOutOfRange {
display("Zxcvbn calculation time created a duration out of range")
}
}
}

Expand All @@ -127,7 +132,7 @@ pub fn zxcvbn(password: &str, user_inputs: &[&str]) -> Result<Entropy, ZxcvbnErr
return Err(ZxcvbnError::BlankPassword);
}

let start_time = Instant::now();
let start_time = Utc::now();

// Only evaluate the first 100 characters of the input.
// This prevents potential DoS attacks from sending extremely long input strings.
Expand All @@ -141,7 +146,9 @@ pub fn zxcvbn(password: &str, user_inputs: &[&str]) -> Result<Entropy, ZxcvbnErr

let matches = matching::omnimatch(&password, &sanitized_inputs);
let result = scoring::most_guessable_match_sequence(&password, &matches, false);
let calc_time = Instant::now() - start_time;
let calc_time = (Utc::now() - start_time)
.to_std()
.map_err(|_| ZxcvbnError::DurationOutOfRange)?;
let (crack_times, score) = time_estimates::estimate_attack_times(result.guesses);
let feedback = feedback::get_feedback(score, &matches);

Expand All @@ -161,6 +168,9 @@ mod tests {
use super::*;
use quickcheck::TestResult;

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test;

quickcheck! {
fn test_zxcvbn_doesnt_panic(password: String, user_inputs: Vec<String>) -> TestResult {
let inputs = user_inputs.iter().map(|s| s.as_ref()).collect::<Vec<&str>>();
Expand All @@ -176,38 +186,44 @@ mod tests {
}
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_zxcvbn() {
let password = "r0sebudmaelstrom11/20/91aaaa";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.guesses_log10 as u16, 14);
assert_eq!(entropy.score, 4);
assert!(!entropy.sequence.is_empty());
assert!(entropy.feedback.is_none());
assert!(entropy.calc_time.as_nanos() > 0);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_zxcvbn_unicode() {
let password = "𐰊𐰂𐰄𐰀𐰁";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 1);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_zxcvbn_unicode_2() {
let password = "r0sebudmaelstrom丂/20/91aaaa";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 4);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_issue_13() {
let password = "Imaginative-Say-Shoulder-Dish-0";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.score, 4);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_issue_15_example_1() {
let password = "TestMeNow!";
let entropy = zxcvbn(password, &[]).unwrap();
Expand All @@ -216,7 +232,8 @@ mod tests {
assert_eq!(entropy.score, 3);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_issue_15_example_2() {
let password = "hey<123";
let entropy = zxcvbn(password, &[]).unwrap();
Expand All @@ -225,15 +242,17 @@ mod tests {
assert_eq!(entropy.score, 2);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_overflow_safety() {
let password = "!QASW@#EDFR$%TGHY^&UJKI*(OL";
let entropy = zxcvbn(password, &[]).unwrap();
assert_eq!(entropy.guesses, u64::max_value());
assert_eq!(entropy.score, 4);
}

#[test]
#[cfg_attr(not(target_arch = "wasm32"), test)]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn test_unicode_mb() {
let password = "08märz2010";
let entropy = zxcvbn(password, &[]).unwrap();
Expand Down
24 changes: 12 additions & 12 deletions src/scoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,19 +373,19 @@ impl Estimator for SpatialPattern {
}

lazy_static! {
static ref KEYBOARD_AVERAGE_DEGREE: usize = calc_average_degree(&crate::adjacency_graphs::QWERTY);
static ref KEYBOARD_AVERAGE_DEGREE: u64 = calc_average_degree(&crate::adjacency_graphs::QWERTY);
// slightly different for keypad/mac keypad, but close enough
static ref KEYPAD_AVERAGE_DEGREE: usize = calc_average_degree(&crate::adjacency_graphs::KEYPAD);
static ref KEYBOARD_STARTING_POSITIONS: usize = crate::adjacency_graphs::QWERTY.len();
static ref KEYPAD_STARTING_POSITIONS: usize = crate::adjacency_graphs::KEYPAD.len();
static ref KEYPAD_AVERAGE_DEGREE: u64 = calc_average_degree(&crate::adjacency_graphs::KEYPAD);
static ref KEYBOARD_STARTING_POSITIONS: u64 = crate::adjacency_graphs::QWERTY.len() as u64;
static ref KEYPAD_STARTING_POSITIONS: u64 = crate::adjacency_graphs::KEYPAD.len() as u64;
}

fn calc_average_degree(graph: &HashMap<char, Vec<Option<&'static str>>>) -> usize {
let sum: usize = graph
fn calc_average_degree(graph: &HashMap<char, Vec<Option<&'static str>>>) -> u64 {
let sum: u64 = graph
.values()
.map(|neighbors| neighbors.iter().filter(|n| n.is_some()).count())
.map(|neighbors| neighbors.iter().filter(|n| n.is_some()).count() as u64)
.sum();
sum / graph.len()
sum / graph.len() as u64
}

impl Estimator for RepeatPattern {
Expand Down Expand Up @@ -826,7 +826,7 @@ mod tests {
let token = "zxcvbn";
let base_guesses = *scoring::KEYBOARD_STARTING_POSITIONS
* *scoring::KEYBOARD_AVERAGE_DEGREE
* (token.len() - 1);
* (token.len() - 1) as u64;
assert_eq!(p.estimate(token), base_guesses as u64);
}

Expand All @@ -839,9 +839,9 @@ mod tests {
shifted_count: 2,
};
let token = "ZxCvbn";
let base_guesses = (*scoring::KEYBOARD_STARTING_POSITIONS
let base_guesses = *scoring::KEYBOARD_STARTING_POSITIONS
* *scoring::KEYBOARD_AVERAGE_DEGREE
* (token.len() - 1)) as u64
* (token.len() - 1) as u64
* (scoring::n_ck(6, 2) + scoring::n_ck(6, 1));
assert_eq!(p.estimate(token), base_guesses);
}
Expand All @@ -857,7 +857,7 @@ mod tests {
let token = "ZXCVBN";
let base_guesses = *scoring::KEYBOARD_STARTING_POSITIONS
* *scoring::KEYBOARD_AVERAGE_DEGREE
* (token.len() - 1)
* (token.len() - 1) as u64
* 2;
assert_eq!(p.estimate(token), base_guesses as u64);
}
Expand Down

0 comments on commit e48a226

Please sign in to comment.