chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

73
vendor/password-hash/src/encoding.rs vendored Normal file
View File

@@ -0,0 +1,73 @@
//! Base64 encoding variants.
use base64ct::{
Base64Bcrypt, Base64Crypt, Base64Unpadded as B64, Encoding as _, Error as B64Error,
};
/// Base64 encoding variants.
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum Encoding {
/// "B64" encoding: standard Base64 without padding.
///
/// ```text
/// [A-Z] [a-z] [0-9] + /
/// 0x41-0x5a, 0x61-0x7a, 0x30-0x39, 0x2b, 0x2f
/// ```
/// <https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#b64>
B64,
/// bcrypt encoding.
///
/// ```text
/// ./ [A-Z] [a-z] [0-9]
/// 0x2e-0x2f, 0x41-0x5a, 0x61-0x7a, 0x30-0x39
/// ```
Bcrypt,
/// `crypt(3)` encoding.
///
/// ```text
/// [.-9] [A-Z] [a-z]
/// 0x2e-0x39, 0x41-0x5a, 0x61-0x7a
/// ```
Crypt,
}
impl Default for Encoding {
fn default() -> Self {
Self::B64
}
}
impl Encoding {
/// Decode a Base64 string into the provided destination buffer.
pub fn decode(self, src: impl AsRef<[u8]>, dst: &mut [u8]) -> Result<&[u8], B64Error> {
match self {
Self::B64 => B64::decode(src, dst),
Self::Bcrypt => Base64Bcrypt::decode(src, dst),
Self::Crypt => Base64Crypt::decode(src, dst),
}
}
/// Encode the input byte slice as Base64.
///
/// Writes the result into the provided destination slice, returning an
/// ASCII-encoded Base64 string value.
pub fn encode<'a>(self, src: &[u8], dst: &'a mut [u8]) -> Result<&'a str, B64Error> {
match self {
Self::B64 => B64::encode(src, dst),
Self::Bcrypt => Base64Bcrypt::encode(src, dst),
Self::Crypt => Base64Crypt::encode(src, dst),
}
.map_err(Into::into)
}
/// Get the length of Base64 produced by encoding the given bytes.
pub fn encoded_len(self, bytes: &[u8]) -> usize {
match self {
Self::B64 => B64::encoded_len(bytes),
Self::Bcrypt => Base64Bcrypt::encoded_len(bytes),
Self::Crypt => Base64Crypt::encoded_len(bytes),
}
}
}

