Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 98 additions & 85 deletions mcf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ pub use error::{Error, Result};
pub use fields::{Field, Fields};

#[cfg(feature = "alloc")]
use {
alloc::string::String,
core::{fmt, str},
};
pub use allocating::McfHash;

/// Debug message used in panics when invariants aren't properly held.
const INVARIANT_MSG: &str = "should be ensured valid by constructor";
Expand Down Expand Up @@ -72,110 +69,126 @@ impl<'a> McfHashRef<'a> {
}
}

/// Modular Crypt Format (MCF) serialized password hash.
///
/// Password hashes in this format take the form `${id}$...`, where `{id}` is a short numeric or
/// alphanumeric algorithm identifier optionally containing a `-`, followed by `$` as a delimiter,
/// further followed by an algorithm-specific serialization of a password hash, typically
/// using a variant (often an algorithm-specific variant) of Base64. This algorithm-specific
/// serialization contains one or more fields `${first}[${second}]...`, where each field only uses
/// characters in the regexp range `[A-Za-z0-9./+=,\-]`.
///
/// Example (SHA-crypt w\ SHA-512):
///
/// ```text
/// $6$rounds=100000$exn6tVc2j/MZD8uG$BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0
/// ```
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct McfHash(String);

#[cfg(feature = "alloc")]
impl McfHash {
/// Parse the given input string, returning an [`McfHash`] if valid.
pub fn new(s: impl Into<String>) -> Result<McfHash> {
let s = s.into();
validate(&s)?;
Ok(Self(s))
}
mod allocating {
use crate::{Base64, Error, Field, Fields, McfHashRef, Result, fields, validate, validate_id};
use alloc::string::String;
use core::{fmt, str};

/// Create an [`McfHash`] from an identifier.
/// Modular Crypt Format (MCF) serialized password hash.
///
/// # Returns
/// Password hashes in this format take the form `${id}$...`, where `{id}` is a short numeric or
/// alphanumeric algorithm identifier optionally containing a `-`, followed by `$` as a delimiter,
/// further followed by an algorithm-specific serialization of a password hash, typically
/// using a variant (often an algorithm-specific variant) of Base64. This algorithm-specific
/// serialization contains one or more fields `${first}[${second}]...`, where each field only uses
/// characters in the regexp range `[A-Za-z0-9./+=,\-]`.
///
/// Error if the identifier is invalid.
/// Example (SHA-crypt w\ SHA-512):
///
/// Allowed characters match the regex: `[a-z0-9\-]`, where the first and last characters do NOT
/// contain a `-`.
pub fn from_id(id: &str) -> Result<McfHash> {
validate_id(id)?;
/// ```text
/// $6$rounds=100000$exn6tVc2j/MZD8uG$BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0
/// ```
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct McfHash(String);

impl McfHash {
/// Parse the given input string, returning an [`McfHash`] if valid.
pub fn new(s: impl Into<String>) -> Result<McfHash> {
let s = s.into();
validate(&s)?;
Ok(Self(s))
}

let mut hash = String::with_capacity(1 + id.len());
hash.push(fields::DELIMITER);
hash.push_str(id);
Ok(Self(hash))
}
/// Create an [`McfHash`] from an identifier.
///
/// # Returns
///
/// Error if the identifier is invalid.
///
/// Allowed characters match the regex: `[a-z0-9\-]`, where the first and last characters do NOT
/// contain a `-`.
pub fn from_id(id: &str) -> Result<McfHash> {
validate_id(id)?;

let mut hash = String::with_capacity(1 + id.len());
hash.push(fields::DELIMITER);
hash.push_str(id);
Ok(Self(hash))
}

/// Get the contained string as a `str`.
pub fn as_str(&self) -> &str {
&self.0
}
/// Get the contained string as a `str`.
pub fn as_str(&self) -> &str {
&self.0
}

/// Get an [`McfHashRef`] which corresponds to this owned [`McfHash`].
pub fn as_mcf_hash_ref(&self) -> McfHashRef<'_> {
McfHashRef(self.as_str())
}
/// Get an [`McfHashRef`] which corresponds to this owned [`McfHash`].
pub fn as_mcf_hash_ref(&self) -> McfHashRef<'_> {
McfHashRef(self.as_str())
}

/// Get the algorithm identifier for this MCF hash.
pub fn id(&self) -> &str {
self.as_mcf_hash_ref().id()
}
/// Get the algorithm identifier for this MCF hash.
pub fn id(&self) -> &str {
self.as_mcf_hash_ref().id()
}

/// Get an iterator over the parts of the password hash as delimited by `$`, excluding the
/// initial identifier.
pub fn fields(&self) -> Fields<'_> {
self.as_mcf_hash_ref().fields()
/// Get an iterator over the parts of the password hash as delimited by `$`, excluding the
/// initial identifier.
pub fn fields(&self) -> Fields<'_> {
self.as_mcf_hash_ref().fields()
}

/// Push an additional field onto the password hash string.
pub fn push_field(&mut self, field: Field<'_>) {
self.0.push(fields::DELIMITER);
self.0.push_str(field.as_str());
}

/// Push an additional field onto the password hash string, encoding it first as Base64.
pub fn push_field_base64(&mut self, field: &[u8], base64_encoding: Base64) {
self.0.push(fields::DELIMITER);
self.0.push_str(&base64_encoding.encode_string(field));
}
}

/// Push an additional field onto the password hash string.
pub fn push_field(&mut self, field: Field<'_>) {
self.0.push(fields::DELIMITER);
self.0.push_str(field.as_str());
impl<'a> AsRef<str> for McfHashRef<'a> {
fn as_ref(&self) -> &str {
self.as_str()
}
}

/// Push an additional field onto the password hash string, encoding it first as Base64.
pub fn push_field_base64(&mut self, field: &[u8], base64_encoding: Base64) {
self.0.push(fields::DELIMITER);
self.0.push_str(&base64_encoding.encode_string(field));
impl AsRef<str> for McfHash {
fn as_ref(&self) -> &str {
self.as_str()
}
}
}

impl<'a> AsRef<str> for McfHashRef<'a> {
fn as_ref(&self) -> &str {
self.as_str()
impl From<McfHash> for String {
fn from(hash: McfHash) -> Self {
hash.0
}
}
}

#[cfg(feature = "alloc")]
impl AsRef<str> for McfHash {
fn as_ref(&self) -> &str {
self.as_str()
impl TryFrom<String> for McfHash {
type Error = Error;

fn try_from(s: String) -> Result<Self> {
Self::new(s)
}
}
}

#[cfg(feature = "alloc")]
impl fmt::Display for McfHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
impl fmt::Display for McfHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
}

#[cfg(feature = "alloc")]
impl str::FromStr for McfHash {
type Err = Error;
impl str::FromStr for McfHash {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
Self::new(s)
fn from_str(s: &str) -> Result<Self> {
Self::new(s)
}
}
}

Expand Down