Skip to content

Commit

Permalink
PBKDF2 rounds config draft
Browse files Browse the repository at this point in the history
  • Loading branch information
droideck committed Dec 9, 2024
1 parent 976f43e commit 1c21ef6
Show file tree
Hide file tree
Showing 12 changed files with 373 additions and 19 deletions.
4 changes: 4 additions & 0 deletions ldap/ldif/template-dse-minimal.ldif.in
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ nsslapd-pluginenabled: on
dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init
Expand All @@ -208,6 +209,7 @@ nsslapd-pluginDescription: PBKDF2
dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA1
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init
Expand All @@ -221,6 +223,7 @@ nsslapd-pluginDescription: PBKDF2-SHA1\
dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA256
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init
Expand All @@ -234,6 +237,7 @@ nsslapd-pluginDescription: PBKDF2-SHA256\
dn: cn=PBKDF2-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA512
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init
Expand Down
4 changes: 4 additions & 0 deletions ldap/ldif/template-dse.ldif.in
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ nsslapd-pluginenabled: on
dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init
Expand All @@ -265,6 +266,7 @@ nsslapd-pluginDescription: PBKDF2
dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA1
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init
Expand All @@ -278,6 +280,7 @@ nsslapd-pluginDescription: PBKDF2-SHA1\
dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA256
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init
Expand All @@ -291,6 +294,7 @@ nsslapd-pluginDescription: PBKDF2-SHA256\
dn: cn=PBKDF2-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: PBKDF2-SHA512
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init
Expand Down
3 changes: 3 additions & 0 deletions ldap/servers/slapd/fedse.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ static const char *internal_entries[] =
"dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config\n"
"objectclass: top\n"
"objectclass: nsSlapdPlugin\n"
"objectclass: extensibleObject\n"
"cn: PBKDF2\n"
"nsslapd-pluginpath: libpwdchan-plugin\n"
"nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init\n"
Expand All @@ -244,6 +245,7 @@ static const char *internal_entries[] =
"dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config\n"
"objectclass: top\n"
"objectclass: nsSlapdPlugin\n"
"objectclass: extensibleObject\n"
"cn: PBKDF2-SHA1\n"
"nsslapd-pluginpath: libpwdchan-plugin\n"
"nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init\n"
Expand All @@ -257,6 +259,7 @@ static const char *internal_entries[] =
"dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config\n"
"objectclass: top\n"
"objectclass: nsSlapdPlugin\n"
"objectclass: extensibleObject\n"
"cn: PBKDF2-SHA256\n"
"nsslapd-pluginpath: libpwdchan-plugin\n"
"nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init\n"
Expand Down
1 change: 1 addition & 0 deletions src/plugins/pwdchan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ slapi_r_plugin = { path="../../slapi_r_plugin" }
uuid = { version = "0.8", features = [ "v4" ] }
openssl = { version = "0.10" }
base64 = "0.13"
once_cell = "1.20"

