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