]> git.argeo.org Git - lgpl/argeo-commons.git/blobdiff - org.argeo.enterprise/src/org/argeo/osgi/useradmin/LdifUser.java
Move RAP specific to subdirectory
[lgpl/argeo-commons.git] / org.argeo.enterprise / src / org / argeo / osgi / useradmin / LdifUser.java
index d26ed148f0f861aa22ed0661f56339c4d63da381..b3e7f5955579bac5c53dc57c1b8453f0307fa01e 100644 (file)
@@ -1,9 +1,9 @@
 package org.argeo.osgi.useradmin;
 
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
+import static java.nio.charset.StandardCharsets.US_ASCII;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Base64;
@@ -22,7 +22,9 @@ import javax.naming.directory.Attributes;
 import javax.naming.directory.BasicAttribute;
 import javax.naming.ldap.LdapName;
 
+import org.argeo.naming.AuthPassword;
 import org.argeo.naming.LdapAttrs;
+import org.argeo.naming.SharedSecret;
 
 /** Directory user implementation */
 class LdifUser implements DirectoryUser {
@@ -73,9 +75,33 @@ class LdifUser implements DirectoryUser {
        public boolean hasCredential(String key, Object value) {
                if (key == null) {
                        // TODO check other sources (like PKCS12)
-                       char[] password = toChars(value);
-                       byte[] hashedPassword = hash(password);
-                       return hasCredential(LdapAttrs.userPassword.name(), hashedPassword);
+                       // String pwd = new String((char[]) value);
+                       // authPassword (RFC 312 https://tools.ietf.org/html/rfc3112)
+                       char[] password = DigestUtils.bytesToChars(value);
+                       AuthPassword authPassword = AuthPassword.matchAuthValue(getAttributes(), password);
+                       if (authPassword != null) {
+                               if (authPassword.getAuthScheme().equals(SharedSecret.X_SHARED_SECRET)) {
+                                       SharedSecret onceToken = new SharedSecret(authPassword);
+                                       if (onceToken.isExpired()) {
+                                               // AuthPassword.remove(getAttributes(), onceToken);
+                                               return false;
+                                       } else {
+                                               // boolean wasRemoved = AuthPassword.remove(getAttributes(), onceToken);
+                                               return true;
+                                       }
+                                       // TODO delete expired tokens?
+                               } else {
+                                       // TODO implement SHA
+                                       throw new UnsupportedOperationException(
+                                                       "Unsupported authPassword scheme " + authPassword.getAuthScheme());
+                               }
+                       }
+
+                       // Regular password
+//                     byte[] hashedPassword = hash(password, DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256);
+                       if (hasCredential(LdapAttrs.userPassword.name(), DigestUtils.charsToBytes(password)))
+                               return true;
+                       return false;
                }
 
                Object storedValue = getCredentials().get(key);
@@ -85,41 +111,59 @@ class LdifUser implements DirectoryUser {
                        return false;
                if (storedValue instanceof String && value instanceof String)
                        return storedValue.equals(value);
-               if (storedValue instanceof byte[] && value instanceof byte[])
-                       return Arrays.equals((byte[]) storedValue, (byte[]) value);
+               if (storedValue instanceof byte[] && value instanceof byte[]) {
+                       String storedBase64 = new String((byte[]) storedValue, US_ASCII);
+                       String passwordScheme = null;
+                       if (storedBase64.charAt(0) == '{') {
+                               int index = storedBase64.indexOf('}');
+                               if (index > 0) {
+                                       passwordScheme = storedBase64.substring(1, index);
+                                       String storedValueBase64 = storedBase64.substring(index + 1);
+                                       byte[] storedValueBytes = Base64.getDecoder().decode(storedValueBase64);
+                                       char[] passwordValue = DigestUtils.bytesToChars((byte[]) value);
+                                       byte[] valueBytes;
+                                       if (DigestUtils.PASSWORD_SCHEME_SHA.equals(passwordScheme)) {
+                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, null, null, null);
+                                       } else if (DigestUtils.PASSWORD_SCHEME_PBKDF2_SHA256.equals(passwordScheme)) {
+                                               // see https://www.thesubtlety.com/post/a-389-ds-pbkdf2-password-checker/
+                                               byte[] iterationsArr = Arrays.copyOfRange(storedValueBytes, 0, 4);
+                                               BigInteger iterations = new BigInteger(iterationsArr);
+                                               byte[] salt = Arrays.copyOfRange(storedValueBytes, iterationsArr.length,
+                                                               iterationsArr.length + 64);
+                                               byte[] keyArr = Arrays.copyOfRange(storedValueBytes, iterationsArr.length + salt.length,
+                                                               storedValueBytes.length);
+                                               int keyLengthBits = keyArr.length * 8;
+                                               valueBytes = DigestUtils.toPasswordScheme(passwordScheme, passwordValue, salt,
+                                                               iterations.intValue(), keyLengthBits);
+                                       } else {
+                                               throw new UnsupportedOperationException("Unknown password scheme " + passwordScheme);
+                                       }
+                                       return Arrays.equals(storedValueBytes, valueBytes);
+                               }
+                       }
+               }
+//             if (storedValue instanceof byte[] && value instanceof byte[]) {
+//                     return Arrays.equals((byte[]) storedValue, (byte[]) value);
+//             }
                return false;
        }
 
-       /** Hash and clear the password */
-       private byte[] hash(char[] password) {
-               byte[] hashedPassword = ("{SHA}" + Base64.getEncoder().encodeToString(DigestUtils.sha1(toBytes(password))))
-                               .getBytes();
-               Arrays.fill(password, '\u0000');
+       /** Hash the password */
+       byte[] sha1hash(char[] password) {
+               byte[] hashedPassword = ("{SHA}"
+                               + Base64.getEncoder().encodeToString(DigestUtils.sha1(DigestUtils.charsToBytes(password))))
+                                               .getBytes(StandardCharsets.UTF_8);
                return hashedPassword;
        }
 
-       private byte[] toBytes(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;
-       }
-
-       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 = Charset.forName("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;
-       }
+//     byte[] hash(char[] password, String passwordScheme) {
+//             if (passwordScheme == null)
+//                     passwordScheme = DigestUtils.PASSWORD_SCHEME_SHA;
+//             byte[] hashedPassword = ("{" + passwordScheme + "}"
+//                             + Base64.getEncoder().encodeToString(DigestUtils.toPasswordScheme(passwordScheme, password)))
+//                                             .getBytes(US_ASCII);
+//             return hashedPassword;
+//     }
 
        @Override
        public LdapName getDn() {
@@ -255,7 +299,7 @@ class LdifUser implements DirectoryUser {
                                        if (key.equals(LdapAttrs.userPassword.name()))
                                                // TODO other cases (certificates, images)
                                                return value;
-                                       value = new String((byte[]) value, Charset.forName("UTF-8"));
+                                       value = new String((byte[]) value, StandardCharsets.UTF_8);
                                }
                                if (attr.size() == 1)
                                        return value;
@@ -284,10 +328,13 @@ 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);
-                               byte[] hashedPassword = hash(password);
+                               char[] password = DigestUtils.bytesToChars(value);
+                               byte[] hashedPassword = sha1hash(password);
                                return put(LdapAttrs.userPassword.name(), hashedPassword);
                        }
+                       if (key.startsWith("X-")) {
+                               return put(LdapAttrs.authPassword.name(), value);
+                       }
 
                        userAdmin.checkEdit();
                        if (!isEditing())
@@ -303,13 +350,10 @@ class LdifUser implements DirectoryUser {
 
                        try {
                                Attribute attribute = getModifiedAttributes().get(key.toString());
+                               // if (attribute == null) // block unit tests
                                attribute = new BasicAttribute(key.toString());
                                if (value instanceof String && !isAsciiPrintable(((String) value)))
-                                       try {
-                                               attribute.add(((String) value).getBytes("UTF-8"));
-                                       } catch (UnsupportedEncodingException e) {
-                                               throw new UserDirectoryException("Cannot encode " + value, e);
-                                       }
+                                       attribute.add(((String) value).getBytes(StandardCharsets.UTF_8));
                                else
                                        attribute.add(value);
                                Attribute previousAttribute = getModifiedAttributes().put(attribute);