Full LDAP bind support
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 14 Nov 2017 15:13:59 +0000 (16:13 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 14 Nov 2017 15:13:59 +0000 (16:13 +0100)
demo/argeo_node_rap.properties
org.argeo.cms/src/org/argeo/cms/auth/AuthenticatingUser.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/UserAdminLoginModule.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/DigestUtils.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdapUserAdmin.java
org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java

index d7149b96679b8b78707a8a6aa71b6465009e140f..a9f8f01d7ebbcd305879e750c3aca0d35638bea4 100644 (file)
@@ -5,7 +5,6 @@ org.eclipse.equinox.metatype,\
 org.eclipse.equinox.cm,\
 org.eclipse.rap.rwt.osgi
 
-
 argeo.osgi.start.3.node=\
 org.argeo.cms
 
@@ -23,9 +22,7 @@ java.security.policy=file:../../all.policy
 
 argeo.node.repo.type=h2
 
-#argeo.node.useradmin.uris=ldap://uid=admin,ou=system:secret@localhost:10389/dc=example,dc=com
-#argeo.node.useradmin.uris=ldap://uid=admin,ou=system:secret@localhost:10389\
-#/dc=example,dc=com?userBase=ou=users&groupBase=ou=groups
+#argeo.node.useradmin.uris=ldap://localhost:10389/dc=example,dc=com
 
 #argeo.node.useradmin.uris="\
 #ldap://uid=admin,ou=system:secret\
@@ -35,10 +32,6 @@ argeo.node.repo.type=h2
 #&userObjectClass=inetOrgPerson \
 #dc=example,dc=org.ldif"
 
-#argeo.node.useradmin.uris="dc=example,dc=com.ldif dc=example,dc=org.ldif"
-
-#sun.security.krb5.debug=true
-
 # HTTP
 org.osgi.service.http.port=7070
 #org.eclipse.equinox.http.jetty.log.stderr.threshold=info
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/AuthenticatingUser.java b/org.argeo.cms/src/org/argeo/cms/auth/AuthenticatingUser.java
deleted file mode 100644 (file)
index 9df88a0..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-package org.argeo.cms.auth;
-
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Dictionary;
-import java.util.Hashtable;
-
-import javax.naming.ldap.LdapName;
-
-import org.osgi.service.useradmin.User;
-
-/**
- * A special user type used during authentication in order to provide the
- * credentials required for scoping the user admin.
- */
-class AuthenticatingUser implements User {
-       private final String name;
-       private final Dictionary<String, Object> credentials;
-
-       public AuthenticatingUser(LdapName name) {
-               this.name = name.toString();
-               this.credentials = new Hashtable<>();
-       }
-
-       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
-               this.name = name;
-               this.credentials = credentials;
-       }
-
-       public AuthenticatingUser(String name, char[] password) {
-               this.name = name;
-               credentials = new Hashtable<>();
-               credentials.put(CmsAuthUtils.SHARED_STATE_NAME, name);
-               byte[] pwd = charsToBytes(password);
-               credentials.put(CmsAuthUtils.SHARED_STATE_PWD, pwd);
-       }
-
-       @Override
-       public String getName() {
-               return name;
-       }
-
-       @Override
-       public int getType() {
-               return User.USER;
-       }
-
-       @SuppressWarnings("rawtypes")
-       @Override
-       public Dictionary getProperties() {
-               throw new UnsupportedOperationException();
-       }
-
-       @SuppressWarnings("rawtypes")
-       @Override
-       public Dictionary getCredentials() {
-               return credentials;
-       }
-
-       @Override
-       public boolean hasCredential(String key, Object value) {
-               throw new UnsupportedOperationException();
-       }
-
-
-       static byte[] charsToBytes(char[] chars) {
-               CharBuffer charBuffer = CharBuffer.wrap(chars);
-               ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
-               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
-               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
-               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
-               return bytes;
-       }
-
-       static char[] bytesToChars(byte[] bytes) {
-               ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
-               CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
-               char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
-               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
-               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
-               return chars;
-       }
-
-
-}
index 65ccbd6aba15c7c2cfa71fcb7f39d7107eb5d7a0..d50535eaefcedaa791f06b93342c94634f68b552 100644 (file)
@@ -23,26 +23,22 @@ import org.argeo.node.NodeConstants;
 import org.argeo.node.security.AnonymousPrincipal;
 import org.argeo.node.security.DataAdminPrincipal;
 import org.argeo.node.security.NodeSecurityUtils;