171
vendor/password-hash/src/errors.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
//! Error types.
pub use base64ct::Error as B64Error;
use core::cmp::Ordering;
use core::fmt;
/// Result type.
pub type Result<T> = core::result::Result<T, Error>;
/// Password hashing errors.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
/// Unsupported algorithm.
Algorithm,
/// "B64" encoding error.
B64Encoding(B64Error),
/// Cryptographic error.
Crypto,
/// Output size unexpected.
OutputSize {
/// Indicates why the output size is unexpected.
///
/// - [`Ordering::Less`]: Size is too small.
/// - [`Ordering::Equal`]: Size is not exactly as `expected`.
/// - [`Ordering::Greater`]: Size is too long.
provided: Ordering,
/// Expected output size in relation to `provided`.
///
/// - [`Ordering::Less`]: Minimum size.
/// - [`Ordering::Equal`]: Expecrted size.
/// - [`Ordering::Greater`]: Maximum size.
expected: usize,
},
/// Duplicate parameter name encountered.
ParamNameDuplicated,
/// Invalid parameter name.
ParamNameInvalid,
/// Invalid parameter value.
ParamValueInvalid(InvalidValue),
/// Maximum number of parameters exceeded.
ParamsMaxExceeded,
/// Invalid password.
Password,
/// Password hash string invalid.
PhcStringField,
/// Password hash string contains trailing data.
PhcStringTrailingData,
/// Salt invalid.
SaltInvalid(InvalidValue),
/// Invalid algorithm version.
Version,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
match self {
Self::Algorithm => write!(f, "unsupported algorithm"),
Self::B64Encoding(err) => write!(f, "{}", err),
Self::Crypto => write!(f, "cryptographic error"),
Self::OutputSize { provided, expected } => match provided {
Ordering::Less => write!(
f,
"output size too short, expected at least {} bytes",
expected
),
Ordering::Equal => write!(f, "output size unexpected, expected {} bytes", expected),
Ordering::Greater => write!(
f,
"output size too long, expected at most {} bytes",
expected
),
},
Self::ParamNameDuplicated => f.write_str("duplicate parameter"),
Self::ParamNameInvalid => f.write_str("invalid parameter name"),
Self::ParamValueInvalid(val_err) => write!(f, "invalid parameter value: {}", val_err),
Self::ParamsMaxExceeded => f.write_str("maximum number of parameters reached"),
Self::Password => write!(f, "invalid password"),
Self::PhcStringField => write!(f, "password hash string missing field"),
Self::PhcStringTrailingData => {
write!(f, "password hash string contains trailing characters")
}
Self::SaltInvalid(val_err) => write!(f, "salt invalid: {}", val_err),
Self::Version => write!(f, "invalid algorithm version"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::B64Encoding(err) => Some(err),
Self::ParamValueInvalid(err) => Some(err),
Self::SaltInvalid(err) => Some(err),
_ => None,
}
}
}
impl From<B64Error> for Error {
fn from(err: B64Error) -> Error {
Error::B64Encoding(err)
}
}
impl From<base64ct::InvalidLengthError> for Error {
fn from(_: base64ct::InvalidLengthError) -> Error {
Error::B64Encoding(B64Error::InvalidLength)
}
}
/// Parse errors relating to invalid parameter values or salts.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum InvalidValue {
/// Character is not in the allowed set.
InvalidChar(char),
/// Format is invalid.
InvalidFormat,
/// Value is malformed.
Malformed,
/// Value exceeds the maximum allowed length.
TooLong,
/// Value does not satisfy the minimum length.
TooShort,
}
impl InvalidValue {
/// Create an [`Error::ParamValueInvalid`] which warps this error.
pub fn param_error(self) -> Error {
Error::ParamValueInvalid(self)
}
/// Create an [`Error::SaltInvalid`] which wraps this error.
pub fn salt_error(self) -> Error {
Error::SaltInvalid(self)
}
}
impl fmt::Display for InvalidValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> core::result::Result<(), fmt::Error> {
match self {
Self::InvalidChar(c) => write!(f, "contains invalid character: '{}'", c),
Self::InvalidFormat => f.write_str("value format is invalid"),
Self::Malformed => f.write_str("value malformed"),
Self::TooLong => f.write_str("value to long"),
Self::TooShort => f.write_str("value to short"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidValue {}

171
vendor/password-hash/src/ident.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
//! Algorithm or parameter identifier.
//!
//! Implements the following parts of the [PHC string format specification][1]:
//!
//! > The function symbolic name is a sequence of characters in: `[a-z0-9-]`
//! > (lowercase letters, digits, and the minus sign). No other character is
//! > allowed. Each function defines its own identifier (or identifiers in case
//! > of a function family); identifiers should be explicit (human readable,
//! > not a single digit), with a length of about 5 to 10 characters. An
//! > identifier name MUST NOT exceed 32 characters in length.
//! >
//! > Each parameter name shall be a sequence of characters in: `[a-z0-9-]`
//! > (lowercase letters, digits, and the minus sign). No other character is
//! > allowed. Parameter names SHOULD be readable for a human user. A
//! > parameter name MUST NOT exceed 32 characters in length.
//!
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
use crate::{Error, Result};
use core::{fmt, ops::Deref, str};
/// Algorithm or parameter identifier.
///
/// This type encompasses both the "function symbolic name" and "parameter name"
/// use cases as described in the [PHC string format specification][1].
///
/// # Constraints
/// - ASCII-encoded string consisting of the characters `[a-z0-9-]`
/// (lowercase letters, digits, and the minus sign)
/// - Minimum length: 1 ASCII character (i.e. 1-byte)
/// - Maximum length: 32 ASCII characters (i.e. 32-bytes)
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Ident<'a>(&'a str);
impl<'a> Ident<'a> {
/// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
///
/// This value corresponds to the maximum size of a function symbolic names
/// and parameter names according to the PHC string format.
/// Maximum length of an [`Ident`] - 32 ASCII characters (i.e. 32-bytes).
///
/// This value corresponds to the maximum size of a function symbolic names
/// and parameter names according to the PHC string format.
const MAX_LENGTH: usize = 32;
/// Parse an [`Ident`] from a string.
///
/// String must conform to the constraints given in the type-level
/// documentation.
pub const fn new(s: &'a str) -> Result<Self> {
let input = s.as_bytes();
match input.len() {
1..=Self::MAX_LENGTH => {
let mut i = 0;
while i < input.len() {
if !matches!(input[i], b'a'..=b'z' | b'0'..=b'9' | b'-') {
return Err(Error::ParamNameInvalid);
}
i += 1;
}
Ok(Self(s))
}
_ => Err(Error::ParamNameInvalid),
}
}
/// Parse an [`Ident`] from a string, panicking on parse errors.
///
/// This function exists as a workaround for `unwrap` not yet being
/// stable in `const fn` contexts, and is intended to allow the result to
/// be bound to a constant value.
pub const fn new_unwrap(s: &'a str) -> Self {
assert!(!s.is_empty(), "PHC ident string can't be empty");
assert!(s.len() <= Self::MAX_LENGTH, "PHC ident string too long");
match Self::new(s) {
Ok(ident) => ident,
Err(_) => panic!("invalid PHC string format identifier"),
}
}
/// Borrow this ident as a `str`
pub fn as_str(&self) -> &'a str {
self.0
}
}
impl<'a> AsRef<str> for Ident<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> Deref for Ident<'a> {
type Target = str;
fn deref(&self) -> &str {
self.as_str()
}
}
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
// the `str` the value is being parsed from.
impl<'a> TryFrom<&'a str> for Ident<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self> {
Self::new(s)
}
}
impl<'a> fmt::Display for Ident<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self)
}
}
impl<'a> fmt::Debug for Ident<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Ident").field(&self.as_ref()).finish()
}
}
#[cfg(test)]
mod tests {
use super::{Error, Ident};
// Invalid ident examples
const INVALID_EMPTY: &str = "";
const INVALID_CHAR: &str = "argon2;d";
const INVALID_TOO_LONG: &str = "012345678911234567892123456789312";
const INVALID_CHAR_AND_TOO_LONG: &str = "0!2345678911234567892123456789312";
#[test]
fn parse_valid() {
let valid_examples = ["6", "x", "argon2d", "01234567891123456789212345678931"];
for &example in &valid_examples {
assert_eq!(example, &*Ident::new(example).unwrap());
}
}
#[test]
fn reject_empty() {
assert_eq!(Ident::new(INVALID_EMPTY), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_invalid() {
assert_eq!(Ident::new(INVALID_CHAR), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_too_long() {
assert_eq!(Ident::new(INVALID_TOO_LONG), Err(Error::ParamNameInvalid));
}
#[test]
fn reject_invalid_char_and_too_long() {
assert_eq!(
Ident::new(INVALID_CHAR_AND_TOO_LONG),
Err(Error::ParamNameInvalid)
);
}
}

389
vendor/password-hash/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,389 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms, unused_lifetimes)]
//!
//! # Usage
//!
//! This crate represents password hashes using the [`PasswordHash`] type, which
//! represents a parsed "PHC string" with the following format:
//!
//! ```text
//! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
//! ```
//!
//! For more information, please see the documentation for [`PasswordHash`].
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "rand_core")]
pub use rand_core;
pub mod errors;
mod encoding;
mod ident;
mod output;
mod params;
mod salt;
mod traits;
mod value;
pub use crate::{
encoding::Encoding,
errors::{Error, Result},
ident::Ident,
output::Output,
params::ParamsString,
salt::{Salt, SaltString},
traits::{McfHasher, PasswordHasher, PasswordVerifier},
value::{Decimal, Value},
};
use core::fmt::{self, Debug};
#[cfg(feature = "alloc")]
use alloc::{
str::FromStr,
string::{String, ToString},
};
/// Separator character used in password hashes (e.g. `$6$...`).
const PASSWORD_HASH_SEPARATOR: char = '$';
/// Password hash.
///
/// This type corresponds to the parsed representation of a PHC string as
/// described in the [PHC string format specification][1].
///
/// PHC strings have the following format:
///
/// ```text
/// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]]
/// ```
///
/// where:
///
/// - `<id>` is the symbolic name for the function
/// - `<version>` is the algorithm version
/// - `<param>` is a parameter name
/// - `<value>` is a parameter value
/// - `<salt>` is an encoding of the salt
/// - `<hash>` is an encoding of the hash output
///
/// The string is then the concatenation, in that order, of:
///
/// - a `$` sign;
/// - the function symbolic name;
/// - optionally, a `$` sign followed by the algorithm version with a `v=version` format;
/// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format;
/// the parameters are separated by commas;
/// - optionally, a `$` sign followed by the (encoded) salt value;
/// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present
/// only if the salt is present).
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PasswordHash<'a> {
/// Password hashing algorithm identifier.
///
/// This corresponds to the `<id>` field in a PHC string, a.k.a. the
/// symbolic name for the function.
pub algorithm: Ident<'a>,
/// Optional version field.
///
/// This corresponds to the `<version>` field in a PHC string.
pub version: Option<Decimal>,
/// Algorithm-specific parameters.
///
/// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*`
/// name/value pairs in a PHC string.
pub params: ParamsString,
/// [`Salt`] string for personalizing a password hash output.
///
/// This corresponds to the `<salt>` value in a PHC string.
pub salt: Option<Salt<'a>>,
/// Password hashing function [`Output`], a.k.a. hash/digest.
///
/// This corresponds to the `<hash>` output in a PHC string.
pub hash: Option<Output>,
}
impl<'a> PasswordHash<'a> {
/// Parse a password hash from a string in the PHC string format.
pub fn new(s: &'a str) -> Result<Self> {
Self::parse(s, Encoding::default())
}
/// Parse a password hash from the given [`Encoding`].
pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> {
if s.is_empty() {
return Err(Error::PhcStringField);
}
let mut fields = s.split(PASSWORD_HASH_SEPARATOR);
let beginning = fields.next().expect("no first field");
if beginning.chars().next().is_some() {
return Err(Error::PhcStringField);
}
let algorithm = fields
.next()
.ok_or(Error::PhcStringField)
.and_then(Ident::try_from)?;
let mut version = None;
let mut params = ParamsString::new();
let mut salt = None;
let mut hash = None;
let mut next_field = fields.next();
if let Some(field) = next_field {
// v=<version>
if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) {
version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?);
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(field) = next_field {
// <param>=<value>
if field.contains(params::PAIR_DELIMITER) {
params = field.parse()?;
next_field = None;
}
}
if next_field.is_none() {
next_field = fields.next();
}
if let Some(s) = next_field {
salt = Some(s.try_into()?);
}
if let Some(field) = fields.next() {
hash = Some(Output::decode(field, encoding)?);
}
if fields.next().is_some() {
return Err(Error::PhcStringTrailingData);
}
Ok(Self {
algorithm,
version,
params,
salt,
hash,
})
}
/// Generate a password hash using the supplied algorithm.
pub fn generate(
phf: impl PasswordHasher,
password: impl AsRef<[u8]>,
salt: impl Into<Salt<'a>>,
) -> Result<Self> {
phf.hash_password(password.as_ref(), salt)
}
/// Verify this password hash using the specified set of supported
/// [`PasswordHasher`] trait objects.
pub fn verify_password(
&self,
phfs: &[&dyn PasswordVerifier],
password: impl AsRef<[u8]>,
) -> Result<()> {
for &phf in phfs {
if phf.verify_password(password.as_ref(), self).is_ok() {
return Ok(());
}
}
Err(Error::Password)
}
/// Get the [`Encoding`] that this [`PasswordHash`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.hash.map(|h| h.encoding()).unwrap_or_default()
}
/// Serialize this [`PasswordHash`] as a [`PasswordHashString`].
#[cfg(feature = "alloc")]
pub fn serialize(&self) -> PasswordHashString {
self.into()
}
}
// Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on
// the `str` the value is being parsed from.
impl<'a> TryFrom<&'a str> for PasswordHash<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self> {
Self::new(s)
}
}
impl<'a> fmt::Display for PasswordHash<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?;
if let Some(version) = self.version {
write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?;
}
if !self.params.is_empty() {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?;
}
if let Some(salt) = &self.salt {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?;
if let Some(hash) = &self.hash {
write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?;
}
}
Ok(())
}
}
/// Serialized [`PasswordHash`].
///
/// This type contains a serialized password hash string which is ensured to
/// parse successfully.
// TODO(tarcieri): cached parsed representations? or at least structural data
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PasswordHashString {
/// String value
string: String,
/// String encoding
encoding: Encoding,
}
#[cfg(feature = "alloc")]
#[allow(clippy::len_without_is_empty)]
impl PasswordHashString {
/// Parse a password hash from a string in the PHC string format.
pub fn new(s: &str) -> Result<Self> {
Self::parse(s, Encoding::default())
}
/// Parse a password hash from the given [`Encoding`].
pub fn parse(s: &str, encoding: Encoding) -> Result<Self> {
Ok(PasswordHash::parse(s, encoding)?.into())
}
/// Parse this owned string as a [`PasswordHash`].
pub fn password_hash(&self) -> PasswordHash<'_> {
PasswordHash::parse(&self.string, self.encoding).expect("malformed password hash")
}
/// Get the [`Encoding`] that this [`PasswordHashString`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.encoding
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &str {
self.string.as_str()
}
/// Borrow this value as bytes.
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Password hashing algorithm identifier.
pub fn algorithm(&self) -> Ident<'_> {
self.password_hash().algorithm
}
/// Optional version field.
pub fn version(&self) -> Option<Decimal> {
self.password_hash().version
}
/// Algorithm-specific parameters.
pub fn params(&self) -> ParamsString {
self.password_hash().params
}
/// [`Salt`] string for personalizing a password hash output.
pub fn salt(&self) -> Option<Salt<'_>> {
self.password_hash().salt
}
/// Password hashing function [`Output`], a.k.a. hash/digest.
pub fn hash(&self) -> Option<Output> {
self.password_hash().hash
}
}
#[cfg(feature = "alloc")]
impl AsRef<str> for PasswordHashString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(feature = "alloc")]
impl From<PasswordHash<'_>> for PasswordHashString {
fn from(hash: PasswordHash<'_>) -> PasswordHashString {
PasswordHashString::from(&hash)
}
}
#[cfg(feature = "alloc")]
impl From<&PasswordHash<'_>> for PasswordHashString {
fn from(hash: &PasswordHash<'_>) -> PasswordHashString {
PasswordHashString {
string: hash.to_string(),
encoding: hash.encoding(),
}
}
}
#[cfg(feature = "alloc")]
impl FromStr for PasswordHashString {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::new(s)
}
}
#[cfg(feature = "alloc")]
impl fmt::Display for PasswordHashString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}

