SEC-694: Add check to LdapShaPasswordEncoder to detect use with non-SHA passwords

http://jira.springframework.org/browse/SEC-694
This commit is contained in:
Luke Taylor 2008-03-05 13:29:26 +00:00
parent 426e526694
commit 5ec8aa797c
3 changed files with 64 additions and 33 deletions

View File

@ -15,8 +15,6 @@
package org.springframework.security.providers.ldap.authenticator; package org.springframework.security.providers.ldap.authenticator;
import org.springframework.security.ldap.LdapDataAccessException;
import org.springframework.security.providers.encoding.PasswordEncoder; import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.providers.encoding.ShaPasswordEncoder; import org.springframework.security.providers.encoding.ShaPasswordEncoder;
@ -85,7 +83,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
try { try {
sha = MessageDigest.getInstance("SHA"); sha = MessageDigest.getInstance("SHA");
} catch (java.security.NoSuchAlgorithmException e) { } catch (java.security.NoSuchAlgorithmException e) {
throw new LdapDataAccessException("No SHA implementation available!", e); throw new IllegalStateException("No SHA implementation available!", e);
} }
sha.update(rawPass.getBytes()); sha.update(rawPass.getBytes());
@ -129,23 +127,44 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
* *
* @return true if they match (independent of the case of the prefix). * @return true if they match (independent of the case of the prefix).
*/ */
public boolean isPasswordValid(String encPass, String rawPass, Object salt) { public boolean isPasswordValid(final String encPass, final String rawPass, Object salt) {
String encPassWithoutPrefix; String prefix = extractPrefix(encPass);
if (!encPass.startsWith("{")) { if (prefix == null) {
return encPass.equals(rawPass); return encPass.equals(rawPass);
} }
if (encPass.startsWith(SSHA_PREFIX) || encPass.startsWith(SSHA_PREFIX_LC)) { if (prefix.equals(SSHA_PREFIX) || prefix.equals(SSHA_PREFIX_LC)) {
encPassWithoutPrefix = encPass.substring(6);
salt = extractSalt(encPass); salt = extractSalt(encPass);
} else if (!prefix.equals(SHA_PREFIX) && !prefix.equals(SHA_PREFIX_LC)) {
throw new IllegalArgumentException("Unsupported password prefix '" + prefix + "'");
} else { } else {
encPassWithoutPrefix = encPass.substring(5); // Standard SHA
salt = null; salt = null;
} }
// Compare the encoded passwords without the prefix int startOfHash = prefix.length() + 1;
return encodePassword(rawPass, salt).endsWith(encPassWithoutPrefix);
String encodedRawPass = encodePassword(rawPass, salt).substring(startOfHash);
return encodedRawPass.equals(encPass.substring(startOfHash));
}
/**
* Returns the hash prefix or null if there isn't one.
*/
private String extractPrefix(String encPass) {
if (!encPass.startsWith("{")) {
return null;
}
int secondBrace = encPass.lastIndexOf('}');
if (secondBrace < 0) {
throw new IllegalArgumentException("Couldn't find closing brace for SHA prefix");
}
return encPass.substring(0, secondBrace + 1);
} }
public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) { public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) {

View File

@ -35,17 +35,11 @@ import java.util.Iterator;
/** /**
* An {@link org.springframework.security.providers.ldap.LdapAuthenticator LdapAuthenticator} which compares the login * An {@link org.springframework.security.providers.ldap.LdapAuthenticator LdapAuthenticator} which compares the login
* password with the value stored in the directory using an LDAP "compare" operation. * password with the value stored in the directory using a remote LDAP "compare" operation.
* *
* <p> * <p>
* This can be achieved either by retrieving the password attribute for the user and comparing it locally,
* or by peforming an LDAP "compare" operation. If the password attribute (default "userPassword") is found in the
* retrieved attributes it will be compared locally. If not, the remote comparison will be attempted.
* </p>
* <p>
* If passwords are stored in digest form in the repository, then a suitable {@link PasswordEncoder} * If passwords are stored in digest form in the repository, then a suitable {@link PasswordEncoder}
* implementation must be supplied. By default, passwords are encoded using the {@link LdapShaPasswordEncoder}. * implementation must be supplied. By default, passwords are encoded using the {@link LdapShaPasswordEncoder}.
* </p>
* *
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$

View File

@ -15,7 +15,10 @@
package org.springframework.security.providers.ldap.authenticator; package org.springframework.security.providers.ldap.authenticator;
import junit.framework.TestCase; import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
/** /**
@ -24,37 +27,39 @@ import junit.framework.TestCase;
* @author Luke Taylor * @author Luke Taylor
* @version $Id$ * @version $Id$
*/ */
public class LdapShaPasswordEncoderTests extends TestCase { public class LdapShaPasswordEncoderTests {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
LdapShaPasswordEncoder sha; LdapShaPasswordEncoder sha;
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
protected void setUp() throws Exception { @Before
super.setUp(); public void setUp() throws Exception {
sha = new LdapShaPasswordEncoder(); sha = new LdapShaPasswordEncoder();
} }
public void testInvalidPasswordFails() { @Test
public void invalidPasswordFails() {
assertFalse(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "wrongpassword", null)); assertFalse(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "wrongpassword", null));
} }
public void testInvalidSaltedPasswordFails() { @Test
public void invalidSaltedPasswordFails() {
assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "wrongpassword", null)); assertFalse(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "wrongpassword", null));
assertFalse(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "wrongpassword", null)); assertFalse(sha.isPasswordValid("{SSHA}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "wrongpassword", null));
} }
public void testNonByteArraySaltThrowsException() { @Test(expected=IllegalArgumentException.class)
try { public void nonByteArraySaltThrowsException() {
sha.encodePassword("password", "AStringNotAByteArray"); sha.encodePassword("password", "AStringNotAByteArray");
} catch (IllegalArgumentException expected) {}
} }
/** /**
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd' * Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
*/ */
public void testValidPasswordSucceeds() { @Test
public void validPasswordSucceeds() {
sha.setForceLowerCasePrefix(false); sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null));
@ -66,7 +71,8 @@ public class LdapShaPasswordEncoderTests extends TestCase {
/** /**
* Test values generated by 'slappasswd -s boabspasswurd' * Test values generated by 'slappasswd -s boabspasswurd'
*/ */
public void testValidSaltedPasswordSucceeds() { @Test
public void validSaltedPasswordSucceeds() {
sha.setForceLowerCasePrefix(false); sha.setForceLowerCasePrefix(false);
assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null));
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
@ -75,7 +81,8 @@ public class LdapShaPasswordEncoderTests extends TestCase {
assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null)); assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null));
} }
public void testCorrectPrefixCaseIsUsed() { @Test
public void correctPrefixCaseIsUsed() {
sha.setForceLowerCasePrefix(false); sha.setForceLowerCasePrefix(false);
assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null)); assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null));
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}")); assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}"));
@ -85,4 +92,15 @@ public class LdapShaPasswordEncoderTests extends TestCase {
assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}")); assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}"));
} }
@Test(expected=IllegalArgumentException.class)
public void invalidPrefixIsRejected() {
sha.isPasswordValid("{MD9}xxxxxxxxxx" , "somepassword", null);
}
@Test(expected=IllegalArgumentException.class)
public void malformedPrefixIsRejected() {
// No right brace
sha.isPasswordValid("{SSHA25ro4PKC8jhQZ26jVsozhX/xaP0suHgX" , "somepassword", null);
}
} }