We currently have a Keycloak realm where we want to export all existing users into LDAP (with their passwords intact), so we can add login support for other locations that don't support OpenID Connect or SAML, but do support LDAP.
Given that password credentials are stored in the Keycloak MySQL user database using the pbkdf2-sha256 algorithm, I had to use OpenLDAP 2.4.47 with the contrib modules to support this on the LDAP side as well (I also believe I've enabled this module in the configuration).
I'm having trouble transferring the existing has (from Keycloak) to LDAP using the format described here: https://github.com/hamano/openldap-pbkdf2
Forgive me for using PHP, but it's the programming language I'm most comfortable with at this time.
I've tried using the HASH_ITERATIONS, SALT, and VALUE fields, along with the base64url_encode custom function provided in the comments under base64_encode in the PHP manual, to make an "Adapted Base64" string, and provide the built string as the userPassword field when creating an LDAP user. The user is successfully created in LDAP, but I'm unable to authenticate against LDAP using the credentials for the user I've just created.
/*
This example migrates a single user, to be adapted to a loop later, if it works.
NOTE: KC_* constants are defined in an external config.php file, such as:
KC_LDAP_BASEDN = "ou=users,dc=sso,dc=example,dc=com"
KC_REALM_ID = "KeycloakUserRealm"
(not actual code from config.php, but you get the point)
*/
$ldc = ldap_connect(KC_LDAP_SERVER);
ldap_set_option($ldc, LDAP_OPT_PROTOCOL_VERSION, KC_LDAP_PROTOCOL); // should be 3 in config.php
$ldb = ldap_bind($ldc, KC_LDAP_BINDDN, KC_LDAP_BINDPW);
$usertest = 'username_of_user_to_be_migrated';
// fetch user info
$u_sel = $db->query("SELECT * FROM `USER_ENTITY` WHERE `USERNAME`='".$db->real_escape_string($usertest)."' AND `REALM_ID`='".$db->real_escape_string(KC
_REALM_ID)."'");
$uinfo = $u_sel->fetch_assoc();
// fetch credential of user, type="password"
$c_sel = $db->query("SELECT * FROM `CREDENTIAL` WHERE `USER_ID`='".$db->real_escape_string($uinfo['ID'])."' AND `TYPE`='password'");
$cred = $c_sel->fetch_assoc();
$uprop = array();
$uprop['objectClass'] = array('top', 'person', 'organizationalPerson', 'inetOrgPerson');
$uprop['uid'] = $uinfo['USERNAME'];
$uprop['mail'] = $uinfo['EMAIL'];
$uprop['cn'] = $uinfo['FIRST_NAME'];
$uprop['sn'] = $uinfo['LAST_NAME'];
// also base64_decode() VALUE, since this seems to be already enocoded in base64, before re-encoding it with base64url_encode()
$uprop['userPassword'] = '{'.strtoupper($cred['ALGORITHM']).'}'.$cred['HASH_ITERATIONS'].'$'.base64url_encode($cred['SALT']).'$'.base64url_encode(base6
4_decode($cred['VALUE']));
ldap_add($ldc, 'uid='.$uprop['uid'].','.KC_LDAP_BASEDN, $uprop);
Once this is done, I run a second script to attempt connecting with this user:
$ldc = ldap_connect(KC_LDAP_SERVER);
ldap_set_option($ldc, LDAP_OPT_PROTOCOL_VERSION, KC_LDAP_PROTOCOL); // should be 3 in config.php
$userdn = 'uid=username_of_user_to_be_migrated,'.KC_LDAP_BASEDN;
$userpw = 'asdASD123';
$ldb = ldap_bind($ldc, $userdn, $userpw);
When doing the second script above, I just get:
PHP Warning: ldap_bind(): Unable to bind to server: Invalid credentials in /home/user/projectdir/test_ldap_user.php on line 22
Example entry in the CREDENTIAL database looks something like this:
ID: 5718a65c-1927-4ac7-87ce-aec0c7dda296
DEVICE: NULL
HASH_ITERATIONS: 27500
SALT: � �??�Pz�e��X,
TYPE: password
VALUE: DdCJAJvuhidAC2by7TZY8I0E8HF4V6FXrPa4nSXduvSzbb+xHW3D4QiiiPpvuzL2bdk6k0kNQKS/477k5kiLzA==
USER_ID: b06ce13f-4e8e-474e-b5ee-5d664d6f9575
CREATED_DATE: 1561051801144
COUNTER: 0
DIGITS: 0
PERIOD: 0
ALGORITHM: pbkdf2-sha256
The output of the userPassword is usually something like this:
{PBKDF2-SHA256}27500$FrcRlj8SGJJQet1l9LNYLA$DdCJAJvuhidAC2by7TZY8I0E8HF4V6FXrPa4nSXduvSzbb-xHW3D4QiiiPpvuzL2bdk6k0kNQKS_477k5kiLzA
Is there anything I might be missing from this code to ensure the password hash is properly migrated over?
Or is there already a migration script/solution that tackles Keycloak->LDAP that I haven't found yet?
Thank you in advance for any help or nudge in the right direction.