diff options
author | Andreas Grois <andi@grois.info> | 2022-10-09 15:10:47 +0200 |
---|---|---|
committer | Andreas Grois <andi@grois.info> | 2022-10-09 15:10:47 +0200 |
commit | 5ea2a529887caee8bc9d13f860ca66b97ddd8844 (patch) | |
tree | fdead640af91ac5b6c85e81e5424acc8f3921946 | |
parent | d6d345207530ec3232d937aeee3b0c9255b33129 (diff) |
First draft of docs.
-rw-r--r-- | clippy.toml | 1 | ||||
-rw-r--r-- | src/lib.rs | 172 | ||||
-rw-r--r-- | src/passwordmaker/leet.rs | 6 | ||||
-rw-r--r-- | src/passwordmaker/mod.rs | 11 |
4 files changed, 103 insertions, 87 deletions
diff --git a/clippy.toml b/clippy.toml index 935336a..a11f9cf 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ msrv = "1.52.0" +doc-valid-idents = ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", "WebGL", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase", "PasswordMaker"]
\ No newline at end of file @@ -1,13 +1,22 @@ +#![warn(clippy::pedantic)] #![warn(missing_docs)] -//! Library that should allow quick implementation of tools that are compatible with PasswordMaker Pro. +//#![allow(clippy::doc_markdown)] +//! Library that should allow quick implementation of tools that are compatible with [PasswordMaker Pro](https://passwordmaker.org). //! //! It forms the core of an upcoming PasswordMaker Pro compatible Sailfish OS App (as of yet unnamed). //! This library intentionally does not depend on any specific implementation of the cryptographic hashes //! it relies on. To see an example of how to integrate with the [Rust Crypto Hashes](https://github.com/RustCrypto/hashes), //! see the integration tests. //! -//! There are two main functions in this library: [`generate_password`][PasswordMaker::generate_password] and -//! [`parse()`][UrlParsing::parse]. +//! # Description +//! There are two types in this library, you'll likely want to use: [`UrlParsing`] and [`PasswordMaker`]. +//! +//! [`UrlParsing`] takes a user-supplied string, and generates another string from it, according to the passed in settings. +//! The idea is to strip unwanted parts of an URI when generating passwords. For instance, you usually want the same result +//! for all sub-pages of a given website. +//! +//! [`PasswordMaker`] is the main part of this crate. You give it settings similar to those of a PasswordMaker Pro profile, +//! and it gives you a password that's hopfully the same you'd get from PasswordMaker Pro for the same input. mod passwordmaker; @@ -55,11 +64,8 @@ pub trait HasherList { type RIPEMD160 : Ripemd160; } -/// A single-use instance of PasswordMaker, created after all inputs are verified to be usable. -/// Only has one method, which is to generate the password. +/// A cached instance of validated `PasswordMaker` settings. See [`new`][PasswordMaker::new] for details. pub struct PasswordMaker<'a, T : HasherList>{ - data : String, //aka url aka used text - key : String, //aka master password username : &'a str, modifier : &'a str, password_part_parameters : PasswordPartParameters<'a>, //contains pre_leet, as this is different for different algorithms @@ -69,43 +75,8 @@ pub struct PasswordMaker<'a, T : HasherList>{ } impl<'a, T : HasherList> PasswordMaker<'a, T>{ - /// Generates a password in a way that's (hopefully) compatible to PasswordMaker Pro. Returns an error for unusable input. - /// - /// `data` is the string to use, typically a URL or a part of it. - /// `key` is the master password. - /// `hash_algorithm` is a PasswordMaker Pro algorithm selection. - /// `use_leet` details when to use leet, if at all. - /// `characters` is the list of output password characters. Actually this is not true. It's the list of grapheme clusters. - /// `username` is the "username" field of PasswordMaker Pro. - /// `modifier` is the "modifier" field of PasswordMaker Pro. - /// `password_length` is the desired password length to generate. - /// `prefix` is the prefix to which the password gets appended. Counts towards `password_length`. - /// `suffix` is the suffix appended to the password. Counts towards `password_length`. - pub fn generate_password( - data : String, - key: String, - hash_algorithm : HashAlgorithm, - use_leet : UseLeetWhenGenerating, - characters : &'a str, - username : &'a str, - modifier: &'a str, - password_length : usize, - prefix : &'a str, - suffix : &'a str, - ) -> Result<String, GenerationError>{ - Ok( - Self::validate_input(data, key, hash_algorithm, use_leet, characters, username, modifier, password_length, prefix, suffix)? - .generate() - ) - } - - /// Validates user input and returns a `PasswordMaker` object if the input is valid. - /// Use this if you want to split input validation from actual password computation. - /// Otherwise, consider using the `generate_password` function for shorter code. /// - /// `data` is the string to use, typically a URL or a part of it. - /// `key` is the master password. /// `hash_algorithm` is a PasswordMaker Pro algorithm selection. /// `use_leet` details when to use leet, if at all. /// `characters` is the list of output password characters. Actually this is not true. It's the list of grapheme clusters. @@ -114,9 +85,12 @@ impl<'a, T : HasherList> PasswordMaker<'a, T>{ /// `password_length` is the desired password length to generate. /// `prefix` is the prefix to which the password gets appended. Counts towards `password_length`. /// `suffix` is the suffix appended to the password. Counts towards `password_length`. - pub fn validate_input( - data : String, - key: String, + /// + /// # Errors + /// Fails if characters does not contain at least 2 grapheme clusters. Mapping to output happens by number system conversion, + /// and a number system base 1 or base 0 does not make any sense. + #[allow(clippy::too_many_arguments)] + pub fn new( hash_algorithm : HashAlgorithm, use_leet : UseLeetWhenGenerating, characters : &'a str, @@ -125,38 +99,43 @@ impl<'a, T : HasherList> PasswordMaker<'a, T>{ password_length : usize, prefix : &'a str, suffix : &'a str, - ) -> Result<Self, GenerationError> { - if data.len() == 0 { - Err(GenerationError::MissingTextToUse) - } else if key.len() == 0 { - Err(GenerationError::MissingMasterPassword) - } else if !Self::is_suitable_as_output_characters(characters) { - Err(GenerationError::InsufficientCharset) - } else { + ) -> Result<Self, SettingsError> { + if Self::is_suitable_as_output_characters(characters) { let post_leet = match &use_leet { UseLeetWhenGenerating::NotAtAll | UseLeetWhenGenerating::Before { .. } => None, UseLeetWhenGenerating::After { level } | UseLeetWhenGenerating::BeforeAndAfter { level } - => Some(LeetReplacementTable::get(level)), + => Some(LeetReplacementTable::get(*level)), }; Ok(PasswordMaker { - data, - key, username, modifier, - password_part_parameters: PasswordPartParameters::from_public_parameters(hash_algorithm, &use_leet, characters), + password_part_parameters: PasswordPartParameters::from_public_parameters(hash_algorithm, use_leet, characters), post_leet, assembly_settings: PasswordAssemblyParameters::from_public_parameters(prefix, suffix, password_length), _hashers: PhantomData, }) + } else { + Err(SettingsError::InsufficientCharset) } } - /// Consumes the PasswordMaker and returns the generated password. - pub fn generate(self) -> String { - self.generate_password_verified_input() + /// Generates a password for the given `data` and `key`. + /// `data` is the "text-to-use", typically the output of [`UrlParsing`]. + /// `key` is the key, also known as "master password". + /// + /// # Errors + /// Fails if either of the parameters has zero-length. + pub fn generate(&self, data: String, key: String) -> Result<String, GenerationError> { + if data.is_empty() { + Err(GenerationError::MissingTextToUse) + } else if key.is_empty(){ + Err(GenerationError::MissingMasterPassword) + } else { + Ok(self.generate_password_verified_input(data, key)) + } } } @@ -164,27 +143,39 @@ impl<'a, T : HasherList> PasswordMaker<'a, T>{ #[cfg_attr(test, derive(strum_macros::EnumIter))] #[derive(Debug,Clone, Copy)] pub enum LeetLevel { - /// First Leet level: ["4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"] + /// First Leet level:\ + /// `["4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"]` One, - /// Second Leet level: ["4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"] + /// Second Leet level:\ + /// `["4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"]` Two, - /// Third Leet level: ["4", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"] + /// Third Leet level:\ + /// `["4", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"]` Three, - /// Fourth Leet level: ["@", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"] + /// Fourth Leet level:\ + /// `["@", "8", "c", "d", "3", "f", "6", "h", "'", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "'/", "2"]` Four, - /// Fifth Leet level: ["@", "|3", "c", "d", "3", "f", "6", "#", "!", "7", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"] + /// Fifth Leet level:\ + /// `["@", "|3", "c", "d", "3", "f", "6", "#", "!", "7", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"]` Five, - /// Sixth Leet level: ["@", "|3", "c", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"] + /// Sixth Leet level:\ + /// `["@", "|3", "c", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "m", "n", "0", "|>", "9", "|2", "$", "7", "u", "\\/", "w", "x", "'/", "2"]` Six, - /// Seventh Leet level: ["@", "|3", "[", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "^^", "^/", "0", "|*", "9", "|2", "5", "7", "(_)", "\\/", "\\/\\/", "><", "'/", "2"] + /// Seventh Leet level:\ + /// `["@", "|3", "[", "|)", "&", "|=", "6", "#", "!", ",|", "|<", "1", "^^", "^/", "0", "|*", "9", "|2", "5", "7", "(_)", "\\/", "\\/\\/", "><", "'/", "2"]` Seven, - /// Eigth Leet level: ["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|(", "1", "|\\/|", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"] + /// Eigth Leet level:\ + /// `["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|(", "1", "|\\/|", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"]` Eight, - /// Ninth Leet level: ["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|{", "|_", "/\\/\\", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"] + /// Ninth Leet level:\ + /// `["@", "8", "(", "|)", "&", "|=", "6", "|-|", "!", "_|", "|{", "|_", "/\\/\\", "|\\|", "()", "|>", "(,)", "|2", "$", "|", "|_|", "\\/", "\\^/", ")(", "'/", "\"/_"]` Nine, } /// The hash algorithm to use, as shown in the GUI of the JavaScript edition of PasswordMaker Pro. +/// +/// # Description +/// /// Most algorithms work by computing the hash of the input values and doing a number system base conversion to indices into /// the supplied character array. /// Notable exceptions are the HMAC algorithms, which not only compute the HMAC for the input, but also, before that, encode the @@ -221,7 +212,9 @@ pub enum HashAlgorithm { HmacRipemd160, } -/// When the Leet replacement shown in leet.rs is applied. +/// When the Leet replacement as illustrated in [`LeetLevel`] is applied. +/// +/// # Description /// If Leet is enabled, the input will be converted to lower case. /// It is always applied to each password part when the required password length /// is longer than the length obtained by computing a single hash. This is important if the input data or output charset contains certain @@ -259,8 +252,10 @@ pub struct UrlParsing { use_port_path : bool, } +#[allow(clippy::fn_params_excessive_bools)] impl UrlParsing { /// Creates a new `UrlParsing` instance with the given settings. + #[must_use] pub fn new( use_protocol : ProtocolUsageMode, use_userinfo : bool, @@ -273,11 +268,15 @@ impl UrlParsing { /// Parses an input string, applying the settings in `self`, and generates a string suitable for /// the `data` parameter of [`PasswordMaker`] + #[must_use] pub fn parse(&self, input : &str) -> String{ self.make_used_text_from_url(input) } } +/// How to handle the URL protocol, or the absence of it, during [`UrlParsing`]. +/// +/// # Description /// The "Use Protocol" checkbox in PasswordMaker Pro Javascript Edition has some weird behaviour, that's probably a bug. /// This enum lets you select how to hande the case that the user wants to use the Protocol, but the input string doesn't contain one. #[derive(Debug, Clone, Copy)] @@ -291,19 +290,15 @@ pub enum ProtocolUsageMode{ UsedWithUndefinedIfEmpty, } + + /// Error returned if the supplied input did not meet expectations. -/// The two "missing" variants are self-explanatory, but the `InsufficientCharset` might need some explanation: -/// `InsufficientCharset` means that the output character set does not contain at least two grapheme clusters. -/// Since the output string is computed by doing a base system conversion from binary to number-of-grapheme-clusters, -/// any number of grapheme clusters lower than 2 forms a nonsensical input. There simply is no base-1 or base-0 number system. #[derive(Debug, Clone, Copy)] pub enum GenerationError { /// Password generation failed, because the user did not supply a master password. MissingMasterPassword, /// Password generation failed, because the user did not supply a text-to-use. MissingTextToUse, - /// Password generation failed, because the character set supplied by the user did not contain at least 2 grapheme clusters. - InsufficientCharset } impl Display for GenerationError { @@ -311,8 +306,29 @@ impl Display for GenerationError { match self { GenerationError::MissingMasterPassword => write!(f, "No master password given."), GenerationError::MissingTextToUse => write!(f, "No text to use. Would just hash the master password."), - GenerationError::InsufficientCharset => write!(f, "Charset needs to have at least 2 characters."), } } } -impl Error for GenerationError{}
\ No newline at end of file +impl Error for GenerationError{} + + +/// Error returned if creation of a `PasswordMaker` object failed due to invalid settings. +/// +/// # Description +/// `InsufficientCharset` means that the output character set does not contain at least two grapheme clusters. +/// Since the output string is computed by doing a base system conversion from binary to number-of-grapheme-clusters, +/// any number of grapheme clusters lower than 2 forms a nonsensical input. There simply is no base-1 or base-0 number system. +#[derive(Debug, Clone, Copy)] +pub enum SettingsError { + /// Password generation failed, because the character set supplied by the user did not contain at least 2 grapheme clusters. + InsufficientCharset, +} + +impl Display for SettingsError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SettingsError::InsufficientCharset => write!(f, "Charset needs to have at least 2 characters."), + } + } +} +impl Error for SettingsError{}
\ No newline at end of file diff --git a/src/passwordmaker/leet.rs b/src/passwordmaker/leet.rs index 858b2ad..16f27b8 100644 --- a/src/passwordmaker/leet.rs +++ b/src/passwordmaker/leet.rs @@ -11,7 +11,7 @@ enum CharOrSlice{ impl LeetReplacementTable { /// Gets the appropriate leet replacement table for a given leet level. - pub(crate) fn get(leet_level : &LeetLevel) -> LeetReplacementTable { + pub(crate) fn get(leet_level : LeetLevel) -> LeetReplacementTable { let lookup_table = match leet_level { LeetLevel::One => &["4", "b", "c", "d", "3", "f", "g", "h", "i", "j", "k", "1", "m", "n", "0", "p", "9", "r", "s", "7", "u", "v", "w", "x", "y", "z"], LeetLevel::Two => &["4", "b", "c", "d", "3", "f", "g", "h", "1", "j", "k", "1", "m", "n", "0", "p", "9", "r", "5", "7", "u", "v", "w", "x", "y", "2"], @@ -76,7 +76,7 @@ mod leet_tests{ #[test] fn leet_test_icelandic(){ for leet_level in LeetLevel::iter(){ - let result = LeetReplacementTable::get(&leet_level).leetify(get_icelandic_test_string()); + let result = LeetReplacementTable::get(leet_level).leetify(get_icelandic_test_string()); let expected = get_icelandic_test_result(leet_level); assert_eq!(result, expected); } @@ -92,7 +92,7 @@ mod leet_tests{ #[test] fn leet_test_greek(){ for leet_level in LeetLevel::iter(){ - let result = LeetReplacementTable::get(&leet_level).leetify(get_greek_test_string()); + let result = LeetReplacementTable::get(leet_level).leetify(get_greek_test_string()); let expected = get_greek_test_result(leet_level); assert_eq!(result, expected); } diff --git a/src/passwordmaker/mod.rs b/src/passwordmaker/mod.rs index eb39c9e..ba85623 100644 --- a/src/passwordmaker/mod.rs +++ b/src/passwordmaker/mod.rs @@ -18,18 +18,17 @@ impl<'y, H : super::HasherList> super::PasswordMaker<'y, H>{ characters.graphemes(true).nth(1).is_some() } - pub(super) fn generate_password_verified_input(self) -> String { - let modified_data = self.data + self.username + self.modifier; - let key = self.key; + pub(super) fn generate_password_verified_input(&self, data : String, key : String) -> String { + let modified_data = data + self.username + self.modifier; let get_modified_key = move |i : usize| { if i == 0 {key.clone()} else {key.clone() + "\n" + &i.to_string()}}; //In Passwordmaker Pro, leet is applied on a per-password-part basis. This means that if a password part ends in an upper-case Sigma, //the results would differ if we moved leeting to after all password parts were joined, or worse, did it on a per-character level. //However, this makes the code a lot more complex, as it forces us to create an owned string for each password part before combining. //Therefore, we treat that case special. - match self.post_leet { + match &self.post_leet { None => Self::generate_password_verified_no_post_leet(&modified_data, get_modified_key, &self.assembly_settings, &self.password_part_parameters), - Some(leet_level) => Self::generate_password_verified_with_post_leet(&modified_data, get_modified_key,&self.assembly_settings , &self.password_part_parameters, &leet_level), + Some(leet_level) => Self::generate_password_verified_with_post_leet(&modified_data, get_modified_key,&self.assembly_settings , &self.password_part_parameters, leet_level), } } @@ -247,7 +246,7 @@ pub(super) struct PasswordPartParameters<'a>{ } impl<'a> PasswordPartParameters<'a>{ - pub(super) fn from_public_parameters(hash_algorithm : super::HashAlgorithm, leet : &super::UseLeetWhenGenerating, characters : &'a str) -> Self { + pub(super) fn from_public_parameters(hash_algorithm : super::HashAlgorithm, leet : super::UseLeetWhenGenerating, characters : &'a str) -> Self { use super::UseLeetWhenGenerating; let hash_algorithm = AlgoSelection::from_settings_algorithm(hash_algorithm); PasswordPartParameters{ |