334
vendor/password-hash/src/output.rs vendored Normal file
View File

@@ -0,0 +1,334 @@
//! Outputs from password hashing functions.
use crate::{Encoding, Error, Result};
use core::{
cmp::{Ordering, PartialEq},
fmt,
str::FromStr,
};
use subtle::{Choice, ConstantTimeEq};
/// Output from password hashing functions, i.e. the "hash" or "digest"
/// as raw bytes.
///
/// The [`Output`] type implements the RECOMMENDED best practices described in
/// the [PHC string format specification][1], namely:
///
/// > The hash output, for a verification, must be long enough to make preimage
/// > attacks at least as hard as password guessing. To promote wide acceptance,
/// > a default output size of 256 bits (32 bytes, encoded as 43 characters) is
/// > recommended. Function implementations SHOULD NOT allow outputs of less
/// > than 80 bits to be used for password verification.
///
/// # Recommended length
/// Per the description above, the recommended default length for an [`Output`]
/// of a password hashing function is **32-bytes** (256-bits).
///
/// # Constraints
/// The above guidelines are interpreted into the following constraints:
///
/// - Minimum length: **10**-bytes (80-bits)
/// - Maximum length: **64**-bytes (512-bits)
///
/// The specific recommendation of a 64-byte maximum length is taken as a best
/// practice from the hash output guidelines for [Argon2 Encoding][2] given in
/// the same document:
///
/// > The hash output...length shall be between 12 and 64 bytes (16 and 86
/// > characters, respectively). The default output length is 32 bytes
/// > (43 characters).
///
/// Based on this guidance, this type enforces an upper bound of 64-bytes
/// as a reasonable maximum, and recommends using 32-bytes.
///
/// # Constant-time comparisons
/// The [`Output`] type impls the [`ConstantTimeEq`] trait from the [`subtle`]
/// crate and uses it to perform constant-time comparisons.
///
/// Additionally the [`PartialEq`] and [`Eq`] trait impls for [`Output`] use
/// [`ConstantTimeEq`] when performing comparisons.
///
/// ## Attacks on non-constant-time password hash comparisons
/// Comparing password hashes in constant-time is known to mitigate at least
/// one [poorly understood attack][3] involving an adversary with the following
/// knowledge/capabilities:
///
/// - full knowledge of what password hashing algorithm is being used
/// including any relevant configurable parameters
/// - knowledge of the salt for a particular victim
/// - ability to accurately measure a timing side-channel on comparisons
/// of the password hash over the network
///
/// An attacker with the above is able to perform an offline computation of
/// the hash for any chosen password in such a way that it will match the
/// hash computed by the server.
///
/// As noted above, they also measure timing variability in the server's
/// comparison of the hash it computes for a given password and a target hash
/// the attacker is trying to learn.
///
/// When the attacker observes a hash comparison that takes longer than their
/// previous attempts, they learn that they guessed another byte in the
/// password hash correctly. They can leverage repeated measurements and
/// observations with different candidate passwords to learn the password
/// hash a byte-at-a-time in a manner similar to other such timing side-channel
/// attacks.
///
/// The attack may seem somewhat counterintuitive since learning prefixes of a
/// password hash does not reveal any additional information about the password
/// itself. However, the above can be combined with an offline dictionary
/// attack where the attacker is able to determine candidate passwords to send
/// to the server by performing a brute force search offline and selecting
/// candidate passwords whose hashes match the portion of the prefix they have
/// learned so far.
///
/// As the attacker learns a longer and longer prefix of the password hash,
/// they are able to more effectively eliminate candidate passwords offline as
/// part of a dictionary attack, until they eventually guess the correct
/// password or exhaust their set of candidate passwords.
///
/// ## Mitigations
/// While we have taken care to ensure password hashes are compared in constant
/// time, we would also suggest preventing such attacks by using randomly
/// generated salts and keeping those salts secret.
///
/// The [`SaltString::generate`][`crate::SaltString::generate`] function can be
/// used to generate random high-entropy salt values.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
/// [3]: https://web.archive.org/web/20130208100210/http://security-assessment.com/files/documents/presentations/TimingAttackPresentation2012.pdf
#[derive(Copy, Clone, Eq)]
pub struct Output {
/// Byte array containing a password hashing function output.
bytes: [u8; Self::MAX_LENGTH],
/// Length of the password hashing function output in bytes.
length: u8,
/// Encoding which output should be serialized with.
encoding: Encoding,
}
#[allow(clippy::len_without_is_empty)]
impl Output {
/// Minimum length of a [`Output`] string: 10-bytes.
pub const MIN_LENGTH: usize = 10;
/// Maximum length of [`Output`] string: 64-bytes.
///
/// See type-level documentation about [`Output`] for more information.
pub const MAX_LENGTH: usize = 64;
/// Maximum length of [`Output`] when encoded as B64 string: 86-bytes
/// (i.e. 86 ASCII characters)
pub const B64_MAX_LENGTH: usize = ((Self::MAX_LENGTH * 4) / 3) + 1;
/// Create a [`Output`] from the given byte slice, validating it according
/// to [`Output::MIN_LENGTH`] and [`Output::MAX_LENGTH`] restrictions.
pub fn new(input: &[u8]) -> Result<Self> {
Self::init_with(input.len(), |bytes| {
bytes.copy_from_slice(input);
Ok(())
})
}
/// Create a [`Output`] from the given byte slice and [`Encoding`],
/// validating it according to [`Output::MIN_LENGTH`] and
/// [`Output::MAX_LENGTH`] restrictions.
pub fn new_with_encoding(input: &[u8], encoding: Encoding) -> Result<Self> {
let mut result = Self::new(input)?;
result.encoding = encoding;
Ok(result)
}
/// Initialize an [`Output`] using the provided method, which is given
/// a mutable byte slice into which it should write the output.
///
/// The `output_size` (in bytes) must be known in advance, as well as at
/// least [`Output::MIN_LENGTH`] bytes and at most [`Output::MAX_LENGTH`]
/// bytes.
pub fn init_with<F>(output_size: usize, f: F) -> Result<Self>
where
F: FnOnce(&mut [u8]) -> Result<()>,
{
if output_size < Self::MIN_LENGTH {
return Err(Error::OutputSize {
provided: Ordering::Less,
expected: Self::MIN_LENGTH,
});
}
if output_size > Self::MAX_LENGTH {
return Err(Error::OutputSize {
provided: Ordering::Greater,
expected: Self::MAX_LENGTH,
});
}
let mut bytes = [0u8; Self::MAX_LENGTH];
f(&mut bytes[..output_size])?;
Ok(Self {
bytes,
length: output_size as u8,
encoding: Encoding::default(),
})
}
/// Borrow the output value as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..self.len()]
}
/// Get the [`Encoding`] that this [`Output`] is serialized with.
pub fn encoding(&self) -> Encoding {
self.encoding
}
/// Get the length of the output value as a byte slice.
pub fn len(&self) -> usize {
usize::from(self.length)
}
/// Parse B64-encoded [`Output`], i.e. using the PHC string
/// specification's restricted interpretation of Base64.
pub fn b64_decode(input: &str) -> Result<Self> {
Self::decode(input, Encoding::B64)
}
/// Write B64-encoded [`Output`] to the provided buffer, returning
/// a sub-slice containing the encoded data.
///
/// Returns an error if the buffer is too short to contain the output.
pub fn b64_encode<'a>(&self, out: &'a mut [u8]) -> Result<&'a str> {
self.encode(out, Encoding::B64)
}
/// Decode the given input string using the specified [`Encoding`].
pub fn decode(input: &str, encoding: Encoding) -> Result<Self> {
let mut bytes = [0u8; Self::MAX_LENGTH];
encoding
.decode(input, &mut bytes)
.map_err(Into::into)
.and_then(|decoded| Self::new_with_encoding(decoded, encoding))
}
/// Encode this [`Output`] using the specified [`Encoding`].
pub fn encode<'a>(&self, out: &'a mut [u8], encoding: Encoding) -> Result<&'a str> {
Ok(encoding.encode(self.as_ref(), out)?)
}
/// Get the length of this [`Output`] when encoded as B64.
pub fn b64_len(&self) -> usize {
Encoding::B64.encoded_len(self.as_ref())
}
}
impl AsRef<[u8]> for Output {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl ConstantTimeEq for Output {
fn ct_eq(&self, other: &Self) -> Choice {
self.as_ref().ct_eq(other.as_ref())
}
}
impl FromStr for Output {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Self::b64_decode(s)
}
}
impl PartialEq for Output {
fn eq(&self, other: &Self) -> bool {
self.ct_eq(other).into()
}
}
impl TryFrom<&[u8]> for Output {
type Error = Error;
fn try_from(input: &[u8]) -> Result<Output> {
Self::new(input)
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut buffer = [0u8; Self::B64_MAX_LENGTH];
self.encode(&mut buffer, self.encoding)
.map_err(|_| fmt::Error)
.and_then(|encoded| f.write_str(encoded))
}
}
impl fmt::Debug for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Output(\"{}\")", self)
}
}
#[cfg(test)]
mod tests {
use super::{Error, Ordering, Output};
#[test]
fn new_with_valid_min_length_input() {
let bytes = [10u8; 10];
let output = Output::new(&bytes).unwrap();
assert_eq!(output.as_ref(), &bytes);
}
#[test]
fn new_with_valid_max_length_input() {
let bytes = [64u8; 64];
let output = Output::new(&bytes).unwrap();
assert_eq!(output.as_ref(), &bytes);
}
#[test]
fn reject_new_too_short() {
let bytes = [9u8; 9];
let err = Output::new(&bytes).err().unwrap();
assert_eq!(
err,
Error::OutputSize {
provided: Ordering::Less,
expected: Output::MIN_LENGTH
}
);
}
#[test]
fn reject_new_too_long() {
let bytes = [65u8; 65];
let err = Output::new(&bytes).err().unwrap();
assert_eq!(
err,
Error::OutputSize {
provided: Ordering::Greater,
expected: Output::MAX_LENGTH
}
);
}
#[test]
fn partialeq_true() {
let a = Output::new(&[1u8; 32]).unwrap();
let b = Output::new(&[1u8; 32]).unwrap();
assert_eq!(a, b);
}
#[test]
fn partialeq_false() {
let a = Output::new(&[1u8; 32]).unwrap();
let b = Output::new(&[2u8; 32]).unwrap();
assert_ne!(a, b);
}
}

