From 1c21ef69e231e436770fdec217d59766b3f28e72 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Tue, 3 Dec 2024 18:26:38 -0800 Subject: [PATCH] PBKDF2 rounds config draft --- ldap/ldif/template-dse-minimal.ldif.in | 4 + ldap/ldif/template-dse.ldif.in | 4 + ldap/servers/slapd/fedse.c | 3 + src/plugins/pwdchan/Cargo.toml | 1 + src/plugins/pwdchan/src/lib.rs | 150 ++++++++++++++++++++++- src/plugins/pwdchan/src/pbkdf2.rs | 55 ++++++++- src/plugins/pwdchan/src/pbkdf2_sha1.rs | 55 ++++++++- src/plugins/pwdchan/src/pbkdf2_sha256.rs | 55 ++++++++- src/plugins/pwdchan/src/pbkdf2_sha512.rs | 55 ++++++++- src/slapi_r_plugin/src/error.rs | 2 + src/slapi_r_plugin/src/pblock.rs | 4 +- src/slapi_r_plugin/src/plugin.rs | 4 + 12 files changed, 373 insertions(+), 19 deletions(-) diff --git a/ldap/ldif/template-dse-minimal.ldif.in b/ldap/ldif/template-dse-minimal.ldif.in index d2b02f8be9..7ee4a7c6df 100644 --- a/ldap/ldif/template-dse-minimal.ldif.in +++ b/ldap/ldif/template-dse-minimal.ldif.in @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index ce44d75cd1..be0409f11f 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c index 74f7e6cf99..e295c611ad 100644 --- a/ldap/servers/slapd/fedse.c +++ b/ldap/servers/slapd/fedse.c @@ -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" @@ -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" @@ -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" diff --git a/src/plugins/pwdchan/Cargo.toml b/src/plugins/pwdchan/Cargo.toml index 3cda69f221..e7921c9f30 100644 --- a/src/plugins/pwdchan/Cargo.toml +++ b/src/plugins/pwdchan/Cargo.toml @@ -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"] } diff --git a/src/plugins/pwdchan/src/lib.rs b/src/plugins/pwdchan/src/lib.rs index 7f938fccba..f856783229 100644 --- a/src/plugins/pwdchan/src/lib.rs +++ b/src/plugins/pwdchan/src/lib.rs @@ -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> = 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; @@ -131,10 +138,12 @@ impl PwdChanCrypto { let mut hash_input: Vec = (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(), ) @@ -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 @@ -191,10 +200,57 @@ impl PwdChanCrypto { fn pbkdf2_sha512_encrypt(cleartext: &str) -> Result { 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 { + 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' @@ -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"; diff --git a/src/plugins/pwdchan/src/pbkdf2.rs b/src/plugins/pwdchan/src/pbkdf2.rs index e8ee87b822..0db47a7aa5 100644 --- a/src/plugins/pwdchan/src/pbkdf2.rs +++ b/src/plugins/pwdchan/src/pbkdf2.rs @@ -1,6 +1,7 @@ use crate::PwdChanCrypto; use slapi_r_plugin::prelude::*; use std::os::raw::c_char; +use std::convert::TryInto; /* * /---- plugin ident @@ -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(()) } @@ -41,4 +50,44 @@ impl SlapiPlugin3 for PwdChanPbkdf2 { fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { 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::().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(()) + } } diff --git a/src/plugins/pwdchan/src/pbkdf2_sha1.rs b/src/plugins/pwdchan/src/pbkdf2_sha1.rs index 67afef5cbd..7f11433575 100644 --- a/src/plugins/pwdchan/src/pbkdf2_sha1.rs +++ b/src/plugins/pwdchan/src/pbkdf2_sha1.rs @@ -1,6 +1,7 @@ use crate::PwdChanCrypto; use slapi_r_plugin::prelude::*; use std::os::raw::c_char; +use std::convert::TryInto; /* * /---- plugin ident @@ -16,13 +17,21 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha1 { // 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-SHA1 plugin starting"); + + // Handle initial configuration + Self::handle_pbkdf2_rounds_config(pb)?; + + log_error!( + ErrorLevel::Info, + "PBKDF2-SHA1 plugin started successfully" + ); Ok(()) } fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { - log_error!(ErrorLevel::Trace, "plugin close"); + log_error!(ErrorLevel::Trace, "PBKDF2-SHA1 plugin closing"); Ok(()) } @@ -41,4 +50,44 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha1 { fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { 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::().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(()) + } } diff --git a/src/plugins/pwdchan/src/pbkdf2_sha256.rs b/src/plugins/pwdchan/src/pbkdf2_sha256.rs index efe3fb3836..3510d60c13 100644 --- a/src/plugins/pwdchan/src/pbkdf2_sha256.rs +++ b/src/plugins/pwdchan/src/pbkdf2_sha256.rs @@ -1,6 +1,7 @@ use crate::PwdChanCrypto; use slapi_r_plugin::prelude::*; use std::os::raw::c_char; +use std::convert::TryInto; /* * /---- plugin ident @@ -15,13 +16,21 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha256 { // 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-SHA256 plugin starting"); + + // Handle initial configuration + Self::handle_pbkdf2_rounds_config(pb)?; + + log_error!( + ErrorLevel::Info, + "PBKDF2-SHA256 plugin started successfully" + ); Ok(()) } fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { - log_error!(ErrorLevel::Trace, "plugin close"); + log_error!(ErrorLevel::Trace, "PBKDF2-SHA256 plugin closing"); Ok(()) } @@ -40,4 +49,44 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha256 { fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { PwdChanCrypto::pbkdf2_sha256_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::().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(()) + } } diff --git a/src/plugins/pwdchan/src/pbkdf2_sha512.rs b/src/plugins/pwdchan/src/pbkdf2_sha512.rs index b0ed2bea8f..5a248d9188 100644 --- a/src/plugins/pwdchan/src/pbkdf2_sha512.rs +++ b/src/plugins/pwdchan/src/pbkdf2_sha512.rs @@ -1,6 +1,7 @@ use crate::PwdChanCrypto; use slapi_r_plugin::prelude::*; use std::os::raw::c_char; +use std::convert::TryInto; /* * /---- plugin ident @@ -15,13 +16,21 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha512 { // 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-SHA512 plugin starting"); + + // Handle initial configuration + Self::handle_pbkdf2_rounds_config(pb)?; + + log_error!( + ErrorLevel::Info, + "PBKDF2-SHA512 plugin started successfully" + ); Ok(()) } fn close(_pb: &mut PblockRef) -> Result<(), PluginError> { - log_error!(ErrorLevel::Trace, "plugin close"); + log_error!(ErrorLevel::Trace, "PBKDF2-SHA512 plugin closing"); Ok(()) } @@ -40,4 +49,44 @@ impl SlapiPlugin3 for PwdChanPbkdf2Sha512 { fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { PwdChanCrypto::pbkdf2_sha512_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::().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(()) + } } diff --git a/src/slapi_r_plugin/src/error.rs b/src/slapi_r_plugin/src/error.rs index 228568efa0..0c7f2cd553 100644 --- a/src/slapi_r_plugin/src/error.rs +++ b/src/slapi_r_plugin/src/error.rs @@ -24,6 +24,8 @@ pub enum PluginError { InvalidBase64 = 1009, OpenSSL = 1010, Format = 1011, + LockError, + InvalidConfiguration, } #[derive(Debug)] diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs index 3ff6b9f9ca..18d54d47db 100644 --- a/src/slapi_r_plugin/src/pblock.rs +++ b/src/slapi_r_plugin/src/pblock.rs @@ -86,7 +86,7 @@ impl PblockRef { match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } { 0 => Ok(value), e => { - log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e); + log_error!(ErrorLevel::Error, "Unable to get from pblock -> {:?}", e); Err(()) } } @@ -98,7 +98,7 @@ impl PblockRef { match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } { 0 => Ok(value), e => { - log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e); + log_error!(ErrorLevel::Error, "Unable to get from pblock -> {:?}", e); Err(()) } } diff --git a/src/slapi_r_plugin/src/plugin.rs b/src/slapi_r_plugin/src/plugin.rs index 3790c40439..adbc309245 100644 --- a/src/slapi_r_plugin/src/plugin.rs +++ b/src/slapi_r_plugin/src/plugin.rs @@ -130,4 +130,8 @@ pub trait SlapiPlugin3 { fn pwd_storage_compare(_cleartext: &str, _encrypted: &str) -> Result { Err(PluginError::Unimplemented) } + + fn handle_pbkdf2_rounds_config(_pb: &mut PblockRef) -> Result<(), PluginError> { + Err(PluginError::Unimplemented) + } }