Skip to content

Commit 67f2cb4

Browse files
authored
feat: Add more details to ParamValueInvalid and introduce SaltInvalid error (#713)
1 parent a92e5cd commit 67f2cb4

File tree

4 files changed

+75
-32
lines changed

4 files changed

+75
-32
lines changed

password-hash/src/errors.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub enum Error {
3333
ParamNameInvalid,
3434

3535
/// Invalid parameter value.
36-
ParamValueInvalid,
36+
ParamValueInvalid(InvalidValue),
3737

3838
/// Maximum number of parameters exceeded.
3939
ParamsMaxExceeded,
@@ -50,11 +50,8 @@ pub enum Error {
5050
/// Password hash string too long.
5151
PhcStringTooLong,
5252

53-
/// Salt too short.
54-
SaltTooShort,
55-
56-
/// Salt too long.
57-
SaltTooLong,
53+
/// Salt invalid.
54+
SaltInvalid(InvalidValue),
5855

5956
/// Invalid algorithm version.
6057
Version,
@@ -70,14 +67,13 @@ impl fmt::Display for Error {
7067
Self::OutputTooLong => f.write_str("PHF output too long (max 64-bytes)"),
7168
Self::ParamNameDuplicated => f.write_str("duplicate parameter"),
7269
Self::ParamNameInvalid => f.write_str("invalid parameter name"),
73-
Self::ParamValueInvalid => f.write_str("invalid parameter value"),
70+
Self::ParamValueInvalid(val_err) => write!(f, "invalid parameter value: {}", val_err),
7471
Self::ParamsMaxExceeded => f.write_str("maximum number of parameters reached"),
7572
Self::Password => write!(f, "invalid password"),
7673
Self::PhcStringInvalid => write!(f, "password hash string invalid"),
7774
Self::PhcStringTooShort => write!(f, "password hash string too short"),
7875
Self::PhcStringTooLong => write!(f, "password hash string too long"),
79-
Self::SaltTooShort => write!(f, "salt too short"),
80-
Self::SaltTooLong => write!(f, "salt too long"),
76+
Self::SaltInvalid(val_err) => write!(f, "salt invalid: {}", val_err),
8177
Self::Version => write!(f, "invalid algorithm version"),
8278
}
8379
}
@@ -97,3 +93,25 @@ impl From<base64ct::InvalidLengthError> for Error {
9793
Error::B64(B64Error::InvalidLength)
9894
}
9995
}
96+
97+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
98+
#[non_exhaustive]
99+
pub enum InvalidValue {
100+
ToLong,
101+
ToShort,
102+
NotProvided,
103+
InvalidChar,
104+
InvalidFormat,
105+
}
106+
107+
impl fmt::Display for InvalidValue {
108+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
109+
match self {
110+
Self::ToLong => f.write_str("value to long"),
111+
Self::ToShort => f.write_str("value to short"),
112+
Self::NotProvided => f.write_str("required value not provided"),
113+
Self::InvalidChar => f.write_str("contains invalid character"),
114+
Self::InvalidFormat => f.write_str("value format is invalid"),
115+
}
116+
}
117+
}

