Skip to content

Commit

Permalink
wip: no-std
Browse files Browse the repository at this point in the history
  • Loading branch information
ibotty committed Oct 19, 2022
1 parent 2feb46f commit c631005
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 71 deletions.
4 changes: 2 additions & 2 deletions md5-crypt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ md-5 = { version = "0.10.5", default-features = false }

[features]
default = ["simple"]
simple = ["rand", "std", "subtle"]
std = []
alloc = []
simple = ["alloc", "rand", "subtle"]

[package.metadata.docs.rs]
all-features = true
Expand Down
18 changes: 7 additions & 11 deletions md5-crypt/src/b64.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
//! Base64 encoding support
use crate::defs::{BLOCK_SIZE, MAP_MD5, PW_SIZE_MD5};
use alloc::vec::Vec;
use base64ct::{Base64ShaCrypt, Encoding};

#[cfg(feature = "simple")]
#[cfg(any(feature = "subtle", test))]
use crate::errors::DecodeError;
use base64ct::{Base64ShaCrypt, Encoding};

pub fn encode_md5(source: &[u8]) -> Vec<u8> {
pub fn encode_md5(source: &[u8]) -> [u8; PW_SIZE_MD5] {
let mut transposed = [0u8; BLOCK_SIZE];
for (i, &ti) in MAP_MD5.iter().enumerate() {
transposed[i] = source[ti as usize];
}
let mut buf = [0u8; PW_SIZE_MD5];
Base64ShaCrypt::encode(&transposed, &mut buf).unwrap();
buf.to_vec()
buf
}

#[cfg(feature = "simple")]
pub fn decode_md5(source: &[u8]) -> Result<Vec<u8>, DecodeError> {
#[cfg(any(feature = "subtle", test))]
pub fn decode_md5(source: &[u8]) -> Result<[u8; BLOCK_SIZE], DecodeError> {
let mut buf = [0u8; PW_SIZE_MD5];
Base64ShaCrypt::decode(source, &mut buf).map_err(|_| DecodeError)?;

let mut transposed = [0u8; BLOCK_SIZE];
for (i, &ti) in MAP_MD5.iter().enumerate() {
transposed[ti as usize] = buf[i];
}
Ok(transposed.to_vec())
Ok(transposed)
}

mod tests {
#[cfg(feature = "simple")]
#[test]
fn test_encode_decode_md5() {
let original: [u8; 16] = [
Expand Down
1 change: 0 additions & 1 deletion md5-crypt/src/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ pub const BLOCK_SIZE: usize = 16;
pub const PW_SIZE_MD5: usize = 22;

/// Maximum length of a salt
#[cfg(feature = "simple")]
pub const SALT_MAX_LEN: usize = 8;

/// Encoding table.
Expand Down
31 changes: 7 additions & 24 deletions md5-crypt/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,42 @@
//! Error types.
#[cfg(feature = "alloc")]
use alloc::string;

#[cfg(feature = "simple")]
use alloc::string::String;

#[cfg(feature = "std")]
use std::io;

/// Error type.
#[derive(Debug)]
pub enum CryptError {
/// RNG failed.
RandomError,

/// I/O error.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
IoError(io::Error),

/// UTF-8 error.
#[cfg(feature = "alloc")]
StringError(string::FromUtf8Error),
}

#[cfg(feature = "std")]
impl From<io::Error> for CryptError {
fn from(e: io::Error) -> Self {
CryptError::IoError(e)
}
}

#[cfg(feature = "alloc")]
impl From<string::FromUtf8Error> for CryptError {
fn from(e: string::FromUtf8Error) -> Self {
CryptError::StringError(e)
}
}

#[cfg(feature = "simple")]
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
#[derive(Debug)]
pub enum CheckError {
InvalidFormat(String),
InvalidFormat(&'static str),
#[cfg(feature = "subtle")]
Crypt(CryptError),
#[cfg(feature = "subtle")]
HashMismatch,
}

/// Decoding errors.
#[cfg(feature = "simple")]
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
#[derive(Debug)]
pub struct DecodeError;

#[cfg(feature = "simple")]
impl From<DecodeError> for CheckError {
fn from(_: DecodeError) -> CheckError {
CheckError::InvalidFormat("invalid B64".into())
CheckError::InvalidFormat("invalid B64")
}
}
55 changes: 23 additions & 32 deletions md5-crypt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,34 +33,31 @@
#![warn(missing_docs, rust_2018_idioms)]

// TODO(tarcieri): heapless support
#[macro_use]
#[cfg(feature = "alloc")]
extern crate alloc;

#[cfg(feature = "std")]
extern crate std;

mod b64;
mod defs;
mod errors;

pub use crate::{defs::BLOCK_SIZE, errors::CryptError};
pub use crate::{
defs::{BLOCK_SIZE, PW_SIZE_MD5, SALT_MAX_LEN},
errors::CryptError,
};

use alloc::string::String;
use md5::{Digest, Md5};

#[cfg(feature = "subtle")]
use crate::errors::CheckError;

#[cfg(feature = "simple")]
use {
crate::{
defs::{SALT_MAX_LEN, TAB},
errors::CheckError,
},
alloc::string::ToString,
crate::defs::TAB,
alloc::string::String,
rand::{distributions::Distribution, thread_rng, Rng},
};

#[cfg(feature = "simple")]
static MD5_SALT_PREFIX: &str = "$1$";
#[cfg(feature = "simple")]

/// The MD5 crypt function returned as byte vector
///
Expand Down Expand Up @@ -160,10 +157,9 @@ pub fn md5_crypt(password: &[u8], salt: &[u8]) -> Result<[u8; BLOCK_SIZE], Crypt
/// # Returns
/// - `Ok(())` if calculation was successful
/// - `Err(errors::CryptError)` otherwise
pub fn md5_crypt_b64(password: &[u8], salt: &[u8]) -> Result<String, CryptError> {
pub fn md5_crypt_b64(password: &[u8], salt: &[u8]) -> Result<[u8; PW_SIZE_MD5], CryptError> {
let output = md5_crypt(password, salt)?;
let r = String::from_utf8(b64::encode_md5(&output))?;
Ok(r)
Ok(b64::encode_md5(&output))
}

/// Simple interface for generating a MD5 password hash.
Expand Down Expand Up @@ -191,7 +187,7 @@ pub fn md5_simple(password: &str) -> Result<String, CryptError> {
result.push_str(MD5_SALT_PREFIX);
result.push_str(&salt);
result.push('$');
let s = String::from_utf8(b64::encode_md5(&out))?;
let s = String::from_utf8(b64::encode_md5(&out).to_vec())?;
result.push_str(&s);
Ok(result)
}
Expand All @@ -206,40 +202,35 @@ pub fn md5_simple(password: &str) -> Result<String, CryptError> {
/// # Return
/// `OK(())` if password matches otherwise Err(CheckError) in case of invalid
/// format or password mismatch.
#[cfg(feature = "simple")]
#[cfg(feature = "subtle")]
#[cfg_attr(docsrs, doc(cfg(feature = "simple")))]
pub fn md5_check(password: &str, hashed_value: &str) -> Result<(), CheckError> {
let mut iter = hashed_value.split('$');

// Check that there are no characters before the first "$"
if iter.next() != Some("") {
return Err(CheckError::InvalidFormat(
"Should start with '$".to_string(),
));
return Err(CheckError::InvalidFormat("Should start with '$"));
}

if iter.next() != Some("1") {
return Err(CheckError::InvalidFormat(format!(
"does not contain MD5 identifier: '{}'",
MD5_SALT_PREFIX
)));
return Err(CheckError::InvalidFormat(
"does not contain MD5 identifier: '$1$'",
));
}

let next = iter.next().ok_or_else(|| {
CheckError::InvalidFormat("Does not contain a salt or hash string".to_string())
})?;
let next = iter.next().ok_or(CheckError::InvalidFormat(
"Does not contain a salt or hash string",
))?;

let salt = next;

let hash = iter
.next()
.ok_or_else(|| CheckError::InvalidFormat("Does not contain a hash string".to_string()))?;
.ok_or(CheckError::InvalidFormat("Does not contain a hash string"))?;

// Make sure there is no trailing data after the final "$"
if iter.next().is_some() {
return Err(CheckError::InvalidFormat(
"Trailing characters present".to_string(),
));
return Err(CheckError::InvalidFormat("Trailing characters present"));
}

let output = md5_crypt(password.as_bytes(), salt.as_bytes()).map_err(CheckError::Crypt)?;
Expand Down
5 changes: 4 additions & 1 deletion md5-crypt/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use md5_crypt::md5_crypt_b64;
#[cfg(feature = "simple")]
use md5_crypt::{md5_check, md5_simple};

use std::str;

struct TestVector {
input: &'static str,
salt: &'static str,
Expand Down Expand Up @@ -76,7 +78,8 @@ const TEST_VECTORS: &[TestVector] = &[
#[test]
fn test_md5_crypt() {
for t in TEST_VECTORS {
let result = md5_crypt_b64(t.input.as_bytes(), t.salt.as_bytes()).unwrap();
let result_array = md5_crypt_b64(t.input.as_bytes(), t.salt.as_bytes()).unwrap();
let result = str::from_utf8(&result_array).unwrap();
assert!(result == t.result);
}
}
Expand Down

0 comments on commit c631005

Please sign in to comment.