455
vendor/password-hash/src/params.rs vendored Normal file
View File

@@ -0,0 +1,455 @@
//! Algorithm parameters.
use crate::errors::InvalidValue;
use crate::{
value::{Decimal, Value},
Encoding, Error, Ident, Result,
};
use core::{
fmt::{self, Debug, Write},
iter::FromIterator,
str::{self, FromStr},
};
/// Individual parameter name/value pair.
pub type Pair<'a> = (Ident<'a>, Value<'a>);
/// Delimiter character between name/value pairs.
pub(crate) const PAIR_DELIMITER: char = '=';
/// Delimiter character between parameters.
pub(crate) const PARAMS_DELIMITER: char = ',';
/// Maximum number of supported parameters.
const MAX_LENGTH: usize = 127;
/// Error message used with `expect` for when internal invariants are violated
/// (i.e. the contents of a [`ParamsString`] should always be valid)
const INVARIANT_VIOLATED_MSG: &str = "PHC params invariant violated";
/// Algorithm parameter string.
///
/// The [PHC string format specification][1] defines a set of optional
/// algorithm-specific name/value pairs which can be encoded into a
/// PHC-formatted parameter string as follows:
///
/// ```text
/// $<param>=<value>(,<param>=<value>)*
/// ```
///
/// This type represents that set of parameters.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification
#[derive(Clone, Default, Eq, PartialEq)]
pub struct ParamsString(Buffer);
impl ParamsString {
/// Create new empty [`ParamsString`].
pub fn new() -> Self {
Self::default()
}
/// Add the given byte value to the [`ParamsString`], encoding it as "B64".
pub fn add_b64_bytes<'a>(&mut self, name: impl TryInto<Ident<'a>>, bytes: &[u8]) -> Result<()> {
if !self.is_empty() {
self.0
.write_char(PARAMS_DELIMITER)
.map_err(|_| Error::ParamsMaxExceeded)?
}
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
// Add param name
let offset = self.0.length;
if write!(self.0, "{}=", name).is_err() {
self.0.length = offset;
return Err(Error::ParamsMaxExceeded);
}
// Encode B64 value
let offset = self.0.length as usize;
let written = Encoding::B64
.encode(bytes, &mut self.0.bytes[offset..])?
.len();
self.0.length += written as u8;
Ok(())
}
/// Add a key/value pair with a decimal value to the [`ParamsString`].
pub fn add_decimal<'a>(&mut self, name: impl TryInto<Ident<'a>>, value: Decimal) -> Result<()> {
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
self.add(name, value)
}
/// Add a key/value pair with a string value to the [`ParamsString`].
pub fn add_str<'a>(
&mut self,
name: impl TryInto<Ident<'a>>,
value: impl TryInto<Value<'a>>,
) -> Result<()> {
let name = name.try_into().map_err(|_| Error::ParamNameInvalid)?;
let value = value
.try_into()
.map_err(|_| Error::ParamValueInvalid(InvalidValue::InvalidFormat))?;
self.add(name, value)
}
/// Borrow the contents of this [`ParamsString`] as a byte slice.
pub fn as_bytes(&self) -> &[u8] {
self.as_str().as_bytes()
}
/// Borrow the contents of this [`ParamsString`] as a `str`.
pub fn as_str(&self) -> &str {
self.0.as_ref()
}
/// Get the count of the number ASCII characters in this [`ParamsString`].
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Is this set of parameters empty?
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Iterate over the parameters.
pub fn iter(&self) -> Iter<'_> {
Iter::new(self.as_str())
}
/// Get a parameter [`Value`] by name.
pub fn get<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Value<'_>> {
let name = name.try_into().ok()?;
for (n, v) in self.iter() {
if name == n {
return Some(v);
}
}
None
}
/// Get a parameter as a `str`.
pub fn get_str<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<&str> {
self.get(name).map(|value| value.as_str())
}
/// Get a parameter as a [`Decimal`].
///
/// See [`Value::decimal`] for format information.
pub fn get_decimal<'a>(&self, name: impl TryInto<Ident<'a>>) -> Option<Decimal> {
self.get(name).and_then(|value| value.decimal().ok())
}
/// Add a value to this [`ParamsString`] using the provided callback.
fn add(&mut self, name: Ident<'_>, value: impl fmt::Display) -> Result<()> {
if self.get(name).is_some() {
return Err(Error::ParamNameDuplicated);
}
let orig_len = self.0.length;
if !self.is_empty() {
self.0
.write_char(PARAMS_DELIMITER)
.map_err(|_| Error::ParamsMaxExceeded)?
}
if write!(self.0, "{}={}", name, value).is_err() {
self.0.length = orig_len;
return Err(Error::ParamsMaxExceeded);
}
Ok(())
}
}
impl FromStr for ParamsString {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.as_bytes().len() > MAX_LENGTH {
return Err(Error::ParamsMaxExceeded);
}
if s.is_empty() {
return Ok(ParamsString::new());
}
// Validate the string is well-formed
for mut param in s.split(PARAMS_DELIMITER).map(|p| p.split(PAIR_DELIMITER)) {
// Validate name
param
.next()
.ok_or(Error::ParamNameInvalid)
.and_then(Ident::try_from)?;
// Validate value
param
.next()
.ok_or(Error::ParamValueInvalid(InvalidValue::Malformed))
.and_then(Value::try_from)?;
if param.next().is_some() {
return Err(Error::ParamValueInvalid(InvalidValue::Malformed));
}
}
let mut bytes = [0u8; MAX_LENGTH];
bytes[..s.as_bytes().len()].copy_from_slice(s.as_bytes());
Ok(Self(Buffer {
bytes,
length: s.as_bytes().len() as u8,
}))
}
}
impl<'a> FromIterator<Pair<'a>> for ParamsString {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = Pair<'a>>,
{
let mut params = ParamsString::new();
for pair in iter {
params.add_str(pair.0, pair.1).expect("PHC params error");
}
params
}
}
impl fmt::Display for ParamsString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for ParamsString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map().entries(self.iter()).finish()
}
}
/// Iterator over algorithm parameters stored in a [`ParamsString`] struct.
pub struct Iter<'a> {
inner: Option<str::Split<'a, char>>,
}
impl<'a> Iter<'a> {
/// Create a new [`Iter`].
fn new(s: &'a str) -> Self {
if s.is_empty() {
Self { inner: None }
} else {
Self {
inner: Some(s.split(PARAMS_DELIMITER)),
}
}
}
}
impl<'a> Iterator for Iter<'a> {
type Item = Pair<'a>;
fn next(&mut self) -> Option<Pair<'a>> {
let mut param = self.inner.as_mut()?.next()?.split(PAIR_DELIMITER);
let name = param
.next()
.and_then(|id| Ident::try_from(id).ok())
.expect(INVARIANT_VIOLATED_MSG);
let value = param
.next()
.and_then(|value| Value::try_from(value).ok())
.expect(INVARIANT_VIOLATED_MSG);
debug_assert_eq!(param.next(), None);
Some((name, value))
}
}
/// Parameter buffer.
#[derive(Clone, Debug, Eq)]
struct Buffer {
/// Byte array containing an ASCII-encoded string.
bytes: [u8; MAX_LENGTH],
/// Length of the string in ASCII characters (i.e. bytes).
length: u8,
}
impl AsRef<str> for Buffer {
fn as_ref(&self) -> &str {
str::from_utf8(&self.bytes[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG)
}
}
impl Default for Buffer {
fn default() -> Buffer {
Buffer {
bytes: [0u8; MAX_LENGTH],
length: 0,
}
}
}
impl PartialEq for Buffer {
fn eq(&self, other: &Self) -> bool {
// Ensure comparisons always honor the initialized portion of the buffer
self.as_ref().eq(other.as_ref())
}
}
impl Write for Buffer {
fn write_str(&mut self, input: &str) -> fmt::Result {
let bytes = input.as_bytes();
let length = self.length as usize;
if length + bytes.len() > MAX_LENGTH {
return Err(fmt::Error);
}
self.bytes[length..(length + bytes.len())].copy_from_slice(bytes);
self.length += bytes.len() as u8;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::{Error, FromIterator, Ident, ParamsString, Value};
#[cfg(feature = "alloc")]
use alloc::string::ToString;
use core::str::FromStr;
#[test]
fn add() {
let mut params = ParamsString::new();
params.add_str("a", "1").unwrap();
params.add_decimal("b", 2).unwrap();
params.add_str("c", "3").unwrap();
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
#[test]
#[cfg(feature = "alloc")]
fn add_b64_bytes() {
let mut params = ParamsString::new();
params.add_b64_bytes("a", &[1]).unwrap();
params.add_b64_bytes("b", &[2, 3]).unwrap();
params.add_b64_bytes("c", &[4, 5, 6]).unwrap();
assert_eq!(params.to_string(), "a=AQ,b=AgM,c=BAUG");
}
#[test]
fn duplicate_names() {
let name = Ident::new("a").unwrap();
let mut params = ParamsString::new();
params.add_decimal(name, 1).unwrap();
let err = params.add_decimal(name, 2u32.into()).err().unwrap();
assert_eq!(err, Error::ParamNameDuplicated);
}
#[test]
fn from_iter() {
let params = ParamsString::from_iter(
[
(Ident::new("a").unwrap(), Value::try_from("1").unwrap()),
(Ident::new("b").unwrap(), Value::try_from("2").unwrap()),
(Ident::new("c").unwrap(), Value::try_from("3").unwrap()),
]
.iter()
.cloned(),
);
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
#[test]
fn iter() {
let mut params = ParamsString::new();
params.add_str("a", "1").unwrap();
params.add_str("b", "2").unwrap();
params.add_str("c", "3").unwrap();
let mut i = params.iter();
for (name, value) in &[("a", "1"), ("b", "2"), ("c", "3")] {
let name = Ident::new(name).unwrap();
let value = Value::try_from(*value).unwrap();
assert_eq!(i.next(), Some((name, value)));
}
assert_eq!(i.next(), None);
}
//
// `FromStr` tests
//
#[test]
fn parse_empty() {
let params = ParamsString::from_str("").unwrap();
assert!(params.is_empty());
}
#[test]
fn parse_one() {
let params = ParamsString::from_str("a=1").unwrap();
assert_eq!(params.iter().count(), 1);
assert_eq!(params.get("a").unwrap().decimal().unwrap(), 1);
}
#[test]
fn parse_many() {
let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
assert_eq!(params.iter().count(), 3);
assert_eq!(params.get_decimal("a").unwrap(), 1);
assert_eq!(params.get_decimal("b").unwrap(), 2);
assert_eq!(params.get_decimal("c").unwrap(), 3);
}
//
// `Display` tests
//
#[test]
#[cfg(feature = "alloc")]
fn display_empty() {
let params = ParamsString::new();
assert_eq!(params.to_string(), "");
}
#[test]
#[cfg(feature = "alloc")]
fn display_one() {
let params = ParamsString::from_str("a=1").unwrap();
assert_eq!(params.to_string(), "a=1");
}
#[test]
#[cfg(feature = "alloc")]
fn display_many() {
let params = ParamsString::from_str("a=1,b=2,c=3").unwrap();
assert_eq!(params.to_string(), "a=1,b=2,c=3");
}
}

354
vendor/password-hash/src/salt.rs vendored Normal file
View File

@@ -0,0 +1,354 @@
//! Salt string support.
use crate::{Encoding, Error, Result, Value};
use core::{fmt, str};
use crate::errors::InvalidValue;
#[cfg(feature = "rand_core")]
use rand_core::CryptoRngCore;
/// Error message used with `expect` for when internal invariants are violated
/// (i.e. the contents of a [`Salt`] should always be valid)
const INVARIANT_VIOLATED_MSG: &str = "salt string invariant violated";
/// Salt string.
///
/// In password hashing, a "salt" is an additional value used to
/// personalize/tweak the output of a password hashing function for a given
/// input password.
///
/// Salts help defend against attacks based on precomputed tables of hashed
/// passwords, i.e. "[rainbow tables][1]".
///
/// The [`Salt`] type implements the RECOMMENDED best practices for salts
/// described in the [PHC string format specification][2], namely:
///
/// > - Maximum lengths for salt, output and parameter values are meant to help
/// > consumer implementations, in particular written in C and using
/// > stack-allocated buffers. These buffers must account for the worst case,
/// > i.e. the maximum defined length. Therefore, keep these lengths low.
/// > - The role of salts is to achieve uniqueness. A random salt is fine for
/// > that as long as its length is sufficient; a 16-byte salt would work well
/// > (by definition, UUID are very good salts, and they encode over exactly
/// > 16 bytes). 16 bytes encode as 22 characters in B64. Functions should
/// > disallow salt values that are too small for security (4 bytes should be
/// > viewed as an absolute minimum).
///
/// # Recommended length
/// The recommended default length for a salt string is **16-bytes** (128-bits).
///
/// See [`Salt::RECOMMENDED_LENGTH`] for more information.
///
/// # Constraints
/// Salt strings are constrained to the following set of characters per the
/// PHC spec:
///
/// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]`
/// > (lowercase letters, uppercase letters, digits, /, +, . and -).
///
/// Additionally the following length restrictions are enforced based on the
/// guidelines from the spec:
///
/// - Minimum length: **4**-bytes
/// - Maximum length: **64**-bytes
///
/// A maximum length is enforced based on the above recommendation for
/// supporting stack-allocated buffers (which this library uses), and the
/// specific determination of 64-bytes is taken as a best practice from the
/// [Argon2 Encoding][3] specification in the same document:
///
/// > The length in bytes of the salt is between 8 and 64 bytes<sup>†</sup>, thus
/// > yielding a length in characters between 11 and 64 characters (and that
/// > length is never equal to 1 modulo 4). The default byte length of the salt
/// > is 16 bytes (22 characters in B64 encoding). An encoded UUID, or a
/// > sequence of 16 bytes produced with a cryptographically strong PRNG, are
/// > appropriate salt values.
/// >
/// > <sup>†</sup>The Argon2 specification states that the salt can be much longer, up
/// > to 2^32-1 bytes, but this makes little sense for password hashing.
/// > Specifying a relatively small maximum length allows for parsing with a
/// > stack allocated buffer.)
///
/// Based on this guidance, this type enforces an upper bound of 64-bytes
/// as a reasonable maximum, and recommends using 16-bytes.
///
/// [1]: https://en.wikipedia.org/wiki/Rainbow_table
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
/// [3]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
#[derive(Copy, Clone, Eq, PartialEq)]
pub struct Salt<'a>(Value<'a>);
#[allow(clippy::len_without_is_empty)]
impl<'a> Salt<'a> {
/// Minimum length of a [`Salt`] string: 4-bytes.
pub const MIN_LENGTH: usize = 4;
/// Maximum length of a [`Salt`] string: 64-bytes.
///
/// See type-level documentation about [`Salt`] for more information.
pub const MAX_LENGTH: usize = 64;
/// Recommended length of a salt: 16-bytes.
///
/// This recommendation comes from the [PHC string format specification]:
///
/// > The role of salts is to achieve uniqueness. A *random* salt is fine
/// > for that as long as its length is sufficient; a 16-byte salt would
/// > work well (by definition, UUID are very good salts, and they encode
/// > over exactly 16 bytes). 16 bytes encode as 22 characters in B64.
///
/// [PHC string format specification]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#function-duties
pub const RECOMMENDED_LENGTH: usize = 16;
/// Create a [`Salt`] from the given B64-encoded input string, validating
/// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
pub fn from_b64(input: &'a str) -> Result<Self> {
let length = input.as_bytes().len();
if length < Self::MIN_LENGTH {
return Err(Error::SaltInvalid(InvalidValue::TooShort));
}
if length > Self::MAX_LENGTH {
return Err(Error::SaltInvalid(InvalidValue::TooLong));
}
// TODO(tarcieri): full B64 decoding check?
for char in input.chars() {
// From the PHC string format spec:
//
// > The salt consists in a sequence of characters in: `[a-zA-Z0-9/+.-]`
// > (lowercase letters, uppercase letters, digits, /, +, . and -).
if !matches!(char, 'a'..='z' | 'A'..='Z' | '0'..='9' | '/' | '+' | '.' | '-') {
return Err(Error::SaltInvalid(InvalidValue::InvalidChar(char)));
}
}
input.try_into().map(Self).map_err(|e| match e {
Error::ParamValueInvalid(value_err) => Error::SaltInvalid(value_err),
err => err,
})
}
/// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the
/// decoded output into the provided buffer, and returning a slice of the
/// portion of the buffer containing the decoded result on success.
pub fn decode_b64<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
self.0.b64_decode(buf)
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &'a str {
self.0.as_str()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Create a [`Salt`] from the given B64-encoded input string, validating
/// [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
#[deprecated(since = "0.5.0", note = "use `from_b64` instead")]
pub fn new(input: &'a str) -> Result<Self> {
Self::from_b64(input)
}
/// Attempt to decode a B64-encoded [`Salt`] into bytes, writing the
/// decoded output into the provided buffer, and returning a slice of the
/// portion of the buffer containing the decoded result on success.
#[deprecated(since = "0.5.0", note = "use `decode_b64` instead")]
pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
self.decode_b64(buf)
}
}
impl<'a> AsRef<str> for Salt<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> TryFrom<&'a str> for Salt<'a> {
type Error = Error;
fn try_from(input: &'a str) -> Result<Self> {
Self::from_b64(input)
}
}
impl<'a> fmt::Display for Salt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl<'a> fmt::Debug for Salt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Salt({:?})", self.as_str())
}
}
/// Owned stack-allocated equivalent of [`Salt`].
#[derive(Clone, Eq)]
pub struct SaltString {
/// ASCII-encoded characters which comprise the salt.
chars: [u8; Salt::MAX_LENGTH],
/// Length of the string in ASCII characters (i.e. bytes).
length: u8,
}
#[allow(clippy::len_without_is_empty)]
impl SaltString {
/// Generate a random B64-encoded [`SaltString`].
#[cfg(feature = "rand_core")]
pub fn generate(mut rng: impl CryptoRngCore) -> Self {
let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
rng.fill_bytes(&mut bytes);
Self::encode_b64(&bytes).expect(INVARIANT_VIOLATED_MSG)
}
/// Create a new [`SaltString`] from the given B64-encoded input string,
/// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
pub fn from_b64(s: &str) -> Result<Self> {
// Assert `s` parses successfully as a `Salt`
Salt::from_b64(s)?;
let len = s.as_bytes().len();
let mut bytes = [0u8; Salt::MAX_LENGTH];
bytes[..len].copy_from_slice(s.as_bytes());
Ok(SaltString {
chars: bytes,
length: len as u8, // `Salt::from_b64` check prevents overflow
})
}
/// Decode this [`SaltString`] from B64 into the provided output buffer.
pub fn decode_b64<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> {
self.as_salt().decode_b64(buf)
}
/// Encode the given byte slice as B64 into a new [`SaltString`].
///
/// Returns `Error` if the slice is too long.
pub fn encode_b64(input: &[u8]) -> Result<Self> {
let mut bytes = [0u8; Salt::MAX_LENGTH];
let length = Encoding::B64.encode(input, &mut bytes)?.len() as u8;
Ok(Self {
chars: bytes,
length,
})
}
/// Borrow the contents of a [`SaltString`] as a [`Salt`].
pub fn as_salt(&self) -> Salt<'_> {
Salt::from_b64(self.as_str()).expect(INVARIANT_VIOLATED_MSG)
}
/// Borrow the contents of a [`SaltString`] as a `str`.
pub fn as_str(&self) -> &str {
str::from_utf8(&self.chars[..(self.length as usize)]).expect(INVARIANT_VIOLATED_MSG)
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Create a new [`SaltString`] from the given B64-encoded input string,
/// validating [`Salt::MIN_LENGTH`] and [`Salt::MAX_LENGTH`] restrictions.
#[deprecated(since = "0.5.0", note = "use `from_b64` instead")]
pub fn new(s: &str) -> Result<Self> {
Self::from_b64(s)
}
/// Decode this [`SaltString`] from B64 into the provided output buffer.
#[deprecated(since = "0.5.0", note = "use `decode_b64` instead")]
pub fn b64_decode<'a>(&self, buf: &'a mut [u8]) -> Result<&'a [u8]> {
self.decode_b64(buf)
}
/// Encode the given byte slice as B64 into a new [`SaltString`].
///
/// Returns `Error` if the slice is too long.
#[deprecated(since = "0.5.0", note = "use `encode_b64` instead")]
pub fn b64_encode(input: &[u8]) -> Result<Self> {
Self::encode_b64(input)
}
}
impl AsRef<str> for SaltString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq for SaltString {
fn eq(&self, other: &Self) -> bool {
// Ensure comparisons always honor the initialized portion of the buffer
self.as_ref().eq(other.as_ref())
}
}
impl<'a> From<&'a SaltString> for Salt<'a> {
fn from(salt_string: &'a SaltString) -> Salt<'a> {
salt_string.as_salt()
}
}
impl fmt::Display for SaltString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl fmt::Debug for SaltString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SaltString({:?})", self.as_str())
}
}
#[cfg(test)]
mod tests {
use super::{Error, Salt};
use crate::errors::InvalidValue;
#[test]
fn new_with_valid_min_length_input() {
let s = "abcd";
let salt = Salt::from_b64(s).unwrap();
assert_eq!(salt.as_ref(), s);
}
#[test]
fn new_with_valid_max_length_input() {
let s = "012345678911234567892123456789312345678941234567";
let salt = Salt::from_b64(s).unwrap();
assert_eq!(salt.as_ref(), s);
}
#[test]
fn reject_new_too_short() {
for &too_short in &["", "a", "ab", "abc"] {
let err = Salt::from_b64(too_short).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::TooShort));
}
}
#[test]
fn reject_new_too_long() {
let s = "01234567891123456789212345678931234567894123456785234567896234567";
let err = Salt::from_b64(s).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::TooLong));
}
#[test]
fn reject_new_invalid_char() {
let s = "01234_abcd";
let err = Salt::from_b64(s).err().unwrap();
assert_eq!(err, Error::SaltInvalid(InvalidValue::InvalidChar('_')));
}
}

