Merge branch 'fix-crypt_r-utf8' into 'master'

fix: crypt: treat key as raw bytes instead of requiring UTF-8

See merge request redox-os/relibc!1442
This commit is contained in:
Jeremy Soller
2026-06-09 06:20:42 -06:00
8 changed files with 37 additions and 21 deletions
+2 -2
View File
@@ -4,11 +4,11 @@ use argon2::{
password_hash::{PasswordHash, PasswordVerifier},
};
pub fn crypt_argon2(key: &str, setting: &str) -> Option<String> {
pub fn crypt_argon2(key: &[u8], setting: &str) -> Option<String> {
let hash = PasswordHash::new(setting).ok()?;
let argon2 = Argon2::default();
if argon2.verify_password(key.as_bytes(), &hash).is_ok() {
if argon2.verify_password(key, &hash).is_ok() {
Some(setting.to_string())
} else {
None
+2 -2
View File
@@ -61,14 +61,14 @@ fn split_with_prefix(hash: &str) -> Option<(&str, &str, c_uint)> {
/// # 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> {
pub fn crypt_blowfish(passw: &[u8], 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.extend_from_slice(passw);
vec.push(0);
// We only consider the first 72 chars; truncate if necessary.
+2 -2
View File
@@ -27,7 +27,7 @@ fn encode_md5(source: &[c_uchar]) -> Option<[c_uchar; PW_SIZE_MD5]> {
/// 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> {
fn inner_md5(passw: &[u8], setting: &str) -> Option<String> {
let mut digest_b = Md5::default();
digest_b.update(passw);
digest_b.update(setting);
@@ -124,7 +124,7 @@ fn inner_md5(passw: &str, setting: &str) -> Option<String> {
/// # 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> {
pub fn crypt_md5(passw: &[u8], setting: &str) -> Option<String> {
/* reject large keys */
if passw.len() > KEY_MAX {
return None;
+8 -6
View File
@@ -73,12 +73,14 @@ pub unsafe extern "C" fn crypt_r(
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 key = unsafe { CStr::from_ptr(key) }.to_bytes();
let setting = match unsafe { CStr::from_ptr(setting) }.to_str() {
Ok(s) => s,
Err(_) => {
platform::ERRNO.set(EINVAL);
return ptr::null_mut();
}
};
let encoded = if setting.starts_with('$') {
if setting.starts_with("$1$") {
+2 -2
View File
@@ -36,7 +36,7 @@ use sha2::Sha256;
/// # 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> {
pub fn crypt_pbkdf2(passw: &[u8], setting: &str) -> Option<String> {
if let Some((iter_str, salt)) = &setting[3..].split_once('$') {
if salt.contains('$') {
return None;
@@ -50,7 +50,7 @@ pub fn crypt_pbkdf2(passw: &str, setting: &str) -> Option<String> {
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);
pbkdf2_hmac::<Sha256>(passw, actual_salt.as_bytes(), iter, &mut buffer);
Some(format!(
"$8${}${}${}",
+2 -2
View File
@@ -92,7 +92,7 @@ fn read_setting(setting: &str) -> Option<(c_uchar, c_uint, c_uint, String)> {
/// # 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> {
pub fn crypt_scrypt(passw: &[u8], setting: &str) -> Option<String> {
if setting.len() < 14 {
return None;
}
@@ -102,7 +102,7 @@ pub fn crypt_scrypt(passw: &str, setting: &str) -> Option<String> {
let params = Params::new(nlog2, r, p, 32).ok()?;
let mut output = [0u8; 32];
scrypt(passw.as_bytes(), salt.as_bytes(), &params, &mut output).ok()?;
scrypt(passw, salt.as_bytes(), &params, &mut output).ok()?;
Some(format!(
"$7${}${}${}",
+3 -3
View File
@@ -53,7 +53,7 @@ pub enum ShaType {
/// # 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> {
pub fn crypt_sha(passw: &[u8], setting: &str, cipher: ShaType) -> Option<String> {
let mut cursor = 3;
let rounds;
@@ -117,12 +117,12 @@ pub fn crypt_sha(passw: &str, setting: &str, cipher: ShaType) -> Option<String>
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(), &params)
sha256_crypt_b64(passw, setting.as_bytes(), &params)
}
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(), &params)
sha512_crypt_b64(passw, setting.as_bytes(), &params)
}
} {
let (r_slice, rn_slice) = if has_round {
+16 -2
View File
@@ -1,5 +1,7 @@
#include <assert.h>
#include <crypt.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
@@ -12,14 +14,26 @@ int main()
// No salt
result = crypt("pleaseletmein", "$7$C6..../....");
assert(result != NULL);
// Invalid encoded number for r
result = crypt("pleaseletmein", "$7$C6.../....SodiumChloride");
assert(result == NULL);
// Invalid encoded number for p
result = crypt("pleaseletmein", "$7$C6..../...SodiumChloride");
assert(result == NULL);
// Non-UTF-8 key should succeed (treated as raw bytes)
char key[] = {(char)0xC0, (char)0x01, '\0'};
result = crypt(key, "$5$saltsalt$");
assert(result != NULL);
// Non-UTF-8 setting should return NULL with errno=EINVAL
char setting[] = {'$', '5', '$', (char)0xFE, (char)0xFF, '$', '\0'};
errno = 0;
result = crypt("password", setting);
assert(result == NULL);
assert(errno == EINVAL);
return 0;
}