Fix Secret Key decode rejecting valid keys; verify build

The first base32 symbol carries 3 data bits (26 symbols = 130 bits, 2 spare),
but decode only accepted values 0-3, so ~half of generated keys failed to parse.
Adds an edge-case test, rustfmt config, Cargo.lock, and on-demand cross targets
instead of installing all seven in rust-toolchain.toml.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-15 21:14:15 +09:00
co-authored by Claude Opus 5.5
parent 462fbbc523
commit 4a152bde11
14 changed files with 1947 additions and 59 deletions
+8 -11
View File
@@ -27,16 +27,12 @@ const AMBIGUOUS: &str = "0O1lI|";
/// Generates a password with at least one character from each enabled class.
pub fn generate_password(opts: &PasswordOptions) -> String {
let classes: Vec<Vec<char>> = [
(opts.lowercase, LOWER),
(opts.uppercase, UPPER),
(opts.digits, DIGITS),
(opts.symbols, SYMBOLS),
]
.into_iter()
.filter(|(on, _)| *on)
.map(|(_, set)| set.chars().filter(|c| !(opts.avoid_ambiguous && AMBIGUOUS.contains(*c))).collect())
.collect();
let classes: Vec<Vec<char>> =
[(opts.lowercase, LOWER), (opts.uppercase, UPPER), (opts.digits, DIGITS), (opts.symbols, SYMBOLS)]
.into_iter()
.filter(|(on, _)| *on)
.map(|(_, set)| set.chars().filter(|c| !(opts.avoid_ambiguous && AMBIGUOUS.contains(*c))).collect())
.collect();
let classes = if classes.is_empty() { vec![LOWER.chars().collect()] } else { classes };
let length = opts.length.clamp(classes.len().max(8), 128);
let all: Vec<char> = classes.iter().flatten().copied().collect();
@@ -70,7 +66,8 @@ mod tests {
assert!(p.chars().any(|c| c.is_ascii_digit()));
assert!(!p.chars().any(|c| AMBIGUOUS.contains(c)));
}
let digits_only = PasswordOptions { length: 6, lowercase: false, uppercase: false, symbols: false, ..Default::default() };
let digits_only =
PasswordOptions { length: 6, lowercase: false, uppercase: false, symbols: false, ..Default::default() };
assert_eq!(generate_password(&digits_only).len(), 8); // minimum length 8
}
}
+2 -1
View File
@@ -86,7 +86,8 @@ fn hmac_bytes<M: Mac + hmac::digest::KeyInit>(key: &[u8], msg: &[u8]) -> Vec<u8>
}
fn decode_b32(s: &str) -> Result<Vec<u8>, TotpError> {
let cleaned: String = s.chars().filter(|c| !c.is_whitespace() && *c != '=' && *c != '-').collect::<String>().to_ascii_uppercase();
let cleaned: String =
s.chars().filter(|c| !c.is_whitespace() && *c != '=' && *c != '-').collect::<String>().to_ascii_uppercase();
BASE32_NOPAD.decode(cleaned.as_bytes()).map_err(|_| TotpError::Invalid)
}