Skip to content
Merged
Show file tree
Hide file tree
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
7 changes: 4 additions & 3 deletions mcf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ in the form `${id}$...`
"""

[dependencies]
base64ct = { version = "1.7", features = ["alloc"] }
base64ct = { version = "1.7", optional = true }

[dev-dependencies]
hex-literal = "1"

[features]
default = ["alloc"]
alloc = []
default = ["alloc", "base64"]
alloc = ["base64ct?/alloc"]
base64 = ["dep:base64ct"]
2 changes: 2 additions & 0 deletions mcf/src/base64.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Base64 encoding variants.

#![cfg(feature = "base64")]

use base64ct::{Base64Bcrypt, Base64Crypt, Base64ShaCrypt, Encoding as _, Error as B64Error};

#[cfg(feature = "alloc")]
Expand Down
6 changes: 0 additions & 6 deletions mcf/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ pub type Result<T> = core::result::Result<T, Error>;
#[non_exhaustive]
pub struct Error {}

impl From<base64ct::Error> for Error {
fn from(_: base64ct::Error) -> Error {
Error {}
}
}

impl core::error::Error for Error {}

impl fmt::Display for Error {
Expand Down
13 changes: 8 additions & 5 deletions mcf/src/fields.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Fields of an MCF password hash, delimited by `$`

use crate::{Base64, Error, Result};
use crate::{Error, Result};
use core::fmt;

#[cfg(feature = "alloc")]
#[cfg(feature = "base64")]
use crate::Base64;
#[cfg(all(feature = "alloc", feature = "base64"))]
use alloc::vec::Vec;

/// MCF field delimiter: `$`.
Expand Down Expand Up @@ -70,14 +72,15 @@ impl<'a> Field<'a> {
}

/// Decode Base64 into the provided output buffer.
#[cfg(feature = "base64")]
pub fn decode_base64_into(self, base64_variant: Base64, out: &mut [u8]) -> Result<&[u8]> {
Ok(base64_variant.decode(self.0, out)?)
base64_variant.decode(self.0, out).map_err(|_| Error {})
}

/// Decode this field as the provided Base64 variant.
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", feature = "base64"))]
pub fn decode_base64(self, base64_variant: Base64) -> Result<Vec<u8>> {
Ok(base64_variant.decode_vec(self.0)?)
base64_variant.decode_vec(self.0).map_err(|_| Error {})
}

/// Validate a field in the password hash is well-formed.
Expand Down
11 changes: 7 additions & 4 deletions mcf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ mod base64;
mod error;
mod fields;

pub use base64::Base64;
pub use error::{Error, Result};
pub use fields::{Field, Fields};

#[cfg(feature = "alloc")]
pub use allocating::PasswordHash;
#[cfg(feature = "base64")]
pub use base64::Base64;

/// 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 @@ -93,12 +94,13 @@ impl<'a> TryFrom<&'a str> for PasswordHashRef<'a> {

#[cfg(feature = "alloc")]
mod allocating {
use crate::{
Base64, Error, Field, Fields, PasswordHashRef, Result, fields, validate, validate_id,
};
use crate::{Error, Field, Fields, PasswordHashRef, Result, fields, validate, validate_id};
use alloc::string::String;
use core::{fmt, str};

#[cfg(feature = "base64")]
use crate::Base64;

/// Password hash encoded in the Modular Crypt Format (MCF). Owned form with builder
/// functionality.
///
Expand Down Expand Up @@ -166,6 +168,7 @@ mod allocating {

/// Encode the given data as the specified variant of Base64 and push it onto the password
/// hash string, first adding a `$` delimiter.
#[cfg(feature = "base64")]
pub fn push_base64(&mut self, field: &[u8], base64_encoding: Base64) {
self.0.push(fields::DELIMITER);
self.0.push_str(&base64_encoding.encode_string(field));
Expand Down
23 changes: 17 additions & 6 deletions mcf/tests/mcf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

#![cfg(feature = "alloc")]

use hex_literal::hex;
use mcf::{Base64, PasswordHash};
use mcf::PasswordHash;

#[cfg(feature = "base64")]
use {hex_literal::hex, mcf::Base64};

const SHA512_HASH: &str = "$6$rounds=100000$exn6tVc2j/MZD8uG$BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0";

#[cfg(feature = "base64")]
const EXAMPLE_SALT: &[u8] = &hex!("6a3f237988126f80958fa24b");
#[cfg(feature = "base64")]
const EXAMPLE_HASH: &[u8] = &hex!(
"0d358cad62739eb554863c183aef27e6390368fe061fc5fcb1193a392d60dcad4594fa8d383ab8fc3f0dc8088974602668422e6a58edfa1afe24831b10be69be"
);
Expand Down Expand Up @@ -47,21 +51,28 @@ fn parse_sha512_hash() {
let salt = fields.next().unwrap();
assert_eq!("exn6tVc2j/MZD8uG", salt.as_str());

let salt_bytes = salt.decode_base64(Base64::ShaCrypt).unwrap();
assert_eq!(EXAMPLE_SALT, salt_bytes.as_slice());
#[cfg(feature = "base64")]
{
let salt_bytes = salt.decode_base64(Base64::ShaCrypt).unwrap();
assert_eq!(EXAMPLE_SALT, salt_bytes.as_slice());
}

let hash = fields.next().unwrap();
assert_eq!(
"BI1Xh8qQSK9J4m14uwy7abn.ctj/TIAzlaVCto0MQrOFIeTXsc1iwzH16XEWo/a7c7Y9eVJvufVzYAs4EsPOy0",
hash.as_str()
);

let hash_bytes = hash.decode_base64(Base64::ShaCrypt).unwrap();
assert_eq!(EXAMPLE_HASH, hash_bytes.as_slice());
#[cfg(feature = "base64")]
{
let hash_bytes = hash.decode_base64(Base64::ShaCrypt).unwrap();
assert_eq!(EXAMPLE_HASH, hash_bytes.as_slice());
}

assert_eq!(None, fields.next());
}

#[cfg(feature = "base64")]
#[test]
fn push_fields() {
let mut hash = PasswordHash::new("$6$rounds=100000").unwrap();
Expand Down