+import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.osgi.service.http.HttpContext;
 import org.osgi.service.useradmin.Authorization;
 
 class CmsAuthUtils {
-       /** Shared HTTP request */
-       final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
-       /** From org.osgi.service.http.HttpContext */
-       // final static String SHARED_STATE_AUTHORIZATION =
-       // "org.osgi.service.useradmin.authorization";
-       /** From com.sun.security.auth.module.*LoginModule */
-       final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
-       /** From com.sun.security.auth.module.*LoginModule */
-       final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+       // Standard
+       final static String SHARED_STATE_NAME = AuthenticatingUser.SHARED_STATE_NAME;
+       final static String SHARED_STATE_PWD = AuthenticatingUser.SHARED_STATE_PWD;
+       final static String HEADER_AUTHORIZATION = "Authorization";
+       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
 
+       // Argeo specific
+       final static String SHARED_STATE_HTTP_REQUEST = "org.argeo.cms.auth.http.request";
        final static String SHARED_STATE_SPNEGO_TOKEN = "org.argeo.cms.auth.spnegoToken";
        final static String SHARED_STATE_SPNEGO_OUT_TOKEN = "org.argeo.cms.auth.spnegoOutToken";
 
-       final static String HEADER_AUTHORIZATION = "Authorization";
-       final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
-
        static void addAuthorization(Subject subject, Authorization authorization, Locale locale,
                        HttpServletRequest request) {
                assert subject != null;
index 269f509ed45d665ab4bb1b3dbcccefad21712d42..1d91a21ca4802e861671bc9ec0afd3c75ee89b36 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.cms.CmsException;
 import org.argeo.naming.LdapAttrs;
+import org.argeo.osgi.useradmin.AuthenticatingUser;
 import org.argeo.osgi.useradmin.IpaUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -41,14 +42,16 @@ public class UserAdminLoginModule implements LoginModule {
        private CallbackHandler callbackHandler;
        private Map<String, Object> sharedState = null;
 
-       private List<String> indexedUserProperties = Arrays.asList(
-                       new String[] { LdapAttrs.DN, LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
+       private List<String> indexedUserProperties = Arrays
+                       .asList(new String[] { LdapAttrs.mail.name(), LdapAttrs.uid.name(), LdapAttrs.authPassword.name() });
 
        // private state
        private BundleContext bc;
        private User authenticatedUser = null;
        private Locale locale;
 
+       private Authorization bindAuthorization = null;
+
        @SuppressWarnings("unchecked")
        @Override
        public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
@@ -105,14 +108,27 @@ public class UserAdminLoginModule implements LoginModule {
                                throw new CredentialNotFoundException("No credentials provided");
                }
 
-               // User user = userAdmin.getUser(null, username);
                User user = searchForUser(userAdmin, username);
                if (user == null)
                        return true;// expect Kerberos
-               // throw new FailedLoginException("Invalid credentials");
-               if (!user.hasCredential(null, password))
+               
+               // try bind first
+               try {
+                       AuthenticatingUser authenticatingUser = new AuthenticatingUser(user.getName(), password);
+                       bindAuthorization = userAdmin.getAuthorization(authenticatingUser);
+                       // TODO check tokens as well
+                       if (bindAuthorization != null)
+                               return true;
+               } catch (Exception e) {
+                       // silent
+                       if(log.isTraceEnabled())
+                               log.trace("Bind failed", e);
+               }
+               
+               // works only if a connection password is provided
+               if (!user.hasCredential(null, password)) {
                        return false;
-               // throw new FailedLoginException("Invalid credentials");
+               }
                authenticatedUser = user;
                return true;
        }
@@ -123,7 +139,9 @@ public class UserAdminLoginModule implements LoginModule {
                Authorization authorization;
                if (callbackHandler == null) {// anonymous
                        authorization = userAdmin.getAuthorization(null);
-               } else {
+               } else if (bindAuthorization != null) {// bind
+                       authorization = bindAuthorization;
+               } else {// Kerberos
                        User authenticatingUser;
                        Set<KerberosPrincipal> kerberosPrincipals = subject.getPrincipals(KerberosPrincipal.class);
                        if (kerberosPrincipals.isEmpty()) {
@@ -184,23 +202,26 @@ public class UserAdminLoginModule implements LoginModule {
                        Set<User> collectedUsers = new HashSet<>();
                        // try dn
                        User user = null;
-                       try {
-                               user = (User) userAdmin.getRole(providedUsername);
-                               if (user != null)
-                                       collectedUsers.add(user);
-                       } catch (Exception e) {
-                               // silent
-                       }
                        // try all indexes
                        for (String attr : indexedUserProperties) {
                                user = userAdmin.getUser(attr, providedUsername);
                                if (user != null)
                                        collectedUsers.add(user);
                        }
-                       if (collectedUsers.size() == 1)
-                               return collectedUsers.iterator().next();
-                       else if (collectedUsers.size() > 1)
+                       if (collectedUsers.size() == 1) {
+                               user = collectedUsers.iterator().next();
+                               return user;
+                       } else if (collectedUsers.size() > 1) {
                                log.warn(collectedUsers.size() + " users for provided username" + providedUsername);
+                       }
+                       // try DN as a last resort
+                       try {
+                               user = (User) userAdmin.getRole(providedUsername);
+                               if (user != null)
+                                       return user;
+                       } catch (Exception e) {
+                               // silent
+                       }
                        return null;
                } catch (Exception e) {
                        if (log.isTraceEnabled())
index 957a1c953990767effa6ee122db4a01a8fcb59e2..d6135f8e8f4360572beee6530d8ef34acb1bc2e4 100644 (file)
@@ -34,7 +34,6 @@ import javax.transaction.TransactionManager;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.argeo.naming.LdapAttrs;
-import org.argeo.naming.LdapObjs;
 import org.osgi.framework.Filter;
 import org.osgi.framework.FrameworkUtil;
 import org.osgi.framework.InvalidSyntaxException;
@@ -297,6 +296,8 @@ public abstract class AbstractUserDirectory implements UserAdmin, UserDirectory
                        AbstractUserDirectory scopedUserAdmin = scope(user);
                        try {
                                DirectoryUser directoryUser = (DirectoryUser) scopedUserAdmin.getRole(user.getName());
+                               if (directoryUser == null)
+                                       throw new UserDirectoryException("No scoped user found for " + user);
                                LdifAuthorization authorization = new LdifAuthorization(directoryUser,
                                                scopedUserAdmin.getAllRoles(directoryUser));
                                return authorization;
index 93ecdca47d67d596f3fbae0d7d9e7e210d47f72c..2b2ca0c513ba6028e76679f505b63637ccdc76d8 100644 (file)
@@ -118,7 +118,8 @@ public class AggregatingUserAdmin implements UserAdmin {
 
        private UserAdmin findUserAdmin(String name) {
                try {
-                       return findUserAdmin(new LdapName(name));
+                       UserAdmin userAdmin = findUserAdmin(new LdapName(name));
+                       return userAdmin;
                } catch (InvalidNameException e) {
                        throw new UserDirectoryException("Badly formatted name " + name, e);
                }
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/AuthenticatingUser.java
new file mode 100644 (file)
index 0000000..2689d34
--- /dev/null
@@ -0,0 +1,90 @@
+package org.argeo.osgi.useradmin;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import javax.naming.ldap.LdapName;
+
+import org.osgi.service.useradmin.User;
+
+/**
+ * A special user type used during authentication in order to provide the
+ * credentials required for scoping the user admin.
+ */
+public class AuthenticatingUser implements User {
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_NAME = "javax.security.auth.login.name";
+       /** From com.sun.security.auth.module.*LoginModule */
+       public final static String SHARED_STATE_PWD = "javax.security.auth.login.password";
+
+       private final String name;
+       private final Dictionary<String, Object> credentials;
+
+       public AuthenticatingUser(LdapName name) {
+               this.name = name.toString();
+               this.credentials = new Hashtable<>();
+       }
+
+       public AuthenticatingUser(String name, Dictionary<String, Object> credentials) {
+               this.name = name;
+               this.credentials = credentials;
+       }
+
+       public AuthenticatingUser(String name, char[] password) {
+               this.name = name;
+               credentials = new Hashtable<>();
+               credentials.put(SHARED_STATE_NAME, name);
+               byte[] pwd = charsToBytes(password);
+               credentials.put(SHARED_STATE_PWD, pwd);
+       }
+
+       @Override
+       public String getName() {
+               return name;
+       }
+
+       @Override
+       public int getType() {
+               return User.USER;
+       }
+
+       @SuppressWarnings("rawtypes")
+       @Override
+       public Dictionary getProperties() {
+               throw new UnsupportedOperationException();
+       }
+
+       @SuppressWarnings("rawtypes")
+       @Override
+       public Dictionary getCredentials() {
+               return credentials;
+       }
+
+       @Override
+       public boolean hasCredential(String key, Object value) {
+               throw new UnsupportedOperationException();
+       }
+
+       static byte[] charsToBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
+       static char[] bytesToChars(byte[] bytes) {
+               ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+               CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
+               char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
+               Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return chars;
+       }
+
+}
index d8f8ce9d5d87d018a79684507448d157adcabbd3..f6a237bcc0e4e355dd464af70c180c49bfeb45ff 100644 (file)
@@ -1,7 +1,12 @@
 package org.argeo.osgi.useradmin;
 
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
+import java.util.Arrays;
 
+/** Utilities around digests, mostly those related to passwords. */
 class DigestUtils {
        static byte[] sha1(byte[] bytes) {
                try {
@@ -14,6 +19,29 @@ class DigestUtils {
                }
        }
 
+       static char[] bytesToChars(Object obj) {
+               if (obj instanceof char[])
+                       return (char[]) obj;
+               if (!(obj instanceof byte[]))
+                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
+               // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+               // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+               // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+               return res;
+       }
+
+       static byte[] charsToBytes(char[] chars) {
+               CharBuffer charBuffer = CharBuffer.wrap(chars);
+               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+               // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+               return bytes;
+       }
+
        private DigestUtils() {
        }
 
index cf97ebea393e5cbe06a9de38180ec14615d858da..a9e32fae5697708412eee9d953215c202803d843 100644 (file)
@@ -82,16 +82,19 @@ public class LdapUserAdmin extends AbstractUserDirectory {
        @Override
        protected AbstractUserDirectory scope(User user) {
                Dictionary<String, Object> credentials = user.getCredentials();
-               // FIXME use arrays
                String username = (String) credentials.get(SHARED_STATE_USERNAME);
                if (username == null)
                        username = user.getName();
-               // byte[] pwd = (byte[]) credentials.get(SHARED_STATE_PASSWORD);
-               // char[] password = DigestUtils.bytesToChars(pwd);
                Dictionary<String, Object> properties = cloneProperties();
                properties.put(Context.SECURITY_PRINCIPAL, username.toString());
-               // properties.put(Context.SECURITY_CREDENTIALS, password);
-               properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+               Object pwdCred = credentials.get(SHARED_STATE_PASSWORD);
+               byte[] pwd = (byte[]) pwdCred;
+               if (pwd != null) {
+                       char[] password = DigestUtils.bytesToChars(pwd);
+                       properties.put(Context.SECURITY_CREDENTIALS, new String(password));
+               } else {
+                       properties.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
+               }
                return new LdapUserAdmin(properties);
        }
 
index b25ebfc226a6f2ed6c3659de6d9c09ac8c14697c..392b17428b00b748be25de7f2c8b04000438c120 100644 (file)
@@ -1,7 +1,5 @@
 package org.argeo.osgi.useradmin;
 
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -76,7 +74,7 @@ class LdifUser implements DirectoryUser {
                        // TODO check other sources (like PKCS12)
                        // String pwd = new String((char[]) value);
                        // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
-                       char[] password = toChars(value);
+                       char[] password = DigestUtils.bytesToChars(value);
                        AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
                        if (authPassword != null) {
                                if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
@@ -154,35 +152,38 @@ class LdifUser implements DirectoryUser {
 
        /** Hash and clear the password */
        private byte[] hash(char[] password) {
-               byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password))))
-                               .getBytes(StandardCharsets.UTF_8);
+               byte[] hashedPassword = ("{SHA}"
+                               + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
+                                               .getBytes(StandardCharsets.UTF_8);
                // Arrays.fill(password, '\u0000');
                return hashedPassword;
        }
 
-       private byte[] toBytes(char[] chars) {
-               CharBuffer charBuffer = CharBuffer.wrap(chars);
-               ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
-               byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
-               // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
-               Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
-               return bytes;
-       }
-
-       private char[] toChars(Object obj) {
-               if (obj instanceof char[])
-                       return (char[]) obj;
-               if (!(obj instanceof byte[]))
-                       throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
-               ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
-               CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
-               char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(), toBuffer.limit());
-               Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
-               Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
-               Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
-               return res;
-       }
-
+       // private byte[] toBytes(char[] chars) {
+       // CharBuffer charBuffer = CharBuffer.wrap(chars);
+       // ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
+       // byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(),
+       // byteBuffer.limit());
+       // // Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+       // Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+       // return bytes;
+       // }
+       //
+       // private char[] toChars(Object obj) {
+       // if (obj instanceof char[])
+       // return (char[]) obj;
+       // if (!(obj instanceof byte[]))
+       // throw new IllegalArgumentException(obj.getClass() + " is not a byte array");
+       // ByteBuffer fromBuffer = ByteBuffer.wrap((byte[]) obj);
+       // CharBuffer toBuffer = StandardCharsets.UTF_8.decode(fromBuffer);
+       // char[] res = Arrays.copyOfRange(toBuffer.array(), toBuffer.position(),
+       // toBuffer.limit());
+       // Arrays.fill(fromBuffer.array(), (byte) 0); // clear sensitive data
+       // Arrays.fill((byte[]) obj, (byte) 0); // clear sensitive data
+       // Arrays.fill(toBuffer.array(), '\u0000'); // clear sensitive data
+       // return res;
+       // }
+       //
        @Override
        public LdapName getDn() {
                return dn;
@@ -346,7 +347,7 @@ class LdifUser implements DirectoryUser {
                public Object put(String key, Object value) {
                        if (key == null) {
                                // TODO persist to other sources (like PKCS12)
-                               char[] password = toChars(value);
+                               char[] password = DigestUtils.bytesToChars(value);
                                byte[] hashedPassword = hash(password);
                                return put(LdapAttrs.userPassword.name(), hashedPassword);
                        }