password-hash/src/params.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Algorithm parameters.
22
3+
use crate::errors::InvalidValue;
34
use crate::{
45
value::{Decimal, Value},
56
Error, Ident, Result,
@@ -56,7 +57,9 @@ impl ParamsString {
5657
value: impl TryInto<Value<'a>>,
5758
) -> Result<()> {
5859
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
59-
let value = value.try_into().map_err(|_| Error::ParamValueInvalid)?;
60+
let value = value
61+
.try_into()
62+
.map_err(|_| Error::ParamValueInvalid(InvalidValue::InvalidFormat))?;
6063
self.add(name, value)
6164
}
6265

@@ -162,11 +165,11 @@ impl FromStr for ParamsString {
162165
// Validate value
163166
param
164167
.next()
165-
.ok_or(Error::ParamValueInvalid)
168+
.ok_or(Error::ParamValueInvalid(InvalidValue::NotProvided))
166169
.and_then(Value::try_from)?;
167170

168171
if param.next().is_some() {
169-
return Err(Error::ParamValueInvalid);
172+
return Err(Error::ParamValueInvalid(InvalidValue::NotProvided));
170173
}
171174
}
172175

password-hash/src/salt.rs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use core::{
66
fmt, str,
77
};
88

9+
use crate::errors::InvalidValue;
910
#[cfg(feature = "rand_core")]
1011
use rand_core::{CryptoRng, RngCore};
1112

@@ -99,14 +100,17 @@ impl<'a> Salt<'a> {
99100
let length = input.as_bytes().len();
100101

101102
if length < Self::MIN_LENGTH {
102-
return Err(Error::SaltTooShort);
103+
return Err(Error::SaltInvalid(InvalidValue::ToShort));
103104
}
104105

105106
if length > Self::MAX_LENGTH {
106-
return Err(Error::SaltTooLong);
107+
return Err(Error::SaltInvalid(InvalidValue::ToLong));
107108
}
108109

109-
input.try_into().map(Self)
110+
input.try_into().map(Self).map_err(|e| match e {
111+
Error::ParamValueInvalid(value_err) => Error::SaltInvalid(value_err),
112+
err => err,
113+
})
110114
}
111115

112116
/// Attempt to decode a B64-encoded [`Salt`], writing the decoded result
@@ -183,7 +187,7 @@ impl SaltString {
183187

184188
/// Create a new [`SaltString`].
185189
pub fn new(s: &str) -> Result<Self> {
186-
// Assert `s` parses successifully as a `Salt`
190+
// Assert `s` parses successfully as a `Salt`
187191
Salt::new(s)?;
188192

189193
let length = s.as_bytes().len();
@@ -196,7 +200,7 @@ impl SaltString {
196200
length: length as u8,
197201
})
198202
} else {
199-
Err(Error::SaltTooLong)
203+
Err(Error::SaltInvalid(InvalidValue::ToLong))
200204
}
201205
}
202206

@@ -257,6 +261,7 @@ impl<'a> From<&'a SaltString> for Salt<'a> {
257261
#[cfg(test)]
258262
mod tests {
259263
use super::{Error, Salt};
264+
use crate::errors::InvalidValue;
260265

261266
#[test]
262267
fn new_with_valid_min_length_input() {
@@ -276,14 +281,21 @@ mod tests {
276281
fn reject_new_too_short() {
277282
for &too_short in &["", "a", "ab", "abc"] {
278283
let err = Salt::new(too_short).err().unwrap();
279-
assert_eq!(err, Error::SaltTooShort);
284+
assert_eq!(err, Error::SaltInvalid(InvalidValue::ToShort));
280285
}
281286
}
282287

283288
#[test]
284289
fn reject_new_too_long() {
285290
let s = "01234567891123456789212345678931234567894123456785234567896234567";
286291
let err = Salt::new(s).err().unwrap();
287-
assert_eq!(err, Error::SaltTooLong);
292+
assert_eq!(err, Error::SaltInvalid(InvalidValue::ToLong));
293+
}
294+
295+
#[test]
296+
fn reject_new_invalid_char() {
297+
let s = "01234_abcd";
298+
let err = Salt::new(s).err().unwrap();
299+
assert_eq!(err, Error::SaltInvalid(InvalidValue::InvalidChar));
288300
}
289301
}

password-hash/src/value.rs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//!
1414
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
1515
16+
use crate::errors::InvalidValue;
1617
use crate::{Encoding, Error, Result};
1718
use core::{convert::TryFrom, fmt, str};
1819

@@ -50,7 +51,7 @@ impl<'a> Value<'a> {
5051
/// the PHC string format's rules.
5152
pub fn new(input: &'a str) -> Result<Self> {
5253
if input.as_bytes().len() > Self::MAX_LENGTH {
53-
return Err(Error::ParamValueInvalid);
54+
return Err(Error::ParamValueInvalid(InvalidValue::ToLong));
5455
}
5556

5657
// Check that the characters are permitted in a PHC parameter value.
@@ -124,26 +125,26 @@ impl<'a> Value<'a> {
124125

125126
// Empty strings aren't decimals
126127
if value.is_empty() {
127-
return Err(Error::ParamValueInvalid);
128+
return Err(Error::ParamValueInvalid(InvalidValue::NotProvided));
128129
}
129130

130131
// Ensure all characters are digits
131132
for c in value.chars() {
132133
if !matches!(c, '0'..='9') {
133-
return Err(Error::ParamValueInvalid);
134+
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar));
134135
}
135136
}
136137

137138
// Disallow leading zeroes
138139
if value.starts_with('0') && value.len() > 1 {
139-
return Err(Error::ParamValueInvalid);
140+
return Err(Error::ParamValueInvalid(InvalidValue::InvalidFormat));
140141
}
141142

142143
value.parse().map_err(|_| {
143144
// In theory a value overflow should be the only potential error here.
144145
// When `ParseIntError::kind` is stable it might be good to double check:
145146
// <https://github.com/rust-lang/rust/issues/22639>
146-
Error::ParamValueInvalid
147+
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
147148
})
148149
}
149150

@@ -193,7 +194,7 @@ impl<'a> fmt::Display for Value<'a> {
193194
fn assert_valid_value(input: &str) -> Result<()> {
194195
for c in input.chars() {
195196
if !is_char_valid(c) {
196-
return Err(Error::ParamValueInvalid);
197+
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar));
197198
}
198199
}
199200

@@ -207,7 +208,7 @@ fn is_char_valid(c: char) -> bool {
207208

208209
#[cfg(test)]
209210
mod tests {
210-
use super::{Error, Value};
211+
use super::{Error, InvalidValue, Value};
211212
use core::convert::TryFrom;
212213

213214
// Invalid value examples
@@ -236,21 +237,27 @@ mod tests {
236237
fn reject_decimal_with_leading_zero() {
237238
let value = Value::new("01").unwrap();
238239
let err = u32::try_from(value).err().unwrap();
239-
assert!(matches!(err, Error::ParamValueInvalid));
240+
assert!(matches!(
241+
err,
242+
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
243+
));
240244
}
241245

242246
#[test]
243247
fn reject_overlong_decimal() {
244248
let value = Value::new("4294967296").unwrap();
245249
let err = u32::try_from(value).err().unwrap();
246-
assert_eq!(err, Error::ParamValueInvalid);
250+
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::InvalidFormat));
247251
}
248252

249253
#[test]
250254
fn reject_negative() {
251255
let value = Value::new("-1").unwrap();
252256
let err = u32::try_from(value).err().unwrap();
253-
assert!(matches!(err, Error::ParamValueInvalid));
257+
assert!(matches!(
258+
err,
259+
Error::ParamValueInvalid(InvalidValue::InvalidChar)
260+
));
254261
}
255262

256263
//
@@ -278,18 +285,21 @@ mod tests {
278285
#[test]
279286
fn reject_invalid_char() {
280287
let err = Value::new(INVALID_CHAR).err().unwrap();
281-
assert!(matches!(err, Error::ParamValueInvalid));
288+
assert!(matches!(
289+
err,
290+
Error::ParamValueInvalid(InvalidValue::InvalidChar)
291+
));
282292
}
283293

284294
#[test]
285295
fn reject_too_long() {
286296
let err = Value::new(INVALID_TOO_LONG).err().unwrap();
287-
assert_eq!(err, Error::ParamValueInvalid);
297+
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::ToLong));
288298
}
289299

290300
#[test]
291301
fn reject_invalid_char_and_too_long() {
292302
let err = Value::new(INVALID_CHAR_AND_TOO_LONG).err().unwrap();
293-
assert_eq!(err, Error::ParamValueInvalid);
303+
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::ToLong));
294304
}
295305
}

0 commit comments

Comments
 (0)