This is an automated email from the git hooks/post-receive script.
firstyear pushed a commit to branch master
in repository 389-ds-base.
commit e086b834f43b417007e3f0b0a822db2a2350c2a9
Author: William Brown <firstyear(a)redhat.com>
Date: Tue Apr 25 14:41:30 2017 +1000
Ticket 49135 - PBKDF2 should determine rounds at startup
Bug Description: We used a hardcoded number of rounds for PBKDF2
Fix Description: Rather than hardcoding rounds at startup, we define
an attacker "work" factor. We have chosen 40 milliseconds for now.
Based on this factor, we then run a test to determine the CPU performance
of the system. If the CPU performance is belowe a threshold, we use
10,000 rounds. If it is above, we scale the rounds up to our work
factor. This way, each attempt by an attacker on a password should
take 40 milliseconds - enough to cause them headaches, but still
have a fast ldap server (given a bind takes about 500 milliseconds
on my laptop today).
https://pagure.io/389-ds-base/issue/49135
Author: wibrown
Review by: mreynolds (Thanks!!)
---
Makefile.am | 12 ++-
dirsrvtests/tests/suites/password/pwd_algo_test.py | 17 ++-
ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c | 117 +++++++++++++++++++--
ldap/servers/plugins/pwdstorage/pwd_init.c | 2 +
ldap/servers/plugins/pwdstorage/pwdstorage.h | 23 ++--
test/main.c | 3 +
test/plugins/pwdstorage/pbkdf2.c | 73 +++++++++++++
test/plugins/test.c | 31 ++++++
test/test_slapd.h | 13 +++
9 files changed, 265 insertions(+), 26 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index bedccbf..e2421ab 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2008,13 +2008,19 @@ test_slapd_SOURCES = test/main.c \
test/libslapd/pblock/analytics.c \
test/libslapd/pblock/v3_compat.c \
test/libslapd/operation/v3_compat.c \
- test/libslapd/spal/meminfo.c
+ test/libslapd/spal/meminfo.c \
+ test/plugins/test.c \
+ test/plugins/pwdstorage/pbkdf2.c
-test_slapd_LDADD = libslapd.la
+# We need to link a lot of plugins for this test.
+test_slapd_LDADD = libslapd.la \
+ libpwdstorage-plugin.la
test_slapd_LDFLAGS = $(AM_CPPFLAGS) $(CMOCKA_LINKS)
### WARNING: Slap.h needs cert.h, which requires the -I/lib/ldaputil!!!
### WARNING: Slap.h pulls ssl.h, which requires nss!!!!
-test_slapd_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(DSINTERNAL_CPPFLAGS)
+# We need to pull in plugin header paths too:
+test_slapd_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(DSINTERNAL_CPPFLAGS) \
+ -I$(srcdir)/ldap/servers/plugins/pwdstorage
test_libsds_SOURCES = src/libsds/test/test_sds.c \
src/libsds/test/test_sds_bpt.c \
diff --git a/dirsrvtests/tests/suites/password/pwd_algo_test.py
b/dirsrvtests/tests/suites/password/pwd_algo_test.py
index 4b3fb33..b3f03fe 100644
--- a/dirsrvtests/tests/suites/password/pwd_algo_test.py
+++ b/dirsrvtests/tests/suites/password/pwd_algo_test.py
@@ -69,9 +69,20 @@ def test_pwd_algo_test(topology_st):
password conditions.
"""
- for algo in (
- 'CLEAR', 'CRYPT', 'MD5', 'SHA',
'SHA256', 'SHA384', 'SHA512', 'SMD5', 'SSHA',
'SSHA256', 'SSHA384',
- 'SSHA512'):
+ for algo in ('CLEAR',
+ 'CRYPT',
+ 'MD5',
+ 'SHA',
+ 'SHA256',
+ 'SHA384',
+ 'SHA512',
+ 'SMD5',
+ 'SSHA',
+ 'SSHA256',
+ 'SSHA384',
+ 'SSHA512',
+ 'PBKDF2_SHA256',
+ ):
_test_algo(topology_st.standalone, algo)
log.info('Test PASSED')
diff --git a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
index b228700..f08b6ab 100644
--- a/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
+++ b/ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
@@ -44,7 +44,10 @@
* At the same time we MUST increase this with each version of Directory Server
* This value is written into the hash, so it's safe to change.
*/
-#define PBKDF2_ITERATIONS 30000
+
+#define PBKDF2_MILLISECONDS 40
+
+static PRUint32 PBKDF2_ITERATIONS = 30000;
static const char *schemeName = PBKDF2_SHA256_SCHEME_NAME;
static const PRUint32 schemeNameLength = PBKDF2_SHA256_NAME_LEN;
@@ -52,6 +55,10 @@ static const PRUint32 schemeNameLength = PBKDF2_SHA256_NAME_LEN;
/* For requesting the slot which supports these types */
static CK_MECHANISM_TYPE mechanism_array[] = {CKM_SHA256_HMAC, CKM_PKCS5_PBKD2};
+/* Used in our startup benching code */
+#define PBKDF2_BENCH_ROUNDS 50000
+#define PBKDF2_BENCH_LOOP 10
+
void
pbkdf2_sha256_extract(char *hash_in, SECItem *salt, PRUint32 *iterations)
{
@@ -124,12 +131,11 @@ pbkdf2_sha256_hash(char *hash_out, size_t hash_out_len, SECItem
*pwd, SECItem *s
}
char *
-pbkdf2_sha256_pw_enc(const char *pwd)
+pbkdf2_sha256_pw_enc_rounds(const char *pwd, PRUint32 iterations)
{
char hash[ PBKDF2_TOTAL_LENGTH ];
size_t encsize = 3 + schemeNameLength + LDIF_BASE64_LEN(PBKDF2_TOTAL_LENGTH);
char *enc = slapi_ch_calloc(encsize, sizeof(char));
- PRUint32 iterations = PBKDF2_ITERATIONS;
SECItem saltItem;
SECItem passItem;
@@ -174,24 +180,24 @@ pbkdf2_sha256_pw_enc(const char *pwd)
return enc;
}
+char *
+pbkdf2_sha256_pw_enc(const char *pwd) {
+ return pbkdf2_sha256_pw_enc_rounds(pwd, PBKDF2_ITERATIONS);
+}
+
PRInt32
pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd)
{
PRInt32 result = 1; /* Default to fail. */
- char dbhash[ PBKDF2_TOTAL_LENGTH ];
- char userhash[ PBKDF2_HASH_LENGTH ];
+ char dbhash[ PBKDF2_TOTAL_LENGTH ] = {0};
+ char userhash[ PBKDF2_HASH_LENGTH ] = {0};
PRUint32 dbpwd_len = strlen(dbpwd);
SECItem saltItem;
SECItem passItem;
PRUint32 iterations = 0;
- /* Our hash value is always at a known offset. */
- char *hash = dbhash + PBKDF2_ITERATIONS_LENGTH + PBKDF2_SALT_LENGTH;
-
slapi_log_err(SLAPI_LOG_PLUGIN, (char *)schemeName, "Comparing
password\n");
- memset(dbhash, 0, PBKDF2_TOTAL_LENGTH);
-
passItem.data = (unsigned char *)userpwd;
passItem.len = strlen(userpwd);
@@ -208,10 +214,101 @@ pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd)
slapi_log_err(SLAPI_LOG_ERR, (char *)schemeName, "Unable to hash userpwd
value\n");
return result;
}
+
+ /* Our hash value is always at a known offset in the decoded string. */
+ char *hash = dbhash + PBKDF2_ITERATIONS_LENGTH + PBKDF2_SALT_LENGTH;
+
/* Now compare the result of pbkdf2_sha256_hash. */
result = memcmp(userhash, hash, PBKDF2_HASH_LENGTH);
return result;
}
+uint64_t
+pbkdf2_sha256_benchmark_iterations() {
+ /* Time how long it takes to do PBKDF2_BENCH_LOOP attempts of PBKDF2_BENCH_ROUNDS
rounds */
+ uint64_t time_nsec = 0;
+ char *results[PBKDF2_BENCH_LOOP] = {0};
+ struct timespec start_time;
+ struct timespec finish_time;
+
+ clock_gettime(CLOCK_MONOTONIC, &start_time);
+
+ for (size_t i = 0; i < PBKDF2_BENCH_LOOP; i++) {
+ results[i] = pbkdf2_sha256_pw_enc_rounds("Eequee9mutheuchiehe4",
PBKDF2_BENCH_ROUNDS);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &finish_time);
+
+ for (size_t i = 0; i < PBKDF2_BENCH_LOOP; i++) {
+ slapi_ch_free((void **)&(results[i]));
+ }
+
+ /* Work out the execution time. */
+ time_nsec = (finish_time.tv_sec - start_time.tv_sec) * 1000000000;
+ if (finish_time.tv_nsec > start_time.tv_nsec) {
+ time_nsec += finish_time.tv_nsec - start_time.tv_nsec;
+ } else {
+ time_nsec += 1000000000 - (start_time.tv_nsec - finish_time.tv_nsec);
+ }
+
+ time_nsec = time_nsec / PBKDF2_BENCH_LOOP;
+
+ return time_nsec;
+}
+
+PRUint32
+pbkdf2_sha256_calculate_iterations(uint64_t time_nsec) {
+ /*
+ * So we know that we have nsec for a single round of PBKDF2_BENCH_ROUNDS now.
+ * first, we get the cost of "every 1000 rounds"
+ */
+ uint64_t number_thou_rounds = PBKDF2_BENCH_ROUNDS / 1000;
+ uint64_t thou_time_nsec = time_nsec / number_thou_rounds;
+
+ /*
+ * Now we have the cost of 1000 rounds. Now, knowing this we say
+ * we want an attacker to have to expend say ... example 8 ms of work
+ * to try a password. So this is 1,000,000 ns = 1ms, ergo
+ * 8,000,000
+ */
+ uint64_t attack_work_nsec = PBKDF2_MILLISECONDS * 1000000;
+
+ /*
+ * Knowing the attacker time and our cost, we can divide this
+ * to get how many thousands of rounds we should use.
+ */
+ uint64_t thou_rounds = (attack_work_nsec / thou_time_nsec);
+
+ /*
+ * Finally, we make the rounds in terms of thousands, and cast it.
+ */
+ PRUint32 final_rounds = thou_rounds * 1000;
+
+ if (final_rounds < 10000) {
+ final_rounds = 10000;
+ }
+
+ return final_rounds;
+}
+
+
+int
+pbkdf2_sha256_start(Slapi_PBlock *pb __attribute__((unused))) {
+ /* Run the time generator */
+ uint64_t time_nsec = pbkdf2_sha256_benchmark_iterations();
+ /* Calculate the iterations */
+ /* set it globally */
+ PBKDF2_ITERATIONS = pbkdf2_sha256_calculate_iterations(time_nsec);
+ /* Make a note of it. */
+ slapi_log_err(SLAPI_LOG_PLUGIN, (char *)schemeName, "Based on CPU performance,
chose %"PRIu32" rounds\n", PBKDF2_ITERATIONS);
+ return 0;
+}
+
+/* Do we need the matching close function? */
+int
+pbkdf2_sha256_close(Slapi_PBlock *pb __attribute__((unused))) {
+ return 0;
+}
+
diff --git a/ldap/servers/plugins/pwdstorage/pwd_init.c
b/ldap/servers/plugins/pwdstorage/pwd_init.c
index 0781c09..16d2f32 100644
--- a/ldap/servers/plugins/pwdstorage/pwd_init.c
+++ b/ldap/servers/plugins/pwdstorage/pwd_init.c
@@ -349,6 +349,8 @@ pbkdf2_sha256_pwd_storage_scheme_init(Slapi_PBlock *pb)
rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, (void *) SLAPI_PLUGIN_VERSION_01);
rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void
*)&pbkdf2_sha256_pdesc);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void*)&pbkdf2_sha256_start);
+ rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void*)&pbkdf2_sha256_close);
rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN, (void
*)pbkdf2_sha256_pw_enc);
rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN, (void
*)pbkdf2_sha256_pw_cmp);
rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME,
PBKDF2_SHA256_SCHEME_NAME);
diff --git a/ldap/servers/plugins/pwdstorage/pwdstorage.h
b/ldap/servers/plugins/pwdstorage/pwdstorage.h
index d48b634..2909998 100644
--- a/ldap/servers/plugins/pwdstorage/pwdstorage.h
+++ b/ldap/servers/plugins/pwdstorage/pwdstorage.h
@@ -7,22 +7,20 @@
* See LICENSE for details.
* END COPYRIGHT BLOCK **/
+#pragma once
+
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
-#ifndef _PWDSTORAGE_H
-#define _PWDSTORAGE_H
-
-#include "slapi-plugin.h"
-#include "slapi-private.h"
+#include <slapi-plugin.h>
+#include <slapi-private.h>
#include <ssl.h>
-#include "nspr.h"
-#include "plbase64.h"
-#include "ldif.h"
+#include <nspr.h>
+#include <plbase64.h>
+#include <ldif.h>
#include "md5.h"
-
#define PWD_HASH_PREFIX_START '{'
#define PWD_HASH_PREFIX_END '}'
@@ -86,11 +84,16 @@ char *md5_pw_enc( const char *pwd );
int smd5_pw_cmp( const char *userpwd, const char *dbpwd );
char *smd5_pw_enc( const char *pwd );
+int pbkdf2_sha256_start(Slapi_PBlock *pb);
+int pbkdf2_sha256_close(Slapi_PBlock *pb);
SECStatus pbkdf2_sha256_hash(char *hash_out, size_t hash_out_len, SECItem *pwd, SECItem
*salt, PRUint32 iterations);
char * pbkdf2_sha256_pw_enc(const char *pwd);
int pbkdf2_sha256_pw_cmp(const char *userpwd, const char *dbpwd);
+/* For testing pbkdf2 only */
+uint64_t pbkdf2_sha256_benchmark_iterations();
+PRUint32 pbkdf2_sha256_calculate_iterations();
+
/* Utility functions */
PRUint32 pwdstorage_base64_decode_len(const char *encval, PRUint32 enclen);
-#endif /* _PWDSTORAGE_H */
diff --git a/test/main.c b/test/main.c
index d84f8df..36447ea 100644
--- a/test/main.c
+++ b/test/main.c
@@ -12,5 +12,8 @@ int
main ( int argc __attribute__((unused)), char **argv __attribute__((unused))) {
int result = 0;
result += run_libslapd_tests();
+ result += run_plugin_tests();
+
+ PR_Cleanup();
return result;
}
diff --git a/test/plugins/pwdstorage/pbkdf2.c b/test/plugins/pwdstorage/pbkdf2.c
new file mode 100644
index 0000000..abadbfb
--- /dev/null
+++ b/test/plugins/pwdstorage/pbkdf2.c
@@ -0,0 +1,73 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright (C) 2017 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * License: GPL (version 3 or any later version).
+ * See LICENSE for details.
+ * END COPYRIGHT BLOCK **/
+
+#include "../../test_slapd.h"
+
+#include <nss3/nss.h>
+#include <pwdstorage.h>
+
+int
+test_plugin_pwdstorage_nss_setup(void **state __attribute__((unused))) {
+ int result = NSS_Initialize(NULL, "", "", SECMOD_DB,
NSS_INIT_READONLY|NSS_INIT_NOCERTDB|NSS_INIT_NOMODDB);
+ assert_true(result == 0);
+ return result;
+}
+
+int
+test_plugin_pwdstorage_nss_stop(void **state __attribute__((unused))) {
+ NSS_Shutdown();
+ return 0;
+}
+
+void
+test_plugin_pwdstorage_pbkdf2_auth(void **state __attribute__((unused))) {
+
+ /* Check that given various known passwords and hashes they validate (or don't)
*/
+
+ /* 'password' */
+ const char *password_a =
"AAB1MHPzX9ZP+HDQYp/+qxQwJAW5cXhRvXX1+w0NBMVX6FyMv2uzIvtBfvn6A3o84gKW9fBl5hGPeH87bQMZs977SvCV09P8MV/fkkjH7EoYNXoSQ6FFBpjm3orFplT9Y5PY14xRvJS4iicQ82uKaaARlkbn0uLaHBNS18uz1YFzuYUlf4lqh+uy1VzAR3YQW9FWKL9TYCsTRx75EGUMYj/f7826CqrHNubnljh4s5gi31y+2qsdzdRerT1ISZC5z0kQbkXZYM7UCa4hlbSQl3mO6lpyxk44oiPkbKKii+bS+KRdIMeMgFawXo2L4+IYx+qXvJRwyi1M8vIxK+dnc2kOrLF9E7rZvs0hn9PuXMW3Itq46wPL3R51wo+0ki4gA36ZNF3PegbjFiAvrh24/D3SQMBjfk1YMDstNGJaMefd3bS1";
+
+ /*
+ * 'password' - but we mucked with the rounds
+ * note the 5th char of the b64 is "L" not "M'. This changes the
rounds from
+ * 30000 to 29996, which means we should fail
+ */
+ const char *password_a_rounds =
"AAB1LHPzX9ZP+HDQYp/+qxQwJAW5cXhRvXX1+w0NBMVX6FyMv2uzIvtBfvn6A3o84gKW9fBl5hGPeH87bQMZs977SvCV09P8MV/fkkjH7EoYNXoSQ6FFBpjm3orFplT9Y5PY14xRvJS4iicQ82uKaaARlkbn0uLaHBNS18uz1YFzuYUlf4lqh+uy1VzAR3YQW9FWKL9TYCsTRx75EGUMYj/f7826CqrHNubnljh4s5gi31y+2qsdzdRerT1ISZC5z0kQbkXZYM7UCa4hlbSQl3mO6lpyxk44oiPkbKKii+bS+KRdIMeMgFawXo2L4+IYx+qXvJRwyi1M8vIxK+dnc2kOrLF9E7rZvs0hn9PuXMW3Itq46wPL3R51wo+0ki4gA36ZNF3PegbjFiAvrh24/D3SQMBjfk1YMDstNGJaMefd3bS1";
+
+ /*
+ * 'password' - but we mucked with the salt Note the change in the 8th char
from
+ * z to 0.
+ */
+ const char *password_a_salt =
"AAB1MHP0X9ZP+HDQYp/+qxQwJAW5cXhRvXX1+w0NBMVX6FyMv2uzIvtBfvn6A3o84gKW9fBl5hGPeH87bQMZs977SvCV09P8MV/fkkjH7EoYNXoSQ6FFBpjm3orFplT9Y5PY14xRvJS4iicQ82uKaaARlkbn0uLaHBNS18uz1YFzuYUlf4lqh+uy1VzAR3YQW9FWKL9TYCsTRx75EGUMYj/f7826CqrHNubnljh4s5gi31y+2qsdzdRerT1ISZC5z0kQbkXZYM7UCa4hlbSQl3mO6lpyxk44oiPkbKKii+bS+KRdIMeMgFawXo2L4+IYx+qXvJRwyi1M8vIxK+dnc2kOrLF9E7rZvs0hn9PuXMW3Itq46wPL3R51wo+0ki4gA36ZNF3PegbjFiAvrh24/D3SQMBjfk1YMDstNGJaMefd3bS1";
+
+ assert_true(pbkdf2_sha256_pw_cmp("password", password_a) == 0);
+ assert_false(pbkdf2_sha256_pw_cmp("password", password_a_rounds) == 0);
+ assert_false(pbkdf2_sha256_pw_cmp("password", password_a_salt) == 0);
+ assert_false(pbkdf2_sha256_pw_cmp("password_b", password_a) == 0);
+}
+
+void
+test_plugin_pwdstorage_pbkdf2_rounds(void **state __attribute__((unused))){
+ /* Check the benchmark, and make sure we get a valid timestamp */
+ assert_true(pbkdf2_sha256_benchmark_iterations() > 0);
+ /*
+ * provide various values to the calculator, to check we get the right
+ * number of rounds back.
+ */
+ /*
+ * On a very slow system, we get the default min rounds out.
+ */
+ assert_true(pbkdf2_sha256_calculate_iterations(1000000000) == 10000);
+ /*
+ * On a "fast" system, we should see more rounds.
+ */
+ assert_true(pbkdf2_sha256_calculate_iterations(200000000) == 10000);
+ assert_true(pbkdf2_sha256_calculate_iterations(100000000) == 20000);
+ assert_true(pbkdf2_sha256_calculate_iterations(50000000) == 40000);
+}
+
diff --git a/test/plugins/test.c b/test/plugins/test.c
new file mode 100644
index 0000000..d853913
--- /dev/null
+++ b/test/plugins/test.c
@@ -0,0 +1,31 @@
+/** BEGIN COPYRIGHT BLOCK
+ * Copyright (C) 2017 Red Hat, Inc.
+ * All rights reserved.
+ *
+ * License: GPL (version 3 or any later version).
+ * See LICENSE for details.
+ * END COPYRIGHT BLOCK **/
+
+#include "../test_slapd.h"
+
+void
+test_plugin_hello(void **state __attribute__((unused))) {
+ /* It works! */
+ assert_int_equal(1, 1);
+}
+
+int
+run_plugin_tests (void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_plugin_hello),
+ cmocka_unit_test_setup_teardown(test_plugin_pwdstorage_pbkdf2_auth,
+ test_plugin_pwdstorage_nss_setup,
+ test_plugin_pwdstorage_nss_stop),
+ cmocka_unit_test_setup_teardown(test_plugin_pwdstorage_pbkdf2_rounds,
+ test_plugin_pwdstorage_nss_setup,
+ test_plugin_pwdstorage_nss_stop),
+ };
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
+
+
diff --git a/test/test_slapd.h b/test/test_slapd.h
index 50de11b..2c2b9f5 100644
--- a/test/test_slapd.h
+++ b/test/test_slapd.h
@@ -19,6 +19,7 @@
/* Test runners */
int run_libslapd_tests (void);
+int run_plugin_tests (void);
/* == The tests == */
@@ -47,3 +48,15 @@ void test_libslapd_counters_atomic_overflow(void **state);
void test_libslapd_pal_meminfo(void **state);
void test_libslapd_util_cachesane(void **state);
+/* plugins */
+
+void test_plugin_hello(void **state);
+
+/* plugin-pwdstorage-pbkdf2 */
+
+int test_plugin_pwdstorage_nss_setup(void **state);
+int test_plugin_pwdstorage_nss_stop(void **state);
+
+void test_plugin_pwdstorage_pbkdf2_auth(void **state);
+void test_plugin_pwdstorage_pbkdf2_rounds(void **state);
+
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.