aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2022-10-09 15:10:47 +0200
committerAndreas Grois <andi@grois.info>2022-10-09 15:10:47 +0200
commit5ea2a529887caee8bc9d13f860ca66b97ddd8844 (patch)
treefdead640af91ac5b6c85e81e5424acc8f3921946 /src
parentd6d345207530ec3232d937aeee3b0c9255b33129 (diff)
First draft of docs.
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs172
-rw-r--r--src/passwordmaker/leet.rs6
-rw-r--r--src/passwordmaker/mod.rs11
3 files changed, 102 insertions, 87 deletions
diff --git a/src/lib.rs b/src/lib.rs
index e9181c3..a005e43 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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{