From f8689b18b2d9fa70f187039ec0634cb8d0c699c8 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Mon, 27 Aug 2007 16:23:14 +0000 Subject: [PATCH] SEC-526: Fixed. Support for different case prefixes ({SHA}, {sha} etc). --- .../authenticator/LdapShaPasswordEncoder.java | 37 +++++++++++++++---- .../LdapShaPasswordEncoderTests.java | 22 ++++++++++- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java index 396a036bb6..1c650b5f1e 100644 --- a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java +++ b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoder.java @@ -29,7 +29,8 @@ import java.security.MessageDigest; /** * A version of {@link ShaPasswordEncoder} which supports Ldap SHA and SSHA (salted-SHA) encodings. The values are - * base-64 encoded and have the label "{SHA}" (or "{SSHA}") prepended to the encoded hash. + * base-64 encoded and have the label "{SHA}" (or "{SSHA}") prepended to the encoded hash. These can be made lower-case + * in the encoded password, if required, by setting the forceLowerCasePrefix property to true. * * @author Luke Taylor * @version $Id$ @@ -40,7 +41,12 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { /** The number of bytes in a SHA hash */ private static final int SHA_LENGTH = 20; private static final String SSHA_PREFIX = "{SSHA}"; + private static final String SSHA_PREFIX_LC = SSHA_PREFIX.toLowerCase(); private static final String SHA_PREFIX = "{SHA}"; + private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase(); + + //~ Instance fields ================================================================================================ + private boolean forceLowerCasePrefix; //~ Constructors =================================================================================================== @@ -76,7 +82,7 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { try { sha = MessageDigest.getInstance("SHA"); } catch (java.security.NoSuchAlgorithmException e) { - throw new LdapDataAccessException("No SHA implementation available!"); + throw new LdapDataAccessException("No SHA implementation available!", e); } sha.update(rawPass.getBytes()); @@ -88,7 +94,15 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt); - return ((salt == null) ? SHA_PREFIX : SSHA_PREFIX) + new String(Base64.encodeBase64(hash)); + String prefix; + + if (salt == null) { + prefix = forceLowerCasePrefix ? SHA_PREFIX_LC : SHA_PREFIX; + } else { + prefix = forceLowerCasePrefix ? SSHA_PREFIX_LC : SSHA_PREFIX; + } + + return prefix + new String(Base64.encodeBase64(hash)); } private byte[] extractSalt(String encPass) { @@ -106,19 +120,28 @@ public class LdapShaPasswordEncoder implements PasswordEncoder { * Checks the validity of an unencoded password against an encoded one in the form * "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI". * - * @param encPass the SSHA or SHA encoded password + * @param encPass the actual SSHA or SHA encoded password * @param rawPass unencoded password to be verified. * @param salt ignored. If the format is SSHA the salt bytes will be extracted from the encoded password. * - * @return true if they match. + * @return true if they match (independent of the case of the prefix). */ public boolean isPasswordValid(String encPass, String rawPass, Object salt) { - if (encPass.startsWith(SSHA_PREFIX)) { + String encPassWithoutPrefix; + + if (encPass.startsWith(SSHA_PREFIX) || encPass.startsWith(SSHA_PREFIX_LC)) { + encPassWithoutPrefix = encPass.substring(6); salt = extractSalt(encPass); } else { + encPassWithoutPrefix = encPass.substring(5); salt = null; } - return encPass.equals(encodePassword(rawPass, salt)); + // Compare the encoded passwords without the prefix + return encodePassword(rawPass, salt).endsWith(encPassWithoutPrefix); + } + + public void setForceLowerCasePrefix(boolean forceLowerCasePrefix) { + this.forceLowerCasePrefix = forceLowerCasePrefix; } } diff --git a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoderTests.java b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoderTests.java index 2dd3cd388f..7fc35ea1b9 100644 --- a/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoderTests.java +++ b/core/src/test/java/org/acegisecurity/providers/ldap/authenticator/LdapShaPasswordEncoderTests.java @@ -55,14 +55,34 @@ public class LdapShaPasswordEncoderTests extends TestCase { * Test values generated by 'slappasswd -h {SHA} -s boabspasswurd' */ public void testValidPasswordSucceeds() { + sha.setForceLowerCasePrefix(false); assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); + assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); + sha.setForceLowerCasePrefix(true); + assertTrue(sha.isPasswordValid("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); + assertTrue(sha.isPasswordValid("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", "boabspasswurd", null)); } /** * Test values generated by 'slappasswd -s boabspasswurd' */ public void testValidSaltedPasswordSucceeds() { + sha.setForceLowerCasePrefix(false); 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)); + sha.setForceLowerCasePrefix(true); + assertTrue(sha.isPasswordValid("{SSHA}25ro4PKC8jhQZ26jVsozhX/xaP0suHgX", "boabspasswurd", null)); + assertTrue(sha.isPasswordValid("{ssha}PQy2j+6n5ytA+YlAKkM8Fh4p6u2JxfVd", "boabspasswurd", null)); + } + + public void testCorrectPrefixCaseIsUsed() { + sha.setForceLowerCasePrefix(false); + assertEquals("{SHA}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null)); + assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{SSHA}")); + + sha.setForceLowerCasePrefix(true); + assertEquals("{sha}ddSFGmjXYPbZC+NXR2kCzBRjqiE=", sha.encodePassword("boabspasswurd", null)); + assertTrue(sha.encodePassword("somepassword", "salt".getBytes()).startsWith("{ssha}")); + } }