You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by sz...@apache.org on 2005/11/22 18:06:25 UTC

svn commit: r348196 - in /directory/apacheds/trunk/core/src: main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java

Author: szoerner
Date: Tue Nov 22 09:06:18 2005
New Revision: 348196

URL: http://svn.apache.org/viewcvs?rev=348196&view=rev
Log:
Added binds with passwords stored as one-way encrypted values, e.g. with SHA or MD5.

Added:
    directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java   (with props)
Modified:
    directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java

Modified: directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java?rev=348196&r1=348195&r2=348196&view=diff
==============================================================================
--- directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java (original)
+++ directory/apacheds/trunk/core/src/main/java/org/apache/ldap/server/authn/SimpleAuthenticator.java Tue Nov 22 09:06:18 2005
@@ -16,33 +16,36 @@
  */
 package org.apache.ldap.server.authn;
 
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 import javax.naming.Context;
 import javax.naming.NamingException;
 import javax.naming.directory.Attribute;
 import javax.naming.directory.Attributes;
 
+import org.apache.ldap.common.aci.AuthenticationLevel;
 import org.apache.ldap.common.exception.LdapAuthenticationException;
 import org.apache.ldap.common.name.LdapName;
 import org.apache.ldap.common.util.ArrayUtils;
-import org.apache.ldap.common.aci.AuthenticationLevel;
-import org.apache.ldap.server.jndi.ServerContext;
-import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy;
+import org.apache.ldap.common.util.Base64;
 import org.apache.ldap.server.invocation.Invocation;
 import org.apache.ldap.server.invocation.InvocationStack;
+import org.apache.ldap.server.jndi.ServerContext;
+import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.HashSet;
-import java.util.Collections;
-import java.util.Set;
-import java.util.Collection;
-
-
 /**
  * A simple {@link Authenticator} that authenticates clear text passwords
- * contained within the <code>userPassword</code> attribute in DIT.
- *
+ * contained within the <code>userPassword</code> attribute in DIT. If the
+ * password is stored with a one-way encryption applied (e.g. SHA), the password
+ * is hashed the same way before comparison.
+ * 
  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
  */
 public class SimpleAuthenticator extends AbstractAuthenticator
