SEC-526: Fixed. Support for different case prefixes ({SHA}, {sha} etc).
This commit is contained in:
parent
0425d3b638
commit
f8689b18b2
|
@ -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
|
* 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 <tt>forceLowerCasePrefix</tt> property to true.
|
||||||
*
|
*
|
||||||
* @author Luke Taylor
|
* @author Luke Taylor
|
||||||
* @version $Id$
|
* @version $Id$
|
||||||
|
@ -40,7 +41,12 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||||
/** The number of bytes in a SHA hash */
|
/** The number of bytes in a SHA hash */
|
||||||
private static final int SHA_LENGTH = 20;
|
private static final int SHA_LENGTH = 20;
|
||||||
private static final String SSHA_PREFIX = "{SSHA}";
|
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 = "{SHA}";
|
||||||
|
private static final String SHA_PREFIX_LC = SHA_PREFIX.toLowerCase();
|
||||||
|
|
||||||
|
//~ Instance fields ================================================================================================
|
||||||
|
private boolean forceLowerCasePrefix;
|
||||||
|
|
||||||
//~ Constructors ===================================================================================================
|
//~ Constructors ===================================================================================================
|
||||||
|
|
||||||
|
@ -76,7 +82,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!");
|
throw new LdapDataAccessException("No SHA implementation available!", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
sha.update(rawPass.getBytes());
|
sha.update(rawPass.getBytes());
|
||||||
|
@ -88,7 +94,15 @@ public class LdapShaPasswordEncoder implements PasswordEncoder {
|
||||||
|
|
||||||
byte[] hash = combineHashAndSalt(sha.digest(), (byte[]) salt);
|
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) {
|
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
|
* Checks the validity of an unencoded password against an encoded one in the form
|
||||||
* "{SSHA}sQuQF8vj8Eg2Y1hPdh3bkQhCKQBgjhQI".
|
* "{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 rawPass unencoded password to be verified.
|
||||||
* @param salt ignored. If the format is SSHA the salt bytes will be extracted from the encoded password.
|
* @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) {
|
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);
|
salt = extractSalt(encPass);
|
||||||
} else {
|
} else {
|
||||||
|
encPassWithoutPrefix = encPass.substring(5);
|
||||||
salt = null;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,14 +55,34 @@ public class LdapShaPasswordEncoderTests extends TestCase {
|
||||||
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
|
* Test values generated by 'slappasswd -h {SHA} -s boabspasswurd'
|
||||||
*/
|
*/
|
||||||
public void testValidPasswordSucceeds() {
|
public void testValidPasswordSucceeds() {
|
||||||
|
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));
|
||||||
|
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'
|
* Test values generated by 'slappasswd -s boabspasswurd'
|
||||||
*/
|
*/
|
||||||
public void testValidSaltedPasswordSucceeds() {
|
public void testValidSaltedPasswordSucceeds() {
|
||||||
|
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));
|
||||||
|
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}"));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue