Skip to content

Commit dcd3c66

Browse files
authored
password-hash: remove phc::Ident lifetime (#2107)
`Ident` is a short string with a max length of 32-bytes. Instead of having a lifetime, we can simply store the 32-bytes as an owned type. This is an incremental step towards removing the lifetime from the `phc::PasswordHash` type. The implementation makes the previous `Buffer` (now `StringBuf`) type used for `Params` const generic around a particular size, and adds some basic const init functionality, as well as checks for arithmetic overflow.
1 parent ac8d6ed commit dcd3c66

File tree

5 files changed

+174
-93
lines changed

5 files changed

+174
-93
lines changed

password-hash/src/errors.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ pub enum Error {
2121
/// Cryptographic error.
2222
Crypto,
2323

24+
/// Out of memory (heap allocation failure).
25+
OutOfMemory,
26+
2427
/// Output size unexpected.
2528
OutputSize {
2629
/// Indicates why the output size is unexpected.
@@ -61,11 +64,14 @@ pub enum Error {
6164
/// Salt invalid.
6265
SaltInvalid(InvalidValue),
6366

67+
/// Value exceeds the maximum allowed length.
68+
TooLong,
69+
70+
/// Value does not satisfy the minimum length.
71+
TooShort,
72+
6473
/// Invalid algorithm version.
6574
Version,
66-
67-
/// Out of memory (heap allocation failure).
68-
OutOfMemory,
6975
}
7076

7177
impl fmt::Display for Error {
@@ -74,6 +80,7 @@ impl fmt::Display for Error {
7480
Self::Algorithm => write!(f, "unsupported algorithm"),
7581
Self::B64Encoding(err) => write!(f, "{err}"),
7682
Self::Crypto => write!(f, "cryptographic error"),
83+
Self::OutOfMemory => write!(f, "out of memory"),
7784
Self::OutputSize { provided, expected } => match provided {
7885
Ordering::Less => write!(
7986
f,
@@ -94,8 +101,9 @@ impl fmt::Display for Error {
94101
write!(f, "password hash string contains trailing characters")
95102
}
96103
Self::SaltInvalid(val_err) => write!(f, "salt invalid: {val_err}"),
104+
Self::TooLong => f.write_str("value to long"),
105+
Self::TooShort => f.write_str("value to short"),
97106
Self::Version => write!(f, "invalid algorithm version"),
98-
Self::OutOfMemory => write!(f, "out of memory"),
99107
}
100108
}
101109
}

password-hash/src/phc.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,21 @@ mod ident;
44
mod output;
55
mod params;
66
mod salt;
7+
mod string_buf;
78
mod value;
89

9-
use crate::{Error, PasswordHasher, PasswordVerifier};
1010
pub use ident::Ident;
1111
pub use output::Output;
1212
pub use params::ParamsString;
1313
pub use salt::{Salt, SaltString};
1414
pub use value::{Decimal, Value};
1515

16-
use core::fmt;
16+
use crate::{Error, PasswordHasher, PasswordVerifier};
17+
use core::{fmt, str::FromStr};
18+
use string_buf::StringBuf;
1719

1820
#[cfg(feature = "alloc")]
19-
use alloc::{
20-
str::FromStr,
21-
string::{String, ToString},
22-
};
21+
use alloc::string::{String, ToString};
2322

2423
/// Separator character used in password hashes (e.g. `$6$...`).
2524
const PASSWORD_HASH_SEPARATOR: char = '$';
@@ -62,7 +61,7 @@ pub struct PasswordHash<'a> {
6261
///
6362
/// This corresponds to the `<id>` field in a PHC string, a.k.a. the
6463
/// symbolic name for the function.
65-
pub algorithm: Ident<'a>,
64+
pub algorithm: Ident,
6665

6766
/// Optional version field.
6867
///
@@ -103,7 +102,7 @@ impl<'a> PasswordHash<'a> {
103102
let algorithm = fields
104103
.next()
105104
.ok_or(Error::PhcStringField)
106-
.and_then(Ident::try_from)?;
105+
.and_then(Ident::from_str)?;
107106

108107
let mut version = None;
109108
let mut params = ParamsString::new();
@@ -264,7 +263,7 @@ impl PasswordHashString {
264263
}
265264

266265
/// Password hashing algorithm identifier.
267-
pub fn algorithm(&self) -> Ident<'_> {
266+
pub fn algorithm(&self) -> Ident {
268267
self.password_hash().algorithm
269268
}
270269

password-hash/src/phc/ident.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616
//!
1717
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
1818
19+
use super::StringBuf;
1920
use crate::{Error, Result};
20-
use core::{fmt, ops::Deref, str};
21+
use core::{
22+
fmt,
23+
ops::Deref,
24+
str::{self, FromStr},
25+
};
2126

2227
/// Algorithm or parameter identifier.
2328
///
@@ -32,9 +37,9 @@ use core::{fmt, ops::Deref, str};
3237
///
3338
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
3439
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
35-
pub struct Ident<'a>(&'a str);
40+
pub struct Ident(StringBuf<{ Ident::MAX_LENGTH }>);
3641

37-
impl<'a> Ident<'a> {
42+
impl Ident {
3843
/// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
3944
///
4045
/// This value corresponds to the maximum size of a function symbolic names
@@ -49,7 +54,7 @@ impl<'a> Ident<'a> {
4954
///
5055
/// String must conform to the constraints given in the type-level
5156
/// documentation.
52-
pub const fn new(s: &'a str) -> Result<Self> {
57+
pub const fn new(s: &str) -> Result<Self> {
5358
let input = s.as_bytes();
5459

5560
match input.len() {
@@ -64,7 +69,10 @@ impl<'a> Ident<'a> {
6469
i += 1;
6570
}
6671

67-
Ok(Self(s))
72+
match StringBuf::new(s) {
73+
Ok(buf) => Ok(Self(buf)),
74+
Err(e) => Err(e),
75+
}
6876
}
6977
_ => Err(Error::ParamNameInvalid),
7078
}
@@ -75,7 +83,7 @@ impl<'a> Ident<'a> {
7583
/// This function exists as a workaround for `unwrap` not yet being
7684
/// stable in `const fn` contexts, and is intended to allow the result to
7785
/// be bound to a constant value.
78-
pub const fn new_unwrap(s: &'a str) -> Self {
86+
pub const fn new_unwrap(s: &str) -> Self {
7987
assert!(!s.is_empty(), "PHC ident string can't be empty");
8088
assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long");
8189

@@ -86,42 +94,48 @@ impl<'a> Ident<'a> {
8694
}
8795

8896
/// Borrow this ident as a `str`
89-
pub fn as_str(&self) -> &'a str {
90-
self.0
97+
pub fn as_str(&self) -> &str {
98+
&self.0
9199
}
92100
}
93101

94-
impl AsRef<str> for Ident<'_> {
102+
impl AsRef<str> for Ident {
95103
fn as_ref(&self) -> &str {
96104
self.as_str()
97105
}
98106
}
99107

100-
impl Deref for Ident<'_> {
108+
impl Deref for Ident {
101109
type Target = str;
102110

103111
fn deref(&self) -> &str {
104112
self.as_str()
105113
}
106114
}
107115

108-
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
109-
// the `str` the value is being parsed from.
110-
impl<'a> TryFrom<&'a str> for Ident<'a> {
116+
impl FromStr for Ident {
117+
type Err = Error;
118+
119+
fn from_str(s: &str) -> Result<Self> {
120+
Self::new(s)
121+
}
122+
}
123+
124+
impl TryFrom<&str> for Ident {
111125
type Error = Error;
112126

113-
fn try_from(s: &'a str) -> Result<Self> {
127+
fn try_from(s: &str) -> Result<Self> {
114128
Self::new(s)
115129
}
116130
}
117131

118-
impl fmt::Display for Ident<'_> {
132+
impl fmt::Display for Ident {
119133
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120134
f.write_str(self)
121135
}
122136
}
123137

124-
impl fmt::Debug for Ident<'_> {
138+
impl fmt::Debug for Ident {
125139
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126140
f.debug_tuple("Ident").field(&self.as_ref()).finish()
127141
}

password-hash/src/phc/params.rs

Lines changed: 16 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
11
//! Algorithm parameters.
22
3-
use crate::phc::value::{Decimal, Value};
4-
use crate::{Error, Result, errors::InvalidValue, phc::Ident};
3+
use super::{Decimal, Ident, StringBuf, Value};
4+
use crate::{Error, Result, errors::InvalidValue};
55
use base64ct::{Base64Unpadded as B64, Encoding};
66
use core::{
7-
fmt::{self, Debug, Write},
7+
fmt::{self, Debug, Write as _},
88
str::{self, FromStr},
99
};
1010

1111
/// Individual parameter name/value pair.
12-
pub type Pair<'a> = (Ident<'a>, Value<'a>);
12+
pub type Pair<'a> = (Ident, Value<'a>);
1313

1414
/// Delimiter character between name/value pairs.
1515
pub(crate) const PAIR_DELIMITER: char = '=';
1616

1717
/// Delimiter character between parameters.
1818
pub(crate) const PARAMS_DELIMITER: char = ',';
1919

20-
/// Maximum number of supported parameters.
20+
/// Maximum serialized length of parameters.
2121
const MAX_LENGTH: usize = 127;
2222

2323
/// Error message used with `expect` for when internal invariants are violated
@@ -38,7 +38,7 @@ const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated";
3838
///
3939
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
4040
#[derive(Clone, Default, Eq, PartialEq)]
41-
pub struct ParamsString(Buffer);
41+
pub struct ParamsString(StringBuf<MAX_LENGTH>);
4242

4343
impl ParamsString {
4444
/// Create new empty [`ParamsString`].
@@ -47,7 +47,7 @@ impl ParamsString {
4747
}
4848

4949
/// Add the given byte value to the [`ParamsString`], encoding it as "B64".
50-
pub fn add_b64_bytes<'a>(&mut self, name: impl TryInto<Ident<'a>>, bytes: &[u8]) -> Result<()> {
50+
pub fn add_b64_bytes(&mut self, name: impl TryInto<Ident>, bytes: &[u8]) -> Result<()> {
5151
if !self.is_empty() {
5252
self.0
5353
.write_char(PARAMS_DELIMITER)
@@ -72,15 +72,15 @@ impl ParamsString {
7272
}
7373

7474
/// Add a key/value pair with a decimal value to the [`ParamsString`].
75-
pub fn add_decimal<'a>(&mut self, name: impl TryInto<Ident<'a>>, value: Decimal) -> Result<()> {
75+
pub fn add_decimal(&mut self, name: impl TryInto<Ident>, value: Decimal) -> Result<()> {
7676
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
7777
self.add(name, value)
7878
}
7979

8080
/// Add a key/value pair with a string value to the [`ParamsString`].
8181
pub fn add_str<'a>(
8282
&mut self,
83-
name: impl TryInto<Ident<'a>>,
83+
name: impl TryInto<Ident>,
8484
value: impl TryInto<Value<'a>>,
8585
) -> Result<()> {
8686
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
@@ -118,7 +118,7 @@ impl ParamsString {
118118
}
119119

120120
/// Get a parameter [`Value`] by name.
121-
pub fn get<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Value<'_>> {
121+
pub fn get(&self, name: impl TryInto<Ident>) -> Option<Value<'_>> {
122122
let name = name.try_into().ok()?;
123123

124124
for (n, v) in self.iter() {
@@ -131,19 +131,19 @@ impl ParamsString {
131131
}
132132

133133
/// Get a parameter as a `str`.
134-
pub fn get_str<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<&str> {
134+
pub fn get_str(&self, name: impl TryInto<Ident>) -> Option<&str> {
135135
self.get(name).map(|value| value.as_str())
136136
}
137137

138138
/// Get a parameter as a [`Decimal`].
139139
///
140140
/// See [`Value::decimal`] for format information.
141-
pub fn get_decimal<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Decimal> {
141+
pub fn get_decimal(&self, name: impl TryInto<Ident>) -> Option<Decimal> {
142142
self.get(name).and_then(|value| value.decimal().ok())
143143
}
144144

145145
/// Add a value to this [`ParamsString`] using the provided callback.
146-
fn add(&mut self, name: Ident<'_>, value: impl fmt::Display) -> Result<()> {
146+
fn add(&mut self, name: Ident, value: impl fmt::Display) -> Result<()> {
147147
if self.get(name).is_some() {
148148
return Err(Error::ParamNameDuplicated);
149149
}
@@ -183,7 +183,7 @@ impl FromStr for ParamsString {
183183
param
184184
.next()
185185
.ok_or(Error::ParamNameInvalid)
186-
.and_then(Ident::try_from)?;
186+
.and_then(Ident::from_str)?;
187187

188188
// Validate value
189189
param
@@ -199,7 +199,7 @@ impl FromStr for ParamsString {
199199
let mut bytes = [0u8; MAX_LENGTH];
200200
bytes[..s.len()].copy_from_slice(s.as_bytes());
201201

202-
Ok(Self(Buffer {
202+
Ok(Self(StringBuf {
203203
bytes,
204204
length: s.len() as u8,
205205
}))
@@ -260,7 +260,7 @@ impl<'a> Iterator for Iter<'a> {
260260

261261
let name = param
262262
.next()
263-
.and_then(|id| Ident::try_from(id).ok())
263+
.and_then(|id| Ident::from_str(id).ok())
264264
.expect(INVARIANT_VIOLATED_MSG);
265265

266266
let value = param
@@ -273,54 +273,6 @@ impl<'a> Iterator for Iter<'a> {
273273
}
274274
}
275275

276-
/// Parameter buffer.
277-
#[derive(Clone, Debug, Eq)]
278-
struct Buffer {
279-
/// Byte array containing an ASCII-encoded string.
280-
bytes: [u8; MAX_LENGTH],
281-
282-
/// Length of the string in ASCII characters (i.e. bytes).
283-
length: u8,
284-
}
285-
286-
impl AsRef<str> for Buffer {
287-
fn as_ref(&self) -> &str {
288-
str::from_utf8(&self.bytes[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG)
289-
}
290-
}
291-
292-
impl Default for Buffer {
293-
fn default() -> Buffer {
294-
Buffer {
295-
bytes: [0u8; MAX_LENGTH],
296-
length: 0,
297-
}
298-
}
299-
}
300-
301-
impl PartialEq for Buffer {
302-
fn eq(&self, other: &Self) -> bool {
303-
// Ensure comparisons always honor the initialized portion of the buffer
304-
self.as_ref().eq(other.as_ref())
305-
}
306-
}
307-
308-
impl Write for Buffer {
309-
fn write_str(&mut self, input: &str) -> fmt::Result {
310-
let bytes = input.as_bytes();
311-
let length = self.length as usize;
312-
313-
if length + bytes.len() > MAX_LENGTH {
314-
return Err(fmt::Error);
315-
}
316-
317-
self.bytes[length..(length + bytes.len())].copy_from_slice(bytes);
318-
self.length += bytes.len() as u8;
319-
320-
Ok(())
321-
}
322-
}
323-
324276
#[cfg(test)]
325277
mod tests {
326278
use super::{Error, Ident, ParamsString, Value};

0 commit comments

Comments
 (0)