[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
150 changes: 145 additions & 5 deletions src/plugins/pwdchan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ extern crate slapi_r_plugin;
use base64;
use openssl::{hash::MessageDigest, pkcs5::pbkdf2_hmac, rand::rand_bytes};
use slapi_r_plugin::prelude::*;
use std::fmt;
use std::fmt::Write;
use once_cell::sync::Lazy;
use std::sync::RwLock;

const DEFAULT_PBKDF2_ROUNDS: usize = 10_000;
const MIN_PBKDF2_ROUNDS: usize = 10_000;
const MAX_PBKDF2_ROUNDS: usize = 100_000;

static PBKDF2_ROUNDS: Lazy<RwLock<usize>> = Lazy::new(|| RwLock::new(DEFAULT_PBKDF2_ROUNDS));

const PBKDF2_ROUNDS: usize = 10_000;
const PBKDF2_SALT_LEN: usize = 24;
const PBKDF2_SHA1_EXTRACT: usize = 20;
const PBKDF2_SHA256_EXTRACT: usize = 32;
Expand Down Expand Up @@ -131,10 +138,12 @@ impl PwdChanCrypto {

let mut hash_input: Vec<u8> = (0..hash_length).map(|_| 0).collect();

let rounds = Self::get_pbkdf2_rounds()?;

pbkdf2_hmac(
cleartext.as_bytes(),
&salt,
PBKDF2_ROUNDS,
rounds,
digest,
hash_input.as_mut_slice(),
)
Expand All @@ -147,14 +156,14 @@ impl PwdChanCrypto {
// Write the header
output.push_str(header);
// The iter + delim
fmt::write(&mut output, format_args!("{}$", PBKDF2_ROUNDS)).map_err(|e| {
write!(&mut output, "{}$", rounds).map_err(|e| {
log_error!(ErrorLevel::Error, "Format Error -> {:?}", e);
PluginError::Format
})?;
// the base64 salt
base64::encode_config_buf(&salt, base64::STANDARD, &mut output);
// Push the delim
output.push_str("$");
output.push('$');
// Finally the base64 hash
base64::encode_config_buf(&hash_input, base64::STANDARD, &mut output);
// Return it
Expand Down Expand Up @@ -191,10 +200,57 @@ impl PwdChanCrypto {
fn pbkdf2_sha512_encrypt(cleartext: &str) -> Result<String, PluginError> {
Self::pbkdf2_encrypt(cleartext, MessageDigest::sha512())
}

pub fn set_pbkdf2_rounds(rounds: usize) -> Result<(), PluginError> {
if rounds < MIN_PBKDF2_ROUNDS || rounds > MAX_PBKDF2_ROUNDS {
log_error!(
ErrorLevel::Error,
"Invalid PBKDF2 rounds {}, must be between {} and {}",
rounds,
MIN_PBKDF2_ROUNDS,
MAX_PBKDF2_ROUNDS
);
return Err(PluginError::InvalidConfiguration);
}
match PBKDF2_ROUNDS.write() {
Ok(mut rounds_guard) => {
*rounds_guard = rounds;
log_error!(
ErrorLevel::Info,
"PBKDF2 rounds successfully configured to: {}",
rounds
);
Ok(())
}
Err(e) => {
log_error!(
ErrorLevel::Error,
"Failed to acquire write lock for PBKDF2 rounds: {}",
e
);
Err(PluginError::LockError)
}
}
}

fn get_pbkdf2_rounds() -> Result<usize, PluginError> {
match PBKDF2_ROUNDS.read() {
Ok(rounds_guard) => Ok(*rounds_guard),
Err(e) => {
log_error!(
ErrorLevel::Error,
"Failed to acquire read lock for PBKDF2 rounds: {}",
e
);
Err(PluginError::LockError)
}
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::PwdChanCrypto;
/*
* '{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w'
Expand All @@ -204,6 +260,90 @@ mod tests {
* '{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ'
*/

#[test]
fn test_pbkdf2_rounds_configuration() {
// Test valid rounds configuration
assert!(PwdChanCrypto::set_pbkdf2_rounds(15000).is_ok());
assert_eq!(PwdChanCrypto::get_pbkdf2_rounds().unwrap(), 15000);

// Test invalid rounds - too low
assert!(matches!(
PwdChanCrypto::set_pbkdf2_rounds(5000),
Err(PluginError::InvalidConfiguration)
));

// Test invalid rounds - too high
assert!(matches!(
PwdChanCrypto::set_pbkdf2_rounds(200000),
Err(PluginError::InvalidConfiguration)
));
}

#[test]
fn test_pbkdf2_encrypt_with_rounds() {
// Set a specific number of rounds
PwdChanCrypto::set_pbkdf2_rounds(15000).unwrap();

// Test each hash type
let test_password = "test_password";

// SHA-1
let result = PwdChanCrypto::pbkdf2_sha1_encrypt(test_password).unwrap();
assert!(result.contains("15000$"));
assert!(PwdChanCrypto::pbkdf2_sha1_compare(
test_password,
&result.replace("{PBKDF2-SHA1}", "")
).unwrap());

// SHA-256
let result = PwdChanCrypto::pbkdf2_sha256_encrypt(test_password).unwrap();
assert!(result.contains("15000$"));
assert!(PwdChanCrypto::pbkdf2_sha256_compare(
test_password,
&result.replace("{PBKDF2-SHA256}", "")
).unwrap());

// SHA-512
let result = PwdChanCrypto::pbkdf2_sha512_encrypt(test_password).unwrap();
assert!(result.contains("15000$"));
assert!(PwdChanCrypto::pbkdf2_sha512_compare(
test_password,
&result.replace("{PBKDF2-SHA512}", "")
).unwrap());
}

#[test]
fn test_set_pbkdf2_rounds() {
// Test valid rounds
assert!(PwdChanCrypto::set_pbkdf2_rounds(15_000).is_ok());
assert_eq!(PwdChanCrypto::get_pbkdf2_rounds().unwrap(), 15_000);

// Test invalid rounds - too low
assert!(matches!(
PwdChanCrypto::set_pbkdf2_rounds(5_000),
Err(PluginError::InvalidConfiguration)
));

// Test invalid rounds - too high
assert!(matches!(
PwdChanCrypto::set_pbkdf2_rounds(200_000),
Err(PluginError::InvalidConfiguration)
));
}

#[test]
fn test_pbkdf2_decompose() {
let valid_hash = "10000$salt123$hash456";
let result = PwdChanCrypto::pbkdf2_decompose(valid_hash);
assert!(result.is_ok());
let (iter, salt, hash) = result.unwrap();
assert_eq!(iter, 10000);

// Test invalid format
let invalid_hash = "invalid";
assert!(PwdChanCrypto::pbkdf2_decompose(invalid_hash).is_err());
}

#[test]
fn pwdchan_pbkdf2_sha1_basic() {
let encrypted = "10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
Expand Down
55 changes: 52 additions & 3 deletions src/plugins/pwdchan/src/pbkdf2.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::PwdChanCrypto;
use slapi_r_plugin::prelude::*;
use std::os::raw::c_char;
use std::convert::TryInto;

/*
* /---- plugin ident
Expand All @@ -16,13 +17,21 @@ impl SlapiPlugin3 for PwdChanPbkdf2 {
// We require a newer rust for default associated types.
type TaskData = ();

fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
log_error!(ErrorLevel::Trace, "plugin start");
fn start(pb: &mut PblockRef) -> Result<(), PluginError> {
log_error!(ErrorLevel::Trace, "PBKDF2 plugin starting");

// Handle initial configuration
Self::handle_pbkdf2_rounds_config(pb)?;

log_error!(
ErrorLevel::Info,
"PBKDF2 plugin started successfully"
);
Ok(())
}

fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
log_error!(ErrorLevel::Trace, "plugin close");
log_error!(ErrorLevel::Trace, "PBKDF2 plugin closing");
Ok(())
}

Expand All @@ -41,4 +50,44 @@ impl SlapiPlugin3 for PwdChanPbkdf2 {
fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
PwdChanCrypto::pbkdf2_sha1_compare(cleartext, encrypted)
}

fn handle_pbkdf2_rounds_config(pb: &mut PblockRef) -> Result<(), PluginError> {
const PBKDF2_ROUNDS_ATTR: &str = "passwordPBKDF2Rounds";

if let Ok(entry) = pb.get_op_add_entryref() {
if let Some(value_array) = entry.get_attr(PBKDF2_ROUNDS_ATTR) {
if let Some(value) = value_array.first() {
let rounds_str: String = value
.as_ref()
.try_into()
.map_err(|_| {
log_error!(
ErrorLevel::Error,
"Failed to parse passwordPBKDF2Rounds value"
);
PluginError::InvalidConfiguration
})?;

let rounds = rounds_str.parse::<usize>().map_err(|e| {
log_error!(
ErrorLevel::Error,
"Invalid PBKDF2 rounds value '{}': {}",
rounds_str,
e
);
PluginError::InvalidConfiguration
})?;

PwdChanCrypto::set_pbkdf2_rounds(rounds)?;

log_error!(
ErrorLevel::Info,
"PBKDF2 rounds configured to {} from entry attribute",
rounds
);
}
}
}
Ok(())
}
}
Loading

0 comments on commit 1c21ef6

Please sign in to comment.