@@ -75,9 +78,9 @@
 
 
     /**
-     * Looks up <tt>userPassword</tt> attribute of the entry whose name is
-     * the value of {@link Context#SECURITY_PRINCIPAL} environment variable,
-     * and authenticates a user with the plain-text password.
+     * Looks up <tt>userPassword</tt> attribute of the entry whose name is the
+     * value of {@link Context#SECURITY_PRINCIPAL} environment variable, and
+     * authenticates a user with the plain-text password.
      */
     public LdapPrincipal authenticate( ServerContext ctx ) throws NamingException
     {
@@ -121,8 +124,8 @@
 
         try
         {
-            userEntry = proxy.lookup( principalDn, new String[] {"userPassword"}, USERLOOKUP_BYPASS );
-            
+            userEntry = proxy.lookup( principalDn, new String[] { "userPassword" }, USERLOOKUP_BYPASS );
+
             if ( userEntry == null )
             {
                 throw new LdapAuthenticationException( "Failed to lookup user for authentication: " + principal );
@@ -143,25 +146,155 @@
 
         // ---- assert that credentials match
 
-        if ( userPasswordAttr == null )
-        {
+        boolean credentialsMatch = false;
+
+        if (userPasswordAttr == null) {
             userPassword = ArrayUtils.EMPTY_BYTE_ARRAY;
-        }
-        else
-        {
+        } else {
             userPassword = userPasswordAttr.get();
 
-            if ( userPassword instanceof String )
-            {
-                userPassword = ( ( String ) userPassword ).getBytes();
+            if (userPassword instanceof String) {
+                userPassword = ((String) userPassword).getBytes();
             }
         }
 
-        if ( ! ArrayUtils.isEquals( creds, userPassword ) )
-        {
+        // Check if password is stored as a message digest, i.e. one-way
+        // encrypted
+        if (this.isPasswordOneWayEncrypted(userPassword)) {
+            try {
+                // create a corresponding digested password from creds
+                String algorithm = this.getAlgorithmForHashedPassword(userPassword);
+                String digestedCredits = this.createDigestedPassword(algorithm, creds);
+
+                credentialsMatch = ArrayUtils.isEquals(digestedCredits.getBytes(), userPassword);
+            } catch (NoSuchAlgorithmException nsae) {
+                log.warn("Password stored with unknown algorithm.", nsae);
+            } catch (IllegalArgumentException e) {
+                log.warn("Exception during authentication", e);
+            }
+        } else {
+            // password is not stored one-way encrypted
+            credentialsMatch = ArrayUtils.isEquals(creds, userPassword);
+        }
+
+        if (credentialsMatch) {
+            return new LdapPrincipal(principalDn, AuthenticationLevel.SIMPLE);
+        } else {
             throw new LdapAuthenticationException();
         }
+    }
+
+    /**
+     * Checks if the argument is one-way encryped. If it is a string or a
+     * byte-array which looks like "{XYZ}...", and XYZ is a known lessage
+     * digest, the method returns true. The method does not throw an exception
+     * otherwise, e.g. if the algorithm XYZ is not known to the runtime.
+     * 
+     * @param password
+     *            agument, either a string or a byte-array
+     * @return true, if the value is a digested password with algorithm included
+     */
+    protected boolean isPasswordOneWayEncrypted(Object password)
+    {
+        boolean result = false;
+        try {
+            String algorithm = getAlgorithmForHashedPassword(password);
+            result = (algorithm != null);
+        } catch (IllegalArgumentException ignored) {
+        }
+        return result;
+    }
+
+    /**
+     * Get the algorithm of a password, which is stored in the form "{XYZ}...".
+     * The method returns null, if the argument is not in this form. It returns
+     * XYZ, if XYZ is an algorithm known to the MessageDigest class of
+     * java.security.
+     * 
+     * @param password,
+     *            either a String or a byte[]
+     * @return included message digest alorithm, if any
+     */
+    protected String getAlgorithmForHashedPassword(Object password) throws IllegalArgumentException
+    {
+        String result = null;
+
+        // Check if password arg is string or byte[]
+        String sPassword = null;
+        if (password instanceof byte[]) {
+            sPassword = new String((byte[]) password);
+        } else if (password instanceof String) {
+            sPassword = (String) password;
+        } else {
+            throw new IllegalArgumentException("password is neither a String nor a byte-Array.");
+        }
+
+        if (sPassword != null && sPassword.length() > 2 && sPassword.charAt(0) == '{' && sPassword.indexOf('}') > -1) {
+            int algPosEnd = sPassword.indexOf('}');
+            String algorithm = sPassword.substring(1, algPosEnd);
+            try {
+                MessageDigest.getInstance(algorithm);
+                result = algorithm;
+            } catch (NoSuchAlgorithmException e) {
+                log.warn("Unknown message digest algorithm in password: " + algorithm, e);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a digested password. For a given hash algorithm and a password
+     * value, the algorithm is applied to the password, and the result is Base64
+     * encoded. The method returns a String which looks like "{XYZ}bbbbbbb",
+     * whereas XYZ is the name of the algorithm, and bbbbbbb is the Base64
+     * encoded value of XYZ applied to the password.
+     * 
+     * @param algorithm
+     *            an algorithm which is supported by
+     *            java.security.MessageDigest, e.g. SHA
+     * @param password
+     *            password value, either a string or a byte[]
+     * 
+     * @return a digested password, which looks like
+     *         {SHA}LhkDrSoM6qr0fW6hzlfOJQW61tc=
+     * 
+     * @throws IllegalArgumentException
+     *             if password is neither a String nor a byte[], or algorithm is
+     *             not known to java.security.MessageDigest class
+     */
+    protected String createDigestedPassword(String algorithm, Object password) throws NoSuchAlgorithmException,
+            IllegalArgumentException
+    {
+        // Check if password arg is string or byte[]
+        byte[] data = null;
+        if (password instanceof byte[]) {
+            data = (byte[]) password;
+        } else if (password instanceof String) {
+            data = ((String) password).getBytes();
+        } else {
+            throw new IllegalArgumentException("password is neither a String nor a byte-Array.");
+        }
+
+        // create message digest object
+        MessageDigest digest = null;
+        try {
+            digest = MessageDigest.getInstance(algorithm);
+        } catch (NoSuchAlgorithmException nsae) {
+            throw new IllegalArgumentException(nsae);
+        }
+
+        // calculate hashed value of password
+        byte[] fingerPrint = digest.digest(data);
+        char[] encoded = Base64.encode(fingerPrint);
+
+        // create return result of form "{alg}bbbbbbb"
+        StringBuffer result = new StringBuffer();
+        result.append('{');
+        result.append(algorithm);
+        result.append('}');
+        result.append(encoded);
 
-        return new LdapPrincipal( principalDn, AuthenticationLevel.SIMPLE );
+        return result.toString();
     }
 }

Added: directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java
URL: http://svn.apache.org/viewcvs/directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java?rev=348196&view=auto
==============================================================================
--- directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java (added)
+++ directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java Tue Nov 22 09:06:18 2005
@@ -0,0 +1,62 @@
+/*
+ *   Copyright 2004 The Apache Software Foundation
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+
+package org.apache.ldap.server.authn;
+
+import java.security.NoSuchAlgorithmException;
+
+import junit.framework.TestCase;
+
+/**
+ * Test case for helper methods within SimpleAuthenticator.
+ * 
+ * @author Apache Directory Project (dev@directory.apache.org)
+ */
+public class SimpleAuthenticatorOneWayEncryptedTest extends TestCase
+{
+    private SimpleAuthenticator auth = null;
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        this.auth = new SimpleAuthenticator();
+    }
+
+    public void testGetAlgorithmForHashedPassword()
+    {
+        String digestetValue = "{SHA}LhkDrSoM6qr0fW6hzlfOJQW61tc=";
+        assertEquals("SHA", auth.getAlgorithmForHashedPassword(digestetValue));
+        assertEquals("SHA", auth.getAlgorithmForHashedPassword(digestetValue.getBytes()));
+
+        String noAlgorithm = "Secret1!";
+        assertEquals(null, auth.getAlgorithmForHashedPassword(noAlgorithm));
+        assertEquals(null, auth.getAlgorithmForHashedPassword(noAlgorithm.getBytes()));
+
+        String unknownAlgorithm = "{XYZ}LhkDrSoM6qr0fW6hzlfOJQW61tc=";
+        assertEquals(null, auth.getAlgorithmForHashedPassword(unknownAlgorithm));
+        assertEquals(null, auth.getAlgorithmForHashedPassword(unknownAlgorithm.getBytes()));
+    }
+
+    public void testCreateDigestedPassword() throws NoSuchAlgorithmException
+    {
+        String pwd = "Secret1!";
+        String expected = "{SHA}znbJr3+tymFoQD4+Njh4ITtI7Cc=";
+        String digested = auth.createDigestedPassword("SHA", pwd);
+
+        assertEquals(expected, digested);
+    }
+}
\ No newline at end of file

Propchange: directory/apacheds/trunk/core/src/test/java/org/apache/ldap/server/authn/SimpleAuthenticatorOneWayEncryptedTest.java
------------------------------------------------------------------------------
    svn:keywords = HeadURL Id LastChangedBy LastChangedDate LastChangedRevision