101
vendor/password-hash/src/traits.rs vendored Normal file
View File

@@ -0,0 +1,101 @@
//! Trait definitions.
use crate::{Decimal, Error, Ident, ParamsString, PasswordHash, Result, Salt};
use core::fmt::Debug;
/// Trait for password hashing functions.
pub trait PasswordHasher {
/// Algorithm-specific parameters.
type Params: Clone
+ Debug
+ Default
+ for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error>
+ TryInto<ParamsString, Error = Error>;
/// Compute a [`PasswordHash`] from the provided password using an
/// explicit set of customized algorithm parameters as opposed to the
/// defaults.
///
/// When in doubt, use [`PasswordHasher::hash_password`] instead.
fn hash_password_customized<'a>(
&self,
password: &[u8],
algorithm: Option<Ident<'a>>,
version: Option<Decimal>,
params: Self::Params,
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>>;
/// Simple API for computing a [`PasswordHash`] from a password and
/// salt value.
///
/// Uses the default recommended parameters for a given algorithm.
fn hash_password<'a>(
&self,
password: &[u8],
salt: impl Into<Salt<'a>>,
) -> Result<PasswordHash<'a>> {
self.hash_password_customized(password, None, None, Self::Params::default(), salt)
}
}
/// Trait for password verification.
///
/// Automatically impl'd for any type that impls [`PasswordHasher`].
///
/// This trait is object safe and can be used to implement abstractions over
/// multiple password hashing algorithms. One such abstraction is provided by
/// the [`PasswordHash::verify_password`] method.
pub trait PasswordVerifier {
/// Compute this password hashing function against the provided password
/// using the parameters from the provided password hash and see if the
/// computed output matches.
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>;
}
impl<T: PasswordHasher> PasswordVerifier for T {
fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> {
if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) {
let computed_hash = self.hash_password_customized(
password,
Some(hash.algorithm),
hash.version,
T::Params::try_from(hash)?,
*salt,
)?;
if let Some(computed_output) = &computed_hash.hash {
// See notes on `Output` about the use of a constant-time comparison
if expected_output == computed_output {
return Ok(());
}
}
}
Err(Error::Password)
}
}
/// Trait for password hashing algorithms which support the legacy
/// [Modular Crypt Format (MCF)][MCF].
///
/// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html
pub trait McfHasher {
/// Upgrade an MCF hash to a PHC hash. MCF follow this rough format:
///
/// ```text
/// $<id>$<content>
/// ```
///
/// MCF hashes are otherwise largely unstructured and parsed according to
/// algorithm-specific rules so hashers must parse a raw string themselves.
fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>;
/// Verify a password hash in MCF format against the provided password.
fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()>
where
Self: PasswordVerifier,
{
self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?)
}
}

304
vendor/password-hash/src/value.rs vendored Normal file
View File

@@ -0,0 +1,304 @@
//! Algorithm parameter value as defined by the [PHC string format].
//!
//! Implements the following parts of the specification:
//!
//! > The value for each parameter consists in characters in: `[a-zA-Z0-9/+.-]`
//! > (lowercase letters, uppercase letters, digits, /, +, . and -). No other
//! > character is allowed. Interpretation of the value depends on the
//! > parameter and the function. The function specification MUST unambiguously
//! > define the set of valid parameter values. The function specification MUST
//! > define a maximum length (in characters) for each parameter. For numerical
//! > parameters, functions SHOULD use plain decimal encoding (other encodings
//! > are possible as long as they are clearly defined).
//!
//! [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
use crate::errors::InvalidValue;
use crate::{Encoding, Error, Result};
use core::{fmt, str};
/// Type used to represent decimal (i.e. integer) values.
pub type Decimal = u32;
/// Algorithm parameter value string.
///
/// Parameter values are defined in the [PHC string format specification][1].
///
/// # Constraints
/// - ASCII-encoded string consisting of the characters `[a-zA-Z0-9/+.-]`
/// (lowercase letters, digits, and the minus sign)
/// - Minimum length: 0 (i.e. empty values are allowed)
/// - Maximum length: 64 ASCII characters (i.e. 64-bytes)
///
/// # Additional Notes
/// The PHC spec allows for algorithm-defined maximum lengths for parameter
/// values, however this library defines a [`Value::MAX_LENGTH`] of 64 ASCII
/// characters.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md
/// [2]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub struct Value<'a>(&'a str);
impl<'a> Value<'a> {
/// Maximum length of an [`Value`] - 64 ASCII characters (i.e. 64-bytes).
///
/// This value is selected to match the maximum length of a [`Salt`][`crate::Salt`]
/// as this library internally uses this type to represent salts.
pub const MAX_LENGTH: usize = 64;
/// Parse a [`Value`] from the provided `str`, validating it according to
/// the PHC string format's rules.
pub fn new(input: &'a str) -> Result<Self> {
if input.as_bytes().len() > Self::MAX_LENGTH {
return Err(Error::ParamValueInvalid(InvalidValue::TooLong));
}
// Check that the characters are permitted in a PHC parameter value.
assert_valid_value(input)?;
Ok(Self(input))
}
/// Attempt to decode a B64-encoded [`Value`], writing the decoded
/// result into the provided buffer, and returning a slice of the buffer
/// containing the decoded result on success.
///
/// Examples of "B64"-encoded parameters in practice are the `keyid` and
/// `data` parameters used by the [Argon2 Encoding][1] as described in the
/// PHC string format specification.
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#argon2-encoding
pub fn b64_decode<'b>(&self, buf: &'b mut [u8]) -> Result<&'b [u8]> {
Ok(Encoding::B64.decode(self.as_str(), buf)?)
}
/// Borrow this value as a `str`.
pub fn as_str(&self) -> &'a str {
self.0
}
/// Borrow this value as bytes.
pub fn as_bytes(&self) -> &'a [u8] {
self.as_str().as_bytes()
}
/// Get the length of this value in ASCII characters.
pub fn len(&self) -> usize {
self.as_str().len()
}
/// Is this value empty?
pub fn is_empty(&self) -> bool {
self.as_str().is_empty()
}
/// Attempt to parse this [`Value`] as a PHC-encoded decimal (i.e. integer).
///
/// Decimal values are integers which follow the rules given in the
/// ["Decimal Encoding" section of the PHC string format specification][1].
///
/// The decimal encoding rules are as follows:
/// > For an integer value x, its decimal encoding consist in the following:
/// >
/// > - If x < 0, then its decimal encoding is the minus sign - followed by the decimal
/// > encoding of -x.
/// > - If x = 0, then its decimal encoding is the single character 0.
/// > - If x > 0, then its decimal encoding is the smallest sequence of ASCII digits that
/// > matches its value (i.e. there is no leading zero).
/// >
/// > Thus, a value is a valid decimal for an integer x if and only if all of the following hold true:
/// >
/// > - The first character is either a - sign, or an ASCII digit.
/// > - All characters other than the first are ASCII digits.
/// > - If the first character is - sign, then there is at least another character, and the
/// > second character is not a 0.
/// > - If the string consists in more than one character, then the first one cannot be a 0.
///
/// Note: this implementation does not support negative decimals despite
/// them being allowed per the spec above. If you need to parse a negative
/// number, please parse it from the string representation directly e.g.
/// `value.as_str().parse::<i32>()`
///
/// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#decimal-encoding
pub fn decimal(&self) -> Result<Decimal> {
let value = self.as_str();
// Empty strings aren't decimals
if value.is_empty() {
return Err(Error::ParamValueInvalid(InvalidValue::Malformed));
}
// Ensure all characters are digits
for c in value.chars() {
if !c.is_ascii_digit() {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
}
}
// Disallow leading zeroes
if value.starts_with('0') && value.len() > 1 {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidFormat));
}
value.parse().map_err(|_| {
// In theory a value overflow should be the only potential error here.
// When `ParseIntError::kind` is stable it might be good to double check:
// <https://github.com/rust-lang/rust/issues/22639>
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
})
}
/// Does this value parse successfully as a decimal?
pub fn is_decimal(&self) -> bool {
self.decimal().is_ok()
}
}
impl<'a> AsRef<str> for Value<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> TryFrom<&'a str> for Value<'a> {
type Error = Error;
fn try_from(input: &'a str) -> Result<Self> {
Self::new(input)
}
}
impl<'a> TryFrom<Value<'a>> for Decimal {
type Error = Error;
fn try_from(value: Value<'a>) -> Result<Decimal> {
Decimal::try_from(&value)
}
}
impl<'a> TryFrom<&Value<'a>> for Decimal {
type Error = Error;
fn try_from(value: &Value<'a>) -> Result<Decimal> {
value.decimal()
}
}
impl<'a> fmt::Display for Value<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
/// Are all of the given bytes allowed in a [`Value`]?
fn assert_valid_value(input: &str) -> Result<()> {
for c in input.chars() {
if !is_char_valid(c) {
return Err(Error::ParamValueInvalid(InvalidValue::InvalidChar(c)));
}
}
Ok(())
}
/// Ensure the given ASCII character (i.e. byte) is allowed in a [`Value`].
fn is_char_valid(c: char) -> bool {
matches!(c, 'A' ..= 'Z' | 'a'..='z' | '0'..='9' | '/' | '+' | '.' | '-')
}
#[cfg(test)]
mod tests {
use super::{Error, InvalidValue, Value};
// Invalid value examples
const INVALID_CHAR: &str = "x;y";
const INVALID_TOO_LONG: &str =
"01234567891123456789212345678931234567894123456785234567896234567";
const INVALID_CHAR_AND_TOO_LONG: &str =
"0!234567891123456789212345678931234567894123456785234567896234567";
//
// Decimal parsing tests
//
#[test]
fn decimal_value() {
let valid_decimals = &[("0", 0u32), ("1", 1u32), ("4294967295", u32::MAX)];
for &(s, i) in valid_decimals {
let value = Value::new(s).unwrap();
assert!(value.is_decimal());
assert_eq!(value.decimal().unwrap(), i)
}
}
#[test]
fn reject_decimal_with_leading_zero() {
let value = Value::new("01").unwrap();
let err = u32::try_from(value).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidFormat)
));
}
#[test]
fn reject_overlong_decimal() {
let value = Value::new("4294967296").unwrap();
let err = u32::try_from(value).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::InvalidFormat));
}
#[test]
fn reject_negative() {
let value = Value::new("-1").unwrap();
let err = u32::try_from(value).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
));
}
//
// String parsing tests
//
#[test]
fn string_value() {
let valid_examples = [
"",
"X",
"x",
"xXx",
"a+b.c-d",
"1/2",
"01234567891123456789212345678931",
];
for &example in &valid_examples {
let value = Value::new(example).unwrap();
assert_eq!(value.as_str(), example);
}
}
#[test]
fn reject_invalid_char() {
let err = Value::new(INVALID_CHAR).err().unwrap();
assert!(matches!(
err,
Error::ParamValueInvalid(InvalidValue::InvalidChar(_))
));
}
#[test]
fn reject_too_long() {
let err = Value::new(INVALID_TOO_LONG).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
}
#[test]
fn reject_invalid_char_and_too_long() {
let err = Value::new(INVALID_CHAR_AND_TOO_LONG).err().unwrap();
assert_eq!(err, Error::ParamValueInvalid(InvalidValue::TooLong));
}
}