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 19, 2024
1 parent 196a410 commit d1025bd
Show file tree
Hide file tree
Showing 23 changed files with 876 additions and 258 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import pytest
import os
from lib389.topologies import topology_st
from lib389.password_plugins import PBKDF2Plugin
from lib389.utils import ds_is_older
from lib389.migrate.openldap.config import olConfig
from lib389.migrate.openldap.config import olOverlayType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import pytest
import os
from lib389.topologies import topology_st
from lib389.password_plugins import PBKDF2Plugin
from lib389.utils import ds_is_older
from lib389.migrate.openldap.config import olConfig
from lib389.migrate.openldap.config import olOverlayType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import pytest
import os
from lib389.topologies import topology_st
from lib389.password_plugins import PBKDF2Plugin
from lib389.utils import ds_is_older
from lib389.migrate.openldap.config import olConfig
from lib389.migrate.openldap.config import olOverlayType
Expand Down
1 change: 0 additions & 1 deletion dirsrvtests/tests/suites/openldap_2_389/migrate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import pytest
import os
from lib389.topologies import topology_st
from lib389.password_plugins import PBKDF2Plugin
from lib389.utils import ds_is_older
from lib389.migrate.openldap.config import olConfig
from lib389.migrate.openldap.config import olOverlayType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#
import pytest
from lib389.topologies import topology_st
from lib389.password_plugins import PBKDF2Plugin
from lib389.password_plugins import PBKDF2SHA512Plugin
from lib389.utils import ds_is_older

pytestmark = pytest.mark.tier1
Expand All @@ -35,18 +35,18 @@ def test_pbkdf2_upgrade(topology_st):
"""
# Remove the pbkdf2 plugin config
p1 = PBKDF2Plugin(topology_st.standalone)
p1 = PBKDF2SHA512Plugin(topology_st.standalone)
assert(p1.exists())
p1._protected = False
p1.delete()
# Restart
topology_st.standalone.restart()
# check it's been readded.
p2 = PBKDF2Plugin(topology_st.standalone)
p2 = PBKDF2SHA512Plugin(topology_st.standalone)
assert(p2.exists())
# Now restart to make sure we still work from the non-bootstrap form
topology_st.standalone.restart()
p3 = PBKDF2Plugin(topology_st.standalone)
p3 = PBKDF2SHA512Plugin(topology_st.standalone)
assert(p3.exists())


173 changes: 164 additions & 9 deletions dirsrvtests/tests/suites/pwp_storage/storage_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,50 @@

from lib389.topologies import topology_st as topo
from lib389.idm.user import UserAccounts, UserAccount
from lib389._constants import DEFAULT_SUFFIX
from lib389._constants import DEFAULT_SUFFIX, DN_DM, PASSWORD, ErrorLog
from lib389.config import Config
from lib389.password_plugins import PBKDF2Plugin, SSHA512Plugin
from lib389.password_plugins import (
SSHA512Plugin,
PBKDF2Plugin,
PBKDF2SHA1Plugin,
PBKDF2SHA256Plugin,
PBKDF2SHA512Plugin
)
from lib389.utils import ds_is_older

pytestmark = pytest.mark.tier1


@pytest.fixture(scope="function")
def test_user(request, topo):
"""Fixture to create and clean up a test user for each test"""
# Generate unique user ID based on test name
uid = f'test_user_{request.node.name[:20]}'

# Create user
users = UserAccounts(topo.standalone, DEFAULT_SUFFIX)
user = users.create(properties={
'uid': uid,
'cn': 'Test User',
'sn': 'User',
'uidNumber': '1000',
'gidNumber': '2000',
'homeDirectory': f'/home/{uid}'
})

def fin():
try:
# Ensure we're bound as DM before cleanup
topo.standalone.simple_bind_s(DN_DM, PASSWORD)
if user.exists():
user.delete()
except Exception as e:
log.error(f"Error during user cleanup: {e}")

request.addfinalizer(fin)
return user


def user_config(topo, field_value):
"""
Will set storage schema and create user.
Expand Down Expand Up @@ -106,27 +142,27 @@ def test_check_two_scheme(topo):
user.delete()

