0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
use alloc::string::{String, ToString};
|
||||
use argon2::{
|
||||
Argon2,
|
||||
password_hash::{PasswordHash, PasswordVerifier},
|
||||
};
|
||||
|
||||
pub fn crypt_argon2(key: &str, setting: &str) -> Option<String> {
|
||||
let hash = PasswordHash::new(setting).ok()?;
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
if argon2.verify_password(key.as_bytes(), &hash).is_ok() {
|
||||
Some(setting.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
use crate::platform::types::{c_uchar, c_uint};
|
||||
use alloc::{string::String, vec::Vec};
|
||||
use base64ct::{Base64Bcrypt, Encoding};
|
||||
use bcrypt_pbkdf::bcrypt_pbkdf;
|
||||
use core::str;
|
||||
|
||||
const MIN_COST: u32 = 4;
|
||||
const MAX_COST: u32 = 31;
|
||||
const BHASH_WORDS: usize = 8;
|
||||
const BHASH_OUTPUT_SIZE: usize = BHASH_WORDS * 4;
|
||||
|
||||
/// Inspired by https://github.com/Keats/rust-bcrypt/blob/87fc59e917bcb6cf3f3752fc7f2b4c659d415597/src/lib.rs#L135
|
||||
fn split_with_prefix(hash: &str) -> Option<(&str, &str, c_uint)> {
|
||||
let valid_prefixes = ["2y", "2b", "2a", "2x"];
|
||||
|
||||
// Should be [prefix, cost, hash]
|
||||
let raw_parts: Vec<_> = hash.split('$').skip(1).collect();
|
||||
if raw_parts.len() != 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let prefix = raw_parts[0];
|
||||
let setting = raw_parts[2];
|
||||
|
||||
if !valid_prefixes.contains(&prefix) {
|
||||
return None;
|
||||
}
|
||||
|
||||
raw_parts[1]
|
||||
.parse::<c_uint>()
|
||||
.ok()
|
||||
.map(|cost| (prefix, setting, cost))
|
||||
}
|
||||
|
||||
/// Performs Blowfish key derivation on a given password with a specific setting.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
|
||||
/// * `setting`: The settings for the Blowfish key derivation. It must be a string slice (`&str`)
|
||||
/// and should follow the format `$<prefix>$<cost>$<setting>`, where `<prefix>` is a string that
|
||||
/// indicates the type of the hash (e.g., "$2a$"), `<cost>` is a decimal number representing
|
||||
/// the cost factor for the Blowfish operation, and `<setting>` is a base64-encoded string
|
||||
/// representing the salt to be used for the Blowfish function.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<String>`: Returns `Some(String)` if the Blowfish operation was successful, where the
|
||||
/// returned string is the result of the Blowfish operation formatted according to the Modular
|
||||
/// Crypt Format (MCF). If the Blowfish operation failed, it returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
/// * If the cost factor is outside the range `[MIN_COST, MAX_COST]`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let password = "correctbatteryhorsestapler";
|
||||
/// let setting = "$2y$12$L6Bc/AlTQHyd9liGgGEZyO";
|
||||
/// let result = crypt_blowfish(password, setting);
|
||||
/// assert!(result.is_some());
|
||||
///```
|
||||
///
|
||||
/// # Note
|
||||
/// The `crypt_blowfish` function uses the Blowfish block cipher for hashing.
|
||||
/// The output of the Blowfish operation is base64-encoded using the BCrypt variant of base64.
|
||||
pub fn crypt_blowfish(passw: &str, setting: &str) -> Option<String> {
|
||||
if let Some((prefix, setting, cost)) = split_with_prefix(setting) {
|
||||
if !(MIN_COST..=MAX_COST).contains(&cost) {
|
||||
return None;
|
||||
}
|
||||
// Passwords need to be null terminated
|
||||
let mut vec = Vec::with_capacity(passw.len() + 1);
|
||||
vec.extend_from_slice(passw.as_bytes());
|
||||
vec.push(0);
|
||||
|
||||
// We only consider the first 72 chars; truncate if necessary.
|
||||
let passw_t = if vec.len() > 72 { &vec[..72] } else { &vec };
|
||||
let passw: &[c_uchar] = passw_t;
|
||||
let setting = setting.as_bytes();
|
||||
let mut output = vec![0; BHASH_OUTPUT_SIZE + 1];
|
||||
|
||||
bcrypt_pbkdf(passw, setting, cost, &mut output).ok()?;
|
||||
Some(format!(
|
||||
"${}${}${}",
|
||||
prefix,
|
||||
cost,
|
||||
Base64Bcrypt::encode_string(&output),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
include_guard = "_RELIBC_CRYPT_H"
|
||||
language = "C"
|
||||
style = "Type"
|
||||
no_includes = true
|
||||
cpp_compat = true
|
||||
|
||||
|
||||
[enum]
|
||||
prefix_with_name = true
|
||||
@@ -0,0 +1,146 @@
|
||||
use crate::platform::types::c_uchar;
|
||||
use alloc::string::String;
|
||||
use base64ct::{Base64ShaCrypt, Encoding};
|
||||
use core::str;
|
||||
use md5_crypto::{Digest, Md5};
|
||||
|
||||
// Block size for MD5
|
||||
const BLOCK_SIZE: usize = 16;
|
||||
// PWD part length of the password string
|
||||
const PW_SIZE_MD5: usize = 22;
|
||||
// Maximum length of a setting
|
||||
const SALT_MAX: usize = 8;
|
||||
// Inverse encoding map for MD5.
|
||||
const MAP_MD5: [c_uchar; BLOCK_SIZE] = [12, 6, 0, 13, 7, 1, 14, 8, 2, 15, 9, 3, 5, 10, 4, 11];
|
||||
|
||||
const KEY_MAX: usize = 30000;
|
||||
|
||||
fn encode_md5(source: &[c_uchar]) -> Option<[c_uchar; PW_SIZE_MD5]> {
|
||||
let mut transposed = [0; BLOCK_SIZE];
|
||||
for (i, &ti) in MAP_MD5.iter().enumerate() {
|
||||
transposed[i] = source[ti as usize];
|
||||
}
|
||||
let mut buf = [0; PW_SIZE_MD5];
|
||||
Base64ShaCrypt::encode(&transposed, &mut buf).ok()?;
|
||||
Some(buf)
|
||||
}
|
||||
|
||||
/// Function taken from PR: https://github.com/RustCrypto/password-hashes/pull/351
|
||||
/// This won't be needed once the PR is merged
|
||||
fn inner_md5(passw: &str, setting: &str) -> Option<String> {
|
||||
let mut digest_b = Md5::default();
|
||||
digest_b.update(passw);
|
||||
digest_b.update(setting);
|
||||
digest_b.update(passw);
|
||||
let hash_b = digest_b.finalize();
|
||||
|
||||
let mut digest_a = Md5::default();
|
||||
digest_a.update(passw);
|
||||
digest_a.update("$1$");
|
||||
digest_a.update(setting);
|
||||
|
||||
let mut pw_len = passw.len();
|
||||
let rounds = pw_len / BLOCK_SIZE;
|
||||
for _ in 0..rounds {
|
||||
digest_a.update(hash_b);
|
||||
}
|
||||
|
||||
// leftover passw
|
||||
digest_a.update(&hash_b[..(pw_len - rounds * BLOCK_SIZE)]);
|
||||
|
||||
while pw_len > 0 {
|
||||
match pw_len & 1 {
|
||||
0 => digest_a.update(&passw[..1]),
|
||||
1 => digest_a.update([0u8]),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
pw_len >>= 1;
|
||||
}
|
||||
|
||||
let mut hash_a = digest_a.finalize();
|
||||
|
||||
// Repeatedly run the collected hash value through MD5 to burn
|
||||
// CPU cycles
|
||||
for i in 0..1000_usize {
|
||||
// new hasher
|
||||
let mut hasher = Md5::default();
|
||||
|
||||
// Add key or last result
|
||||
if (i & 1) != 0 {
|
||||
hasher.update(passw);
|
||||
} else {
|
||||
hasher.update(hash_a);
|
||||
}
|
||||
|
||||
// Add setting for numbers not divisible by 3
|
||||
if i % 3 != 0 {
|
||||
hasher.update(setting);
|
||||
}
|
||||
|
||||
// Add key for numbers not divisible by 7
|
||||
if i % 7 != 0 {
|
||||
hasher.update(passw);
|
||||
}
|
||||
|
||||
// Add key or last result
|
||||
if (i & 1) != 0 {
|
||||
hasher.update(hash_a);
|
||||
} else {
|
||||
hasher.update(passw);
|
||||
}
|
||||
|
||||
// digest_c.clone_from_slice(&hasher.finalize());
|
||||
hash_a = hasher.finalize();
|
||||
}
|
||||
encode_md5(hash_a.as_slice())
|
||||
.map(|encstr| format!("$1${}${}", setting, str::from_utf8(&encstr).unwrap()))
|
||||
}
|
||||
|
||||
/// Performs MD5 hashing on a given password with a specific setting.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
|
||||
/// * `setting`: The settings for the MD5 hashing. It must be a string slice (`&str`)
|
||||
/// and should start with "$1$". The rest of the string should represent the salt
|
||||
/// to be used for the MD5 hashing.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<String>`: Returns `Some(String)` if the MD5 operation was successful, where the
|
||||
/// returned string is the result of the MD5 operation formatted according to the Modular
|
||||
/// Crypt Format (MCF). If the MD5 operation failed, it returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
/// * If the `passw` length exceeds `KEY_MAX`.
|
||||
/// * If the `setting` does not start with "$1$".
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let password = "my_password";
|
||||
/// let setting = "$1$saltstring";
|
||||
/// let result = crypt_md5(password, setting);
|
||||
/// assert!(result.is_some());
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// The `crypt_md5` function uses the MD5 hashing algorithm for hashing.
|
||||
/// The output of the MD5 operation is base64-encoded using the BCrypt variant of base64.
|
||||
pub fn crypt_md5(passw: &str, setting: &str) -> Option<String> {
|
||||
/* reject large keys */
|
||||
if passw.len() > KEY_MAX {
|
||||
return None;
|
||||
}
|
||||
|
||||
if &setting[0..3] != "$1$" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let cursor = 3;
|
||||
let slen = cursor
|
||||
+ setting[cursor..cursor + SALT_MAX]
|
||||
.chars()
|
||||
.take_while(|c| *c != '$')
|
||||
.count();
|
||||
let setting = &setting[cursor..slen];
|
||||
|
||||
inner_md5(passw, setting)
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
//! `crypt.h` implementation.
|
||||
//!
|
||||
//! Non-POSIX, see <https://www.man7.org/linux/man-pages/man3/crypt.3.html>.
|
||||
|
||||
use ::scrypt::password_hash::{Salt, SaltString};
|
||||
use alloc::{
|
||||
ffi::CString,
|
||||
string::{String, ToString},
|
||||
};
|
||||
use core::ptr;
|
||||
use rand::{Rng, SeedableRng, rngs::SmallRng};
|
||||
|
||||
use crate::{
|
||||
c_str::CStr,
|
||||
header::{errno::EINVAL, stdlib::rand},
|
||||
platform::{
|
||||
self,
|
||||
types::{c_char, c_int},
|
||||
},
|
||||
};
|
||||
|
||||
mod argon2;
|
||||
mod blowfish;
|
||||
mod md5;
|
||||
mod pbkdf2;
|
||||
mod scrypt;
|
||||
mod sha;
|
||||
|
||||
use self::{
|
||||
argon2::crypt_argon2,
|
||||
blowfish::crypt_blowfish,
|
||||
md5::crypt_md5,
|
||||
pbkdf2::crypt_pbkdf2,
|
||||
scrypt::crypt_scrypt,
|
||||
sha::{
|
||||
ShaType::{Sha256, Sha512},
|
||||
crypt_sha,
|
||||
},
|
||||
};
|
||||
|
||||
/// See <https://www.man7.org/linux/man-pages/man3/crypt.3.html>.
|
||||
#[repr(C)]
|
||||
pub struct crypt_data {
|
||||
initialized: c_int,
|
||||
buff: [c_char; 256],
|
||||
}
|
||||
|
||||
impl crypt_data {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
crypt_data {
|
||||
initialized: 1,
|
||||
buff: [0; 256],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_salt() -> Option<String> {
|
||||
let mut rng = SmallRng::seed_from_u64(unsafe { rand() as u64 });
|
||||
let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
Some(SaltString::encode_b64(&bytes).ok()?.as_str().to_string())
|
||||
}
|
||||
|
||||
/// See <https://www.man7.org/linux/man-pages/man3/crypt.3.html>.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn crypt_r(
|
||||
key: *const c_char,
|
||||
setting: *const c_char,
|
||||
data: *mut crypt_data,
|
||||
) -> *mut c_char {
|
||||
if unsafe { (*data).initialized } == 0 {
|
||||
unsafe { *data = crypt_data::new() };
|
||||
}
|
||||
|
||||
let key = unsafe { CStr::from_ptr(key) }
|
||||
.to_str()
|
||||
.expect("key must be utf-8");
|
||||
let setting = unsafe { CStr::from_ptr(setting) }
|
||||
.to_str()
|
||||
.expect("setting must be utf-8");
|
||||
|
||||
let encoded = if setting.starts_with('$') {
|
||||
if setting.starts_with("$1$") {
|
||||
crypt_md5(key, setting)
|
||||
} else if setting.starts_with("$2") && setting.as_bytes().get(3) == Some(&b'$') {
|
||||
crypt_blowfish(key, setting)
|
||||
} else if setting.starts_with("$5$") {
|
||||
crypt_sha(key, setting, Sha256)
|
||||
} else if setting.starts_with("$6$") {
|
||||
crypt_sha(key, setting, Sha512)
|
||||
} else if setting.starts_with("$7$") {
|
||||
crypt_scrypt(key, setting)
|
||||
} else if setting.starts_with("$8$") {
|
||||
crypt_pbkdf2(key, setting)
|
||||
} else if setting.starts_with("$argon2") {
|
||||
crypt_argon2(key, setting)
|
||||
} else {
|
||||
platform::ERRNO.set(EINVAL);
|
||||
return ptr::null_mut();
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(inner) = encoded {
|
||||
let len = inner.len();
|
||||
if let Ok(ret) = CString::new(inner) {
|
||||
let ret_ptr = ret.into_raw();
|
||||
unsafe {
|
||||
let dst = (*data).buff.as_mut_ptr();
|
||||
ptr::copy_nonoverlapping(ret_ptr, dst.cast(), len);
|
||||
}
|
||||
ret_ptr.cast()
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
} else {
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use super::gen_salt;
|
||||
use alloc::string::{String, ToString};
|
||||
use base64ct::{Base64Bcrypt, Encoding};
|
||||
use core::str;
|
||||
use pbkdf2::pbkdf2_hmac;
|
||||
use sha2::Sha256;
|
||||
|
||||
/// Performs PBKDF2 key derivation on a given password with a specific setting.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
|
||||
/// * `setting`: The settings for the PBKDF2 key derivation. It must be a string slice (`&str`)
|
||||
/// and should follow the format `$<iter>$<salt>`. The `<iter>` part should be a hexadecimal
|
||||
/// number representing the iteration count for the PBKDF2 function. The `<salt>` part should
|
||||
/// be a base64-encoded string representing the salt to be used for the PBKDF2 function.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<String>`: Returns `Some(String)` if the PBKDF2 operation was successful, where the
|
||||
/// returned string is the result of the PBKDF2 operation formatted according to the Modular
|
||||
/// Crypt Format (MCF). If the PBKDF2 operation failed, it returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
/// * If the `setting` does not contain a '$' character.
|
||||
/// * If the `setting` contains another '$' character after the first one.
|
||||
/// * If the `<salt>` part of the `setting` is empty.
|
||||
/// * If the `<iter>` part of the `setting` cannot be converted into a `u32` integer.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let password = "my_password";
|
||||
/// let setting = "$8$3e8$salt";
|
||||
/// let result = crypt_pbkdf2(password, setting);
|
||||
/// assert!(result.is_some());
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// The `crypt_pbkdf2` function uses the SHA256 hashing algorithm for the PBKDF2 operation.
|
||||
/// The output of the PBKDF2 operation is base64-encoded using the BCrypt variant of base64.
|
||||
pub fn crypt_pbkdf2(passw: &str, setting: &str) -> Option<String> {
|
||||
if let Some((iter_str, salt)) = &setting[3..].split_once('$') {
|
||||
if salt.contains('$') {
|
||||
return None;
|
||||
}
|
||||
|
||||
let actual_salt = if !salt.is_empty() {
|
||||
salt.to_string()
|
||||
} else {
|
||||
gen_salt()?
|
||||
};
|
||||
|
||||
let iter = u32::from_str_radix(iter_str, 16).ok()?;
|
||||
let mut buffer = [0u8; 32];
|
||||
pbkdf2_hmac::<Sha256>(passw.as_bytes(), actual_salt.as_bytes(), iter, &mut buffer);
|
||||
|
||||
Some(format!(
|
||||
"$8${}${}${}",
|
||||
iter_str,
|
||||
salt,
|
||||
Base64Bcrypt::encode_string(&buffer)
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
use super::gen_salt;
|
||||
use crate::platform::types::{c_uchar, c_uint};
|
||||
use alloc::string::{String, ToString};
|
||||
use base64ct::{Base64Bcrypt, Encoding};
|
||||
use core::str;
|
||||
use scrypt::{Params, scrypt};
|
||||
|
||||
/// Map for encoding and decoding
|
||||
#[inline(always)]
|
||||
fn to_digit(c: char, radix: u32) -> Option<u32> {
|
||||
match c {
|
||||
'.' => Some(0),
|
||||
'/' => Some(1),
|
||||
_ => c.to_digit(radix).map(|d| d + 2),
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes a 5 character lengt str value to c_uint
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `value` - A string slice that represents a u32 value in base64
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<c_uint>` - Returns the decoded c_uint value if successful, otherwise None
|
||||
fn dencode_uint(value: &str) -> Option<c_uint> {
|
||||
if value.len() != 5 {
|
||||
return None;
|
||||
}
|
||||
|
||||
value
|
||||
.chars()
|
||||
.enumerate()
|
||||
.try_fold(0 as c_uint, |acc, (i, c)| {
|
||||
acc.checked_add((to_digit(c, 30)? as c_uint) << (i * 6))
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads settings for password encryption
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `setting` - A string slice that represents the settings
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Option<(c_uchar, c_uint, c_uint, String)>` - Returns a tuple containing the settings if successful, otherwise None
|
||||
fn read_setting(setting: &str) -> Option<(c_uchar, c_uint, c_uint, String)> {
|
||||
let nlog2 = to_digit(setting.chars().next()?, 30)? as c_uchar;
|
||||
let r = dencode_uint(&setting[1..6])?;
|
||||
let p = dencode_uint(&setting[6..11])?;
|
||||
|
||||
let salt = &setting[11..];
|
||||
let actual_salt = if !salt.is_empty() {
|
||||
salt.to_string()
|
||||
} else {
|
||||
gen_salt()?
|
||||
};
|
||||
|
||||
Some((nlog2, r, p, actual_salt))
|
||||
}
|
||||
|
||||
/// Performs Scrypt key derivation on a given password with a specific setting.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
|
||||
/// * `setting`: The settings for the Scrypt key derivation. It must be a string slice (`&str`)
|
||||
/// and should follow the format `$<Nlog2>$<r>$<p>$<salt>`. The `<Nlog2>` part should be a decimal
|
||||
/// number representing the logarithm base 2 of the CPU/memory cost factor N for Scrypt. The `<r>`
|
||||
/// part should be a decimal number representing the block size r. The `<p>` part should be a decimal
|
||||
/// number representing the parallelization factor p. The `<salt>` part should be a base64-encoded
|
||||
/// string representing the salt to be used for the Scrypt function.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<String>`: Returns `Some(String)` if the Scrypt operation was successful, where the
|
||||
/// returned string is the result of the Scrypt operation formatted according to the Modular
|
||||
/// Crypt Format (MCF). If the Scrypt operation failed, it returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
/// * If the `setting` length is less than 14 characters.
|
||||
/// * If the `scrypt` function fails to perform the Scrypt operation.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let password = "my_password";
|
||||
/// let setting = "$7$C6..../....SodiumChloride";
|
||||
/// let result = crypt_scrypt(password, setting);
|
||||
/// assert!(result.is_some());
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// The `crypt_scrypt` function uses the Scrypt key derivation function for hashing.
|
||||
/// The output of the Scrypt operation is base64-encoded using the BCrypt variant of base64.
|
||||
pub fn crypt_scrypt(passw: &str, setting: &str) -> Option<String> {
|
||||
if setting.len() < 14 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (nlog2, r, p, salt) = read_setting(&setting[3..])?;
|
||||
|
||||
let params = Params::new(nlog2, r, p, 32).ok()?;
|
||||
let mut output = [0u8; 32];
|
||||
|
||||
scrypt(passw.as_bytes(), salt.as_bytes(), ¶ms, &mut output).ok()?;
|
||||
|
||||
Some(format!(
|
||||
"$7${}${}${}",
|
||||
&setting[3..14],
|
||||
salt,
|
||||
Base64Bcrypt::encode_string(&output)
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use alloc::string::{String, ToString};
|
||||
|
||||
use sha_crypt::{
|
||||
ROUNDS_DEFAULT, ROUNDS_MAX, ROUNDS_MIN, Sha256Params, Sha512Params, sha256_crypt_b64,
|
||||
sha512_crypt_b64,
|
||||
};
|
||||
|
||||
use crate::platform::types::c_ulong;
|
||||
|
||||
// key limit is not part of the original design, added for DoS protection.
|
||||
// rounds limit has been lowered (versus the reference/spec), also for DoS
|
||||
// protection. runtime is O(klen^2 + klen*rounds)
|
||||
const KEY_MAX: usize = 256;
|
||||
const SALT_MAX: usize = 16;
|
||||
const RSTRING: &str = "rounds=";
|
||||
|
||||
pub enum ShaType {
|
||||
Sha256,
|
||||
Sha512,
|
||||
}
|
||||
|
||||
/// Performs SHA hashing on a given password with a specific setting.
|
||||
///
|
||||
/// # Parameters
|
||||
/// * `passw`: The password to be hashed. It must be a string slice (`&str`).
|
||||
/// * `setting`: The settings for the SHA hashing. It must be a string slice (`&str`)
|
||||
/// and should start with "$5$" for SHA256 or "$6$" for SHA512. The rest of the string should represent the salt
|
||||
/// to be used for the SHA hashing.
|
||||
/// * `cipher`: The type of SHA algorithm to use. It should be either `ShaType::Sha256` or `ShaType::Sha512`.
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Option<String>`: Returns `Some(String)` if the SHA operation was successful, where the
|
||||
/// returned string is the result of the SHA operation formatted according to the Modular
|
||||
/// Crypt Format (MCF). If the SHA operation failed, it returns `None`.
|
||||
///
|
||||
/// # Errors
|
||||
/// * If the `passw` length exceeds `KEY_MAX`.
|
||||
/// * If the `setting` does not start with "$5$" or "$6$".
|
||||
/// * If the `setting` does not contain a '$' character.
|
||||
/// * If the `setting` contains another '$' character after the first one.
|
||||
/// * If the `setting` contains invalid characters.
|
||||
/// * If the `setting` contains an invalid number of rounds.
|
||||
/// * If the `sha256_crypt_b64` or `sha512_crypt_b64` function fails to hash the password.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let password = "my_password";
|
||||
/// let setting = "$5$rounds=1400$anotherlongsaltstringg";
|
||||
/// let result = crypt_sha(password, setting, ShaType::Sha256);
|
||||
/// assert!(result.is_some());
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
/// The `crypt_sha` function uses the SHA256 or SHA512 hashing algorithm for hashing.
|
||||
/// The output of the SHA operation is base64-encoded using the BCrypt variant of base64.
|
||||
pub fn crypt_sha(passw: &str, setting: &str, cipher: ShaType) -> Option<String> {
|
||||
let mut cursor = 3;
|
||||
let rounds;
|
||||
|
||||
/* reject large keys */
|
||||
if passw.len() > KEY_MAX {
|
||||
return None;
|
||||
}
|
||||
|
||||
// SHA256
|
||||
// setting: $5$rounds=n$setting$ (rounds=n$ and closing $ are optional)
|
||||
// SHA512
|
||||
// setting: $6$rounds=n$setting$ (rounds=n$ and closing $ are optional)
|
||||
let param = match cipher {
|
||||
ShaType::Sha256 => "$5$",
|
||||
ShaType::Sha512 => "$6$",
|
||||
};
|
||||
|
||||
if &setting[0..3] != param {
|
||||
return None;
|
||||
}
|
||||
|
||||
let has_round;
|
||||
// 7 is len("rounds=")
|
||||
if &setting[cursor..cursor + 7] == RSTRING {
|
||||
cursor += 7;
|
||||
has_round = true;
|
||||
if let Some(c_end) = setting[cursor..].chars().position(|r| r == '$') {
|
||||
if let Ok(u) = setting[cursor..cursor + c_end].parse::<c_ulong>() {
|
||||
cursor += c_end + 1;
|
||||
rounds = u.min(ROUNDS_MAX as c_ulong).max(ROUNDS_MIN as c_ulong);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
has_round = false;
|
||||
rounds = ROUNDS_DEFAULT as c_ulong;
|
||||
}
|
||||
|
||||
let mut slen = cursor;
|
||||
|
||||
for i in 0..SALT_MAX.min(setting.len() - cursor) {
|
||||
let idx = cursor + i;
|
||||
|
||||
if &setting[idx..idx + 1] == "$" {
|
||||
break;
|
||||
}
|
||||
|
||||
// reject characters that interfere with /etc/shadow parsing
|
||||
if &setting[idx..idx + 1] == "\n" || &setting[idx..idx + 1] == ":" {
|
||||
return None;
|
||||
}
|
||||
slen += 1;
|
||||
}
|
||||
|
||||
let setting = &setting[cursor..slen];
|
||||
|
||||
if let Ok(enc) = match cipher {
|
||||
ShaType::Sha256 => {
|
||||
let params = Sha256Params::new(rounds as usize)
|
||||
.unwrap_or(Sha256Params::new(ROUNDS_DEFAULT).unwrap());
|
||||
sha256_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms)
|
||||
}
|
||||
ShaType::Sha512 => {
|
||||
let params = Sha512Params::new(rounds as usize)
|
||||
.unwrap_or(Sha512Params::new(ROUNDS_DEFAULT).unwrap());
|
||||
sha512_crypt_b64(passw.as_bytes(), setting.as_bytes(), ¶ms)
|
||||
}
|
||||
} {
|
||||
let (r_slice, rn_slice) = if has_round {
|
||||
(RSTRING, rounds.to_string() + "$")
|
||||
} else {
|
||||
("", String::new())
|
||||
};
|
||||
|
||||
Some(format!(
|
||||
"{}{}{}{}${}",
|
||||
param, r_slice, rn_slice, setting, enc
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user