@pytest.mark.skipif(ds_is_older('1.4'), reason="Not implemented")
def test_check_pbkdf2_sha256(topo):
"""Check password scheme PBKDF2_SHA256.
def test_check_pbkdf2_sha512(topo):
"""Check password scheme PBKDF2-SHA512.
:id: 31612e7e-33a6-11ea-a750-8c16451d917b
:setup: Standalone
:steps:
1. Try to delete PBKDF2_SHA256.
2. Should not deleted PBKDF2_SHA256 and server should up.
1. Try to delete PBKDF2-SHA512.
2. Should not deleted PBKDF2-SHA512 and server should up.
:expectedresults:
1. Pass
2. Pass
"""
value = 'PBKDF2_SHA256'
value = 'PBKDF2-SHA512'
user = user_config(topo, value)
assert '{' + f'{value.lower()}' + '}' in \
UserAccount(topo.standalone, user.dn).get_attr_val_utf8('userpassword').lower()
plg = PBKDF2Plugin(topo.standalone)
plg = PBKDF2SHA512Plugin(topo.standalone)
plg._protected = False
plg.delete()
topo.standalone.restart()
assert Config(topo.standalone).get_attr_val_utf8('passwordStorageScheme') == 'PBKDF2_SHA256'
assert Config(topo.standalone).get_attr_val_utf8('passwordStorageScheme') == 'PBKDF2-SHA512'
assert topo.standalone.status()
user.delete()

Expand Down Expand Up @@ -160,6 +196,125 @@ def test_check_ssha512(topo):
user.delete()


@pytest.mark.parametrize('plugin_class,plugin_name', [
(PBKDF2Plugin, 'PBKDF2'),
(PBKDF2SHA1Plugin, 'PBKDF2-SHA1'),
(PBKDF2SHA256Plugin, 'PBKDF2-SHA256'),
(PBKDF2SHA512Plugin, 'PBKDF2-SHA512')
])
def test_pbkdf2_rounds_configuration(topo, test_user, plugin_class, plugin_name):
"""Test PBKDF2 rounds configuration for different variants"""
try:
# Enable plugin logging
topo.standalone.config.loglevel((ErrorLog.DEFAULT, ErrorLog.PLUGIN))

# Configure plugin
plugin = plugin_class(topo.standalone)
plugin.enable()

# Test rounds configuration
test_rounds = 20000
plugin.set_rounds(test_rounds)
# Restart after changing rounds
topo.standalone.restart()
assert plugin.get_rounds() == test_rounds

# Verify invalid rounds are rejected
with pytest.raises(ValueError):
plugin.set_rounds(5000) # Too low
with pytest.raises(ValueError):
plugin.set_rounds(2000000) # Too high

# Configure as password storage scheme
topo.standalone.config.replace('passwordStorageScheme', plugin_name)
topo.standalone.deleteErrorLogs()

# PBKDF2-SHA1 is the actual digest used for PBKDF2
plugin_name = 'PBKDF2-SHA1' if plugin_name == 'PBKDF2' else plugin_name
digest_name = plugin_name.split('-')[1] if '-' in plugin_name else 'SHA1'

TEST_PASSWORD = 'Secret123'
test_user.set('userPassword', TEST_PASSWORD)

# Verify password hash format
pwd_hash = test_user.get_attr_val_utf8('userPassword')
assert pwd_hash.startswith('{' + plugin_name.upper() + '}')

# Test authentication
topo.standalone.simple_bind_s(test_user.dn, TEST_PASSWORD)
topo.standalone.simple_bind_s(DN_DM, PASSWORD)

# Restart to flush logs
topo.standalone.restart()

# Verify logs for configuration message
assert topo.standalone.searchErrorsLog(
f'handle_pbkdf2_rounds_config -> Number of iterations for PBKDF2-{digest_name} password scheme set to {test_rounds}'
)

finally:
# Always rebind as Directory Manager
topo.standalone.simple_bind_s(DN_DM, PASSWORD)


@pytest.mark.parametrize('plugin_class,plugin_name', [
(PBKDF2Plugin, 'PBKDF2'),
(PBKDF2SHA1Plugin, 'PBKDF2-SHA1'),
(PBKDF2SHA256Plugin, 'PBKDF2-SHA256'),
(PBKDF2SHA512Plugin, 'PBKDF2-SHA512')
])
def test_pbkdf2_rounds_modification(topo, test_user, plugin_class, plugin_name):
"""Test PBKDF2 rounds modification behavior"""
try:
# Enable plugin logging
topo.standalone.config.loglevel((ErrorLog.DEFAULT, ErrorLog.PLUGIN))

plugin = plugin_class(topo.standalone)
plugin.enable()

# Set initial rounds and restart
initial_rounds = 15000
plugin.set_rounds(initial_rounds)
topo.standalone.restart()

# Configure as password storage scheme
topo.standalone.config.replace('passwordStorageScheme', plugin_name)
topo.standalone.deleteErrorLogs()

# PBKDF2-SHA1 is the actual digest used for PBKDF2
plugin_name = 'PBKDF2-SHA1' if plugin_name == 'PBKDF2' else plugin_name
digest_name = plugin_name.split('-')[1] if '-' in plugin_name else 'SHA1'

INITIAL_PASSWORD = 'Initial123'
NEW_PASSWORD = 'New123'

test_user.set('userPassword', INITIAL_PASSWORD)

# Modify rounds and restart
new_rounds = 25000
plugin.set_rounds(new_rounds)
topo.standalone.restart()

# Verify old password still works
topo.standalone.simple_bind_s(test_user.dn, INITIAL_PASSWORD)
topo.standalone.simple_bind_s(DN_DM, PASSWORD)

# Set new password
test_user.set('userPassword', NEW_PASSWORD)

# Verify new password works
topo.standalone.simple_bind_s(test_user.dn, NEW_PASSWORD)
topo.standalone.simple_bind_s(DN_DM, PASSWORD)

# Verify logs for configuration message
assert topo.standalone.searchErrorsLog(
f'handle_pbkdf2_rounds_config -> Number of iterations for PBKDF2-{digest_name} password scheme set to {new_rounds}'
)

finally:
# Always rebind as Directory Manager
topo.standalone.simple_bind_s(DN_DM, PASSWORD)

if __name__ == "__main__":
CURRENT_FILE = os.path.realpath(__file__)
pytest.main("-s -v %s" % CURRENT_FILE)
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
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: pwdPBKDF2PluginConfig
cn: PBKDF2-SHA512
nsslapd-pluginpath: libpwdchan-plugin
nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init
Expand Down
2 changes: 2 additions & 0 deletions ldap/schema/01core389.ldif
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2391 NAME 'dsEntryDN' DESC '389 Director
attributeTypes: ( 2.16.840.1.113730.3.1.2392 NAME 'nsslapd-return-original-entrydn' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2393 NAME 'nsslapd-auditlog-display-attrs' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2398 NAME 'nsslapd-haproxy-trusted-ip' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN '389 Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2400 NAME 'nsslapd-pwdPBKDF2NumIterations' DESC '389 Directory Server defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE X-ORIGIN 'Directory Server' )
#
# objectclasses
#
Expand All @@ -353,3 +354,4 @@ objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netsc
objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.337 NAME 'rewriterEntry' DESC '' SUP top MUST ( nsslapd-libPath ) MAY ( cn $ nsslapd-filterrewriter $ nsslapd-returnedAttrRewriter ) X-ORIGIN '389 Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.340 NAME 'pwdPBKDF2PluginConfig' DESC 'PBKDF2 Password Storage Plugin configuration' SUP top MAY ( nsslapd-pwdPBKDF2NumIterations ) X-ORIGIN '389 Directory Server' )
1 change: 1 addition & 0 deletions ldap/servers/slapd/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static char *bootstrap_plugins[] = {
"dn: cn=PBKDF2-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config\n"
"objectclass: top\n"
"objectclass: nsSlapdPlugin\n"
"objectClass: pwdPBKDF2PluginConfig\n"
"cn: PBKDF2-SHA512\n"
"nsslapd-pluginpath: libpwdchan-plugin\n"
"nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init\n"
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: pwdPBKDF2PluginConfig\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: pwdPBKDF2PluginConfig\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: pwdPBKDF2PluginConfig\n"
"cn: PBKDF2-SHA256\n"
"nsslapd-pluginpath: libpwdchan-plugin\n"
"nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init\n"
Expand Down
Loading

0 comments on commit d1025bd

Please sign in to comment.