@@ -869,12 +869,14 @@ realm.userService = ${baseFolder}/users.conf | |||
realm.authenticationProviders = | |||
# How to store passwords. | |||
# Valid values are plain, md5, or combined-md5. md5 is the hash of password. | |||
# Valid values are plain, md5, combined-md5 or pbkdf2. | |||
# md5 is the hash of password. | |||
# combined-md5 is the hash of username.toLowerCase()+password. | |||
# Default is md5. | |||
# pbkdf2 implements the PBKDF2 algorithm, which is a secure, salted password hashing scheme. | |||
# Default is pbkdf2. Using plain, md5 or combined-md5 is deprecated, as these are insecure schemes by now. | |||
# | |||
# SINCE 0.5.0 | |||
realm.passwordStorage = md5 | |||
realm.passwordStorage = pbkdf2 | |||
# Minimum valid length for a plain text password. | |||
# Default value is 5. Absolute minimum is 4. |
@@ -58,8 +58,10 @@ import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.ServerSettings; | |||
import com.gitblit.models.TeamModel; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.utils.PasswordHash; | |||
import com.gitblit.utils.StringUtils; | |||
public class EditUserDialog extends JDialog { | |||
private static final long serialVersionUID = 1L; | |||
@@ -317,8 +319,10 @@ public class EditUserDialog extends JDialog { | |||
minLength)); | |||
return false; | |||
} | |||
if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE) | |||
&& !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | |||
// What we actually test for here, is if the password has been changed. But this also catches if the password | |||
// was not changed, but is stored in plain-text. Which is good because then editing the user will hash the | |||
// password if by now the storage has been changed to a hashed variant. | |||
if (!PasswordHash.isHashedEntry(password)) { | |||
String cpw = new String(confirmPasswordField.getPassword()); | |||
if (cpw == null || cpw.length() != password.length()) { | |||
error("Please confirm the password!"); | |||
@@ -332,19 +336,17 @@ public class EditUserDialog extends JDialog { | |||
// change the cookie | |||
user.cookie = user.createCookie(); | |||
String type = settings.get(Keys.realm.passwordStorage).getString("md5"); | |||
if (type.equalsIgnoreCase("md5")) { | |||
// store MD5 digest of password | |||
user.password = StringUtils.MD5_TYPE + StringUtils.getMD5(password); | |||
} else if (type.equalsIgnoreCase("combined-md5")) { | |||
// store MD5 digest of username+password | |||
user.password = StringUtils.COMBINED_MD5_TYPE | |||
+ StringUtils.getMD5(user.username + password); | |||
String type = settings.get(Keys.realm.passwordStorage).getString(PasswordHash.getDefaultType().name()); | |||
PasswordHash pwdHash = PasswordHash.instanceOf(type); | |||
if (pwdHash != null) { | |||
user.password = pwdHash.toHashedEntry(password, user.username); | |||
} else { | |||
// plain-text password | |||
// plain-text password. | |||
// TODO: This is also used when the "realm.passwordStorage" configuration is not a valid type. | |||
// This is a rather bad default, and should probably caught and changed to a secure default. | |||
user.password = password; | |||
} | |||
} else if (rename && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | |||
} else if (rename && password.toUpperCase().startsWith(PasswordHash.Type.CMD5.name())) { | |||
error("Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename."); | |||
return false; | |||
} else { |
@@ -52,6 +52,7 @@ import com.gitblit.models.UserModel; | |||
import com.gitblit.transport.ssh.SshKey; | |||
import com.gitblit.utils.Base64; | |||
import com.gitblit.utils.HttpUtils; | |||
import com.gitblit.utils.PasswordHash; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.utils.X509Utils.X509Metadata; | |||
import com.google.inject.Inject; | |||
@@ -518,26 +519,51 @@ public class AuthenticationManager implements IAuthenticationManager { | |||
*/ | |||
protected UserModel authenticateLocal(UserModel user, char [] password) { | |||
UserModel returnedUser = null; | |||
if (user.password.startsWith(StringUtils.MD5_TYPE)) { | |||
// password digest | |||
String md5 = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(password)); | |||
if (user.password.equalsIgnoreCase(md5)) { | |||
returnedUser = user; | |||
} | |||
} else if (user.password.startsWith(StringUtils.COMBINED_MD5_TYPE)) { | |||
// username+password digest | |||
String md5 = StringUtils.COMBINED_MD5_TYPE | |||
+ StringUtils.getMD5(user.username.toLowerCase() + new String(password)); | |||
if (user.password.equalsIgnoreCase(md5)) { | |||
PasswordHash pwdHash = PasswordHash.instanceFor(user.password); | |||
if (pwdHash != null) { | |||
if (pwdHash.matches(user.password, password, user.username)) { | |||
returnedUser = user; | |||
} | |||
} else if (user.password.equals(new String(password))) { | |||
// plain-text password | |||
returnedUser = user; | |||
} | |||
// validate user | |||
returnedUser = validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); | |||
// try to upgrade the stored password hash to a stronger hash, if necessary | |||
upgradeStoredPassword(returnedUser, password, pwdHash); | |||
return returnedUser; | |||
} | |||
/** | |||
* Upgrade stored password to a strong hash if configured. | |||
* | |||
* @param user the user to be updated | |||
* @param password the password | |||
* @param pwdHash | |||
* Instance of PasswordHash for the stored password entry. If null, no current hashing is assumed. | |||
*/ | |||
private void upgradeStoredPassword(UserModel user, char[] password, PasswordHash pwdHash) { | |||
// check if user has successfully authenticated i.e. is not null | |||
if (user == null) return; | |||
// check if strong hash algorithm is configured | |||
String algorithm = settings.getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | |||
if (pwdHash == null || pwdHash.needsUpgradeTo(algorithm)) { | |||
// rehash the provided correct password and update the user model | |||
pwdHash = PasswordHash.instanceOf(algorithm); | |||
if (pwdHash != null) { // necessary since the algorithm name could be something not supported. | |||
user.password = pwdHash.toHashedEntry(password, user.username); | |||
userManager.updateUserModel(user); | |||
} | |||
} | |||
return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS); | |||
} | |||
/** | |||
* Returns the Gitlbit cookie in the request. | |||
* |
@@ -0,0 +1,292 @@ | |||
/* | |||
* Copyright 2017 gitblit.com. | |||
* | |||
* 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 com.gitblit.utils; | |||
import java.util.Arrays; | |||
/** | |||
* This is the superclass for classes responsible for handling password hashing. | |||
* | |||
* It provides a factory-like interface to create an instance of a class that | |||
* is responsible for the mechanics of a specific password hashing method. | |||
* It also provides the common interface, leaving implementation specifics | |||
* to subclasses of itself, which are the factory products. | |||
* | |||
* @author Florian Zschocke | |||
* @since 1.9.0 | |||
*/ | |||
public abstract class PasswordHash { | |||
/** | |||
* The types of implemented password hashing schemes. | |||
*/ | |||
public enum Type { | |||
MD5, | |||
CMD5, | |||
PBKDF2; | |||
static Type fromName(String name) { | |||
if (name == null) return null; | |||
for (Type type : Type.values()) { | |||
if (type.name().equalsIgnoreCase(name)) return type; | |||
} | |||
// Compatibility with type id "PBKDF2WITHHMACSHA256", which is also handled by PBKDF2 type. | |||
if (name.equalsIgnoreCase("PBKDF2WITHHMACSHA256")) return Type.PBKDF2; | |||
// Recognise the name used for CMD5 in the settings file. | |||
if (name.equalsIgnoreCase("combined-md5")) return Type.CMD5; | |||
return null; | |||
} | |||
} | |||
/** | |||
* The hashing scheme type handled by an instance of a subclass | |||
*/ | |||
final Type type; | |||
/** | |||
* Constructor for subclasses to initialize the final type field. | |||
* @param type | |||
* Type of hashing scheme implemented by this instance. | |||
*/ | |||
PasswordHash(Type type) { | |||
this.type = type; | |||
} | |||
/** | |||
* When no hash type is specified, this determines the default that should be used. | |||
*/ | |||
public static Type getDefaultType() { | |||
return Type.PBKDF2; | |||
} | |||
/** | |||
* Create an instance of a password hashing class for the given hash type. | |||
* | |||
* @param type | |||
* Type of hash to be used. | |||
* @return A class that can calculate the given hash type and verify a user password, | |||
* or null if the given hash type is not a valid one. | |||
*/ | |||
public static PasswordHash instanceOf(String type) { | |||
Type hashType = Type.fromName(type); | |||
if (hashType == null) return null; | |||
switch (hashType) { | |||
case MD5: | |||
return new PasswordHashMD5(); | |||
case CMD5: | |||
return new PasswordHashCombinedMD5(); | |||
case PBKDF2: | |||
return new PasswordHashPbkdf2(); | |||
default: | |||
return null; | |||
} | |||
} | |||
/** | |||
* Create an instance of a password hashing class of the correct type for a given | |||
* hashed password from the user password table. The stored hashed password needs | |||
* to be prefixed with the hash type identifier. | |||
* | |||
* @param hashedEntry | |||
* Hashed password string from the user table. | |||
* @return | |||
* A class that can calculate the given hash type and verify a user password, | |||
* or null if no instance can be created for the hashed user password. | |||
*/ | |||
public static PasswordHash instanceFor(String hashedEntry) { | |||
Type type = getEntryType(hashedEntry); | |||
if (type != null) return instanceOf(type.name()); | |||
return null; | |||
} | |||
/** | |||
* Test if a given string is a hashed password entry. This method simply checks if the | |||
* given string is prefixed by a known hash type identifier. | |||
* | |||
* @param storedPassword | |||
* A stored user password. | |||
* @return True if the given string is detected to be hashed with a known hash type, | |||
* false otherwise. | |||
*/ | |||
public static boolean isHashedEntry(String storedPassword) { | |||
return null != getEntryType(storedPassword); | |||
} | |||
/** | |||
* Some hash methods are considered more secure than others. This method determines for a certain type | |||
* of password hash if it is inferior than a given other type and stored passwords should be | |||
* upgraded to the given hashing method. | |||
* | |||
* @param algorithm | |||
* Password hashing type to be checked if it is superior than the one of this instance. | |||
* @return True, if the given type in parameter {@code algorithm} is better and stored passwords should be upgraded to it, | |||
* false, otehrwise. | |||
*/ | |||
public boolean needsUpgradeTo(String algorithm) { | |||
Type hashType = Type.fromName(algorithm); | |||
if (hashType == null) return false; | |||
if (this.type == hashType) return false; | |||
// Right now we keep it really simple. With the existing types, only PBKDF2 is considered secure, | |||
// everything else is inferior. This will need to be updated once more secure hashing algorithms | |||
// are implemented, or the workload/parameters of the PBKDF2 are changed. | |||
return hashType == Type.PBKDF2; | |||
} | |||
/** | |||
* Convert the given password to a hashed password entry to be stored in the user table. | |||
* The resulting string is prefixed by the hashing scheme type followed by a colon: | |||
* TYPE:theactualhashinhex | |||
* | |||
* @param password | |||
* Password to be hashed. | |||
* @param username | |||
* User name, only used for the Combined-MD5 (user+MD5) hashing type. | |||
* @return | |||
* Hashed password entry to be stored in the user table. | |||
*/ | |||
abstract public String toHashedEntry(char[] password, String username); | |||
/** | |||
* Convert the given password to a hashed password entry to be stored in the user table. | |||
* The resulting string is prefixed by the hashing scheme type followed by a colon: | |||
* TYPE:theactualhashinhex | |||
* | |||
* @param password | |||
* Password to be hashed. | |||
* @param username | |||
* User name, only used for the Combined-MD5 (user+MD5) hashing type. | |||
* @return | |||
* Hashed password entry to be stored in the user table. | |||
*/ | |||
public String toHashedEntry(String password, String username) { | |||
if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
return toHashedEntry(password.toCharArray(), username); | |||
} | |||
/** | |||
* Test if a given password (and user name) match a hashed password. | |||
* The instance of the password hash class has to be created with | |||
* {code instanceFor}, so that it matches the type of the hashed password | |||
* entry to test against. | |||
* | |||
* | |||
* @param hashedEntry | |||
* The hashed password entry from the user password table. | |||
* @param password | |||
* Clear text password to test against the hashed one. | |||
* @param username | |||
* User name, needed for the MD5+USER hash type. | |||
* @return True, if the password (and username) match the hashed password, | |||
* false, otherwise. | |||
*/ | |||
public boolean matches(String hashedEntry, char[] password, String username) { | |||
if (hashedEntry == null || type != PasswordHash.getEntryType(hashedEntry)) return false; | |||
if (password == null) return false; | |||
String hashed = toHashedEntry(password, username); | |||
Arrays.fill(password, Character.MIN_VALUE); | |||
return hashed.equalsIgnoreCase(hashedEntry); | |||
} | |||
static Type getEntryType(String hashedEntry) { | |||
if (hashedEntry == null) return null; | |||
int indexOfSeparator = hashedEntry.indexOf(':'); | |||
if (indexOfSeparator <= 0) return null; | |||
String typeId = hashedEntry.substring(0, indexOfSeparator); | |||
return Type.fromName(typeId); | |||
} | |||
static String getEntryValue(String hashedEntry) { | |||
if (hashedEntry == null) return null; | |||
int indexOfSeparator = hashedEntry.indexOf(':'); | |||
return hashedEntry.substring(indexOfSeparator +1); | |||
} | |||
/************************************** Implementations *************************************************/ | |||
private static class PasswordHashMD5 extends PasswordHash | |||
{ | |||
PasswordHashMD5() { | |||
super(Type.MD5); | |||
} | |||
// To keep the handling identical to how it was before, and therefore not risk invalidating stored passwords, | |||
// for MD5 the (String,String) variant of the method is the one implementing the hashing. | |||
@Override | |||
public String toHashedEntry(char[] password, String username) { | |||
if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
return toHashedEntry(new String(password), username); | |||
} | |||
@Override | |||
public String toHashedEntry(String password, String username) { | |||
if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
return type.name() + ":" | |||
+ StringUtils.getMD5(password); | |||
} | |||
} | |||
private static class PasswordHashCombinedMD5 extends PasswordHash | |||
{ | |||
PasswordHashCombinedMD5() { | |||
super(Type.CMD5); | |||
} | |||
// To keep the handling identical to how it was before, and therefore not risk invalidating stored passwords, | |||
// for Combined-MD5 the (String,String) variant of the method is the one implementing the hashing. | |||
@Override | |||
public String toHashedEntry(char[] password, String username) { | |||
if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
return toHashedEntry(new String(password), username); | |||
} | |||
@Override | |||
public String toHashedEntry(String password, String username) { | |||
if (password == null) throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
if (username == null) throw new IllegalArgumentException("The username argument may not be null when hashing a password with Combined-MD5."); | |||
if (StringUtils.isEmpty(username)) throw new IllegalArgumentException("The username argument may not be empty when hashing a password with Combined-MD5."); | |||
return type.name() + ":" | |||
+ StringUtils.getMD5(username.toLowerCase() + password); | |||
} | |||
@Override | |||
public boolean matches(String hashedEntry, char[] password, String username) { | |||
if (username == null || StringUtils.isEmpty(username)) return false; | |||
return super.matches(hashedEntry, password, username); | |||
} | |||
} | |||
} |
@@ -0,0 +1,276 @@ | |||
package com.gitblit.utils; | |||
import org.apache.commons.codec.DecoderException; | |||
import org.apache.commons.codec.binary.Hex; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import javax.crypto.SecretKeyFactory; | |||
import javax.crypto.spec.PBEKeySpec; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.security.spec.InvalidKeySpecException; | |||
import java.util.Arrays; | |||
/** | |||
* The class PasswordHashPbkdf2 implements password hashing and validation | |||
* with PBKDF2 | |||
* | |||
* It uses the concept proposed by OWASP - Hashing Java: | |||
* https://www.owasp.org/index.php/Hashing_Java | |||
*/ | |||
class PasswordHashPbkdf2 extends PasswordHash | |||
{ | |||
private static final Logger LOGGER = LoggerFactory.getLogger(PasswordHashPbkdf2.class); | |||
/** | |||
* The PBKDF has some parameters that define security and workload. | |||
* The Configuration class keeps these parameters. | |||
*/ | |||
private static class Configuration | |||
{ | |||
private final String algorithm; | |||
private final int iterations; | |||
private final int keyLen; | |||
private final int saltLen; | |||
private Configuration(String algorithm, int iterations, int keyLen, int saltLen) { | |||
this.algorithm = algorithm; | |||
this.iterations = iterations; | |||
this.keyLen = keyLen; | |||
this.saltLen = saltLen; | |||
} | |||
} | |||
private static final SecureRandom RANDOM = new SecureRandom(); | |||
/** | |||
* A list of Configurations is created to list the configurations supported by | |||
* this implementation. The configuration id is stored in the hashed entry, | |||
* identifying the Configuration in this array. | |||
* When adding a new variant with different values for these parameters, add | |||
* it to this array. | |||
* The code uses the last configuration in the array as the most secure, to be used | |||
* when creating new hashes when no configuration is specified. | |||
*/ | |||
private static final Configuration[] configurations = { | |||
// Configuration 0, also default when none is specified in the stored hashed entry. | |||
new Configuration("PBKDF2WithHmacSHA256", 10000, 256, 32) | |||
}; | |||
PasswordHashPbkdf2() { | |||
super(Type.PBKDF2); | |||
} | |||
/* | |||
* We return a hashed entry, where the hash part (salt+hash) itself is prefixed | |||
* again by the configuration id of the configuration that was used for the PBKDF, | |||
* enclosed in '$': | |||
* PBKDF2:$0$thesaltThehash | |||
*/ | |||
@Override | |||
public String toHashedEntry(char[] password, String username) { | |||
if (password == null) { | |||
LOGGER.warn("The password argument may not be null when hashing a password."); | |||
throw new IllegalArgumentException("The password argument may not be null when hashing a password."); | |||
} | |||
int configId = getLatestConfigurationId(); | |||
Configuration config = configurations[configId]; | |||
byte[] salt = new byte[config.saltLen]; | |||
RANDOM.nextBytes(salt); | |||
byte[] hash = hash(password, salt, config); | |||
return type.name() + ":" | |||
+ "$" + configId + "$" | |||
+ StringUtils.toHex(salt) | |||
+ StringUtils.toHex(hash); | |||
} | |||
@Override | |||
public boolean matches(String hashedEntry, char[] password, String username) { | |||
if (hashedEntry == null || type != PasswordHash.getEntryType(hashedEntry)) return false; | |||
if (password == null) return false; | |||
String hashedPart = getEntryValue(hashedEntry); | |||
int configId = getConfigIdFromStoredPassword(hashedPart); | |||
return isPasswordCorrect(password, hashedPart, configurations[configId]); | |||
} | |||
/** | |||
* Return the id of the most updated configuration of parameters for the PBKDF. | |||
* New password hashes should be generated with this one. | |||
* | |||
* @return An index into the configurations array for the latest configuration. | |||
*/ | |||
private int getLatestConfigurationId() { | |||
return configurations.length-1; | |||
} | |||
/** | |||
* Get the configuration id from the stored hashed password, that was used when the | |||
* hash was created. The configuration id is the index into the configuration array, | |||
* and is stored in the format $Id$ after the type identifier: TYPE:$Id$.... | |||
* If there is no identifier in the stored entry, id 0 is used, to keep backward | |||
* compatibility. | |||
* If an id is found that is not in the range of the declared configurations, | |||
* 0 is returned. This may fail password validation. As of now there is only one | |||
* configuration and even if there were more, chances are slim that anything else | |||
* was used. So we try at least the first one instead of failing with an exception | |||
* as the probability of success is high enough to save the user from a bad experience | |||
* and to risk some hassle for the admin finding out in the logs why a login failed, | |||
* when it does. | |||
* | |||
* @param hashPart | |||
* The hash part of the stored entry, i.e. the part after the TYPE: | |||
* @return The configuration id, or | |||
* 0 if none was found. | |||
*/ | |||
private static int getConfigIdFromStoredPassword(String hashPart) { | |||
String[] parts = hashPart.split("\\$", 3); | |||
// If there are not two parts, there is no '$'-enclosed part and we have no configuration information stored. | |||
// Return default 0. | |||
if (parts.length <= 2) return 0; | |||
// The first string wil be empty. Even if it isn't we ignore it because it doesn't contain our information. | |||
try { | |||
int configId = Integer.parseInt(parts[1]); | |||
if (configId < 0 || configId >= configurations.length) { | |||
LOGGER.warn("A user table password entry contains a configuration id that is not valid: {}." + | |||
"Assuming PBKDF configuration 0. This may fail to validate the password.", configId); | |||
return 0; | |||
} | |||
return configId; | |||
} | |||
catch (NumberFormatException e) { | |||
LOGGER.warn("A user table password entry contains a configuration id that is not a parsable number ({}${}$...)." + | |||
"Assuming PBKDF configuration 0. This may fail to validate the password.", parts[0], parts[1], e); | |||
return 0; | |||
} | |||
} | |||
/** | |||
* Hash. | |||
* | |||
* @param password | |||
* the password | |||
* @param salt | |||
* the salt | |||
* @param config | |||
* Parameter configuration to use for the PBKDF | |||
* @return Hashed result | |||
*/ | |||
private static byte[] hash(char[] password, byte[] salt, Configuration config) { | |||
PBEKeySpec spec = new PBEKeySpec(password, salt, config.iterations, config.keyLen); | |||
Arrays.fill(password, Character.MIN_VALUE); | |||
try { | |||
SecretKeyFactory skf = SecretKeyFactory.getInstance(config.algorithm); | |||
return skf.generateSecret(spec).getEncoded(); | |||
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) { | |||
LOGGER.warn("Error while hashing password.", e); | |||
throw new IllegalStateException("Error while hashing password", e); | |||
} finally { | |||
spec.clearPassword(); | |||
} | |||
} | |||
/** | |||
* Checks if is password correct. | |||
* | |||
* @param passwordToCheck | |||
* the password to check | |||
* @param salt | |||
* the salt | |||
* @param expectedHash | |||
* the expected hash | |||
* @return true, if is password correct | |||
*/ | |||
private static boolean isPasswordCorrect(char[] passwordToCheck, byte[] salt, byte[] expectedHash, Configuration config) { | |||
byte[] hashToCheck = hash(passwordToCheck, salt, config); | |||
Arrays.fill(passwordToCheck, Character.MIN_VALUE); | |||
if (hashToCheck.length != expectedHash.length) { | |||
return false; | |||
} | |||
for (int i = 0; i < hashToCheck.length; i++) { | |||
if (hashToCheck[i] != expectedHash[i]) { | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
/** | |||
* Gets the salt from stored password. | |||
* | |||
* @param storedPassword | |||
* the stored password | |||
* @return the salt from stored password | |||
*/ | |||
private static byte[] getSaltFromStoredPassword(String storedPassword, Configuration config) { | |||
byte[] pw = getStoredHashWithStrippedPrefix(storedPassword); | |||
return Arrays.copyOfRange(pw, 0, config.saltLen); | |||
} | |||
/** | |||
* Gets the hash from stored password. | |||
* | |||
* @param storedPassword | |||
* the stored password | |||
* @return the hash from stored password | |||
*/ | |||
private static byte[] getHashFromStoredPassword(String storedPassword, Configuration config) { | |||
byte[] pw = getStoredHashWithStrippedPrefix(storedPassword); | |||
return Arrays.copyOfRange(pw, config.saltLen, pw.length); | |||
} | |||
/** | |||
* Strips the configuration id prefix ($Id$) from a stored | |||
* password and returns the decoded hash | |||
* | |||
* @param storedPassword | |||
* the stored password | |||
* @return the stored hash with stripped prefix | |||
*/ | |||
private static byte[] getStoredHashWithStrippedPrefix(String storedPassword) { | |||
String[] strings = storedPassword.split("\\$", 3); | |||
String saltAndHash = strings[strings.length -1]; | |||
try { | |||
return Hex.decodeHex(saltAndHash.toCharArray()); | |||
} catch (DecoderException e) { | |||
LOGGER.warn("Failed to decode stored password entry from hex to string.", e); | |||
throw new IllegalStateException("Error while reading stored credentials", e); | |||
} | |||
} | |||
/** | |||
* Checks if password is correct. | |||
* | |||
* @param password | |||
* the password to validate | |||
* @param storedPassword | |||
* the stored password, i.e. the password entry value, without the leading TYPE: | |||
* @return true, if password is correct, false otherwise | |||
*/ | |||
private static boolean isPasswordCorrect(char[] password, String storedPassword, Configuration config) { | |||
byte[] storedSalt = getSaltFromStoredPassword(storedPassword, config); | |||
byte[] storedHash = getHashFromStoredPassword(storedPassword, config); | |||
return isPasswordCorrect(password, storedSalt, storedHash, config); | |||
} | |||
} |
@@ -46,10 +46,6 @@ import java.util.regex.PatternSyntaxException; | |||
*/ | |||
public class StringUtils { | |||
public static final String MD5_TYPE = "MD5:"; | |||
public static final String COMBINED_MD5_TYPE = "CMD5:"; | |||
/** | |||
* Returns true if the string is null or empty. | |||
* |
@@ -28,14 +28,14 @@ import org.apache.wicket.protocol.http.WebResponse; | |||
import com.gitblit.GitBlitException; | |||
import com.gitblit.Keys; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.utils.PasswordHash; | |||
import com.gitblit.wicket.GitBlitWebSession; | |||
import com.gitblit.wicket.NonTrimmedPasswordTextField; | |||
public class ChangePasswordPage extends RootSubPage { | |||
IModel<String> password = new Model<String>(""); | |||
IModel<String> confirmPassword = new Model<String>(""); | |||
private IModel<String> password = new Model<String>(""); | |||
private IModel<String> confirmPassword = new Model<String>(""); | |||
public ChangePasswordPage() { | |||
super(); | |||
@@ -85,15 +85,11 @@ public class ChangePasswordPage extends RootSubPage { | |||
UserModel user = GitBlitWebSession.get().getUser(); | |||
// convert to MD5 digest, if appropriate | |||
String type = app().settings().getString(Keys.realm.passwordStorage, "md5"); | |||
if (type.equalsIgnoreCase("md5")) { | |||
// store MD5 digest of password | |||
password = StringUtils.MD5_TYPE + StringUtils.getMD5(password); | |||
} else if (type.equalsIgnoreCase("combined-md5")) { | |||
// store MD5 digest of username+password | |||
password = StringUtils.COMBINED_MD5_TYPE | |||
+ StringUtils.getMD5(user.username.toLowerCase() + password); | |||
// convert to digest, if appropriate | |||
String type = app().settings().getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | |||
PasswordHash pwdHash = PasswordHash.instanceOf(type); | |||
if (pwdHash != null) { | |||
password = pwdHash.toHashedEntry(password, user.username); | |||
} | |||
user.password = password; |
@@ -22,6 +22,7 @@ import java.util.Iterator; | |||
import java.util.List; | |||
import java.util.Locale; | |||
import com.gitblit.utils.PasswordHash; | |||
import org.apache.wicket.PageParameters; | |||
import org.apache.wicket.behavior.SimpleAttributeModifier; | |||
import org.apache.wicket.extensions.markup.html.form.palette.Palette; | |||
@@ -172,15 +173,14 @@ public class EditUserPage extends RootSubPage { | |||
return; | |||
} | |||
String password = userModel.password; | |||
if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE) | |||
&& !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | |||
if (!PasswordHash.isHashedEntry(password)) { | |||
// This is a plain text password. | |||
// Check length. | |||
int minLength = app().settings().getInteger(Keys.realm.minPasswordLength, 5); | |||
if (minLength < 4) { | |||
minLength = 4; | |||
} | |||
if (password.trim().length() < minLength) { | |||
if (password.trim().length() < minLength) { // TODO: Why do we trim here, but not in EditUserDialog and ChangePasswordPage? | |||
error(MessageFormat.format(getString("gb.passwordTooShort"), | |||
minLength)); | |||
return; | |||
@@ -190,18 +190,13 @@ public class EditUserPage extends RootSubPage { | |||
userModel.cookie = userModel.createCookie(); | |||
// Optionally store the password MD5 digest. | |||
String type = app().settings().getString(Keys.realm.passwordStorage, "md5"); | |||
if (type.equalsIgnoreCase("md5")) { | |||
// store MD5 digest of password | |||
userModel.password = StringUtils.MD5_TYPE | |||
+ StringUtils.getMD5(userModel.password); | |||
} else if (type.equalsIgnoreCase("combined-md5")) { | |||
// store MD5 digest of username+password | |||
userModel.password = StringUtils.COMBINED_MD5_TYPE | |||
+ StringUtils.getMD5(username + userModel.password); | |||
String type = app().settings().getString(Keys.realm.passwordStorage, PasswordHash.getDefaultType().name()); | |||
PasswordHash pwdh = PasswordHash.instanceOf(type); | |||
if (pwdh != null) { // Hash the password | |||
userModel.password = pwdh.toHashedEntry(password, username); | |||
} | |||
} else if (rename | |||
&& password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) { | |||
&& password.toUpperCase().startsWith(PasswordHash.Type.CMD5.name())) { | |||
error(getString("gb.combinedMd5Rename")); | |||
return; | |||
} |
@@ -169,7 +169,7 @@ Usernames must be unique and are case-insensitive. | |||
Whitespace is illegal. | |||
### Passwords | |||
User passwords are CASE-SENSITIVE and may be *plain*, *md5*, or *combined-md5* formatted (see `gitblit.properties` -> *realm.passwordStorage*). | |||
User passwords are CASE-SENSITIVE and may be *plain*, *md5*, *combined-md5* or *pbkdf2* formatted (see `gitblit.properties` -> *realm.passwordStorage*). | |||
### User Roles | |||
There are four actual *roles* in Gitblit: |
@@ -43,6 +43,7 @@ import javax.servlet.http.HttpSessionContext; | |||
import javax.servlet.http.HttpUpgradeHandler; | |||
import javax.servlet.http.Part; | |||
import com.gitblit.utils.PasswordHash; | |||
import org.junit.Test; | |||
import com.gitblit.IUserService; | |||
@@ -665,6 +666,37 @@ public class AuthenticationManagerTest extends GitblitUnitTest { | |||
users.deleteUserModel(user); | |||
} | |||
@Test | |||
public void testAuthenticateUpgradePlaintext() throws Exception { | |||
IAuthenticationManager auth = newAuthenticationManager(); | |||
UserModel user = new UserModel("sunnyjim"); | |||
user.password = "password"; | |||
users.updateUserModel(user); | |||
assertNotNull(auth.authenticate(user.username, user.password.toCharArray(), null)); | |||
// validate that plaintext password was automatically updated to hashed one | |||
assertTrue(user.password.startsWith(PasswordHash.getDefaultType().name() + ":")); | |||
} | |||
@Test | |||
public void testAuthenticateUpgradeMD5() throws Exception { | |||
IAuthenticationManager auth = newAuthenticationManager(); | |||
UserModel user = new UserModel("sunnyjim"); | |||
user.password = "MD5:5F4DCC3B5AA765D61D8327DEB882CF99"; | |||
users.updateUserModel(user); | |||
assertNotNull(auth.authenticate(user.username, "password".toCharArray(), null)); | |||
// validate that MD5 password was automatically updated to hashed one | |||
assertTrue(user.password.startsWith(PasswordHash.getDefaultType().name() + ":")); | |||
} | |||
@Test | |||
public void testContenairAuthenticate() throws Exception { | |||
settings.put(Keys.realm.container.autoCreateAccounts, "true"); |
@@ -0,0 +1,666 @@ | |||
/* | |||
* Copyright 2017 gitblit.com. | |||
* | |||
* 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 com.gitblit.utils; | |||
import static org.junit.Assert.*; | |||
import org.junit.Test; | |||
/** | |||
* @author Florian Zschocke | |||
* | |||
*/ | |||
public class PasswordHashTest { | |||
static final String MD5_PASSWORD_0 = "password"; | |||
static final String MD5_HASHED_ENTRY_0 = "MD5:5F4DCC3B5AA765D61D8327DEB882CF99"; | |||
static final String MD5_PASSWORD_1 = "This is a test password"; | |||
static final String MD5_HASHED_ENTRY_1 = "md5:8e1901831af502c0f842d4efb9083bcf"; | |||
static final String MD5_PASSWORD_2 = "版本库管理方案"; | |||
static final String MD5_HASHED_ENTRY_2 = "MD5:980017891ff67cf8a20f23aa810e7b5a"; | |||
static final String MD5_PASSWORD_3 = "PÿrâṃiĐ"; | |||
static final String MD5_HASHED_ENTRY_3 = "MD5:60359b7e22941164708ae2040040521f"; | |||
static final String CMD5_USERNAME_0 = "Jane Doe"; | |||
static final String CMD5_PASSWORD_0 = "password"; | |||
static final String CMD5_HASHED_ENTRY_0 = "CMD5:DB9639A6E5F21457F9DFD7735FAFA68B"; | |||
static final String CMD5_USERNAME_1 = "Joe Black"; | |||
static final String CMD5_PASSWORD_1 = "ThisIsAWeirdScheme.Weird"; | |||
static final String CMD5_HASHED_ENTRY_1 = "cmd5:5c154768287e32fa605656b98894da89"; | |||
static final String CMD5_USERNAME_2 = "快速便"; | |||
static final String CMD5_PASSWORD_2 = "版本库管理方案"; | |||
static final String CMD5_HASHED_ENTRY_2 = "CMD5:f38575ee8af23ba6d923c0d98ee767fc"; | |||
static final String CMD5_USERNAME_3 = "İńa"; | |||
static final String CMD5_PASSWORD_3 = "PÿrâṃiĐ"; | |||
static final String CMD5_HASHED_ENTRY_3 = "CMD5:f1cdc2348c907677529e0e1b011f6793"; | |||
static final String PBKDF2_PASSWORD_0 = "password"; | |||
static final String PBKDF2_HASHED_ENTRY_0 = "PBKDF2:70617373776f726450415353574f524470617373776f726450415353574f52440f17d16621b32ae1bb2b1041fcb19e294b35d514d361c08eed385766e38f6f3a"; | |||
static final String PBKDF2_PASSWORD_1 = "A REALLY better scheme than MD5"; | |||
static final String PBKDF2_HASHED_ENTRY_1 = "PBKDF2:$0$46726573682066726f6d207468652053414c54206d696e65206f6620446f6f6de8e50b035679b25ce8b6ab41440938b7b1f97fc0c797fcf59302c2916f6c8fef"; | |||
static final String PBKDF2_PASSWORD_2 = "passwordPASSWORDpassword"; | |||
static final String PBKDF2_HASHED_ENTRY_2 = "pbkdf2:$0$73616c7453414c5473616c7453414c5473616c7453414c5473616c7453414c54560d0f02b565e37695da15141044506d54cb633a5a70b41c574069ea50a1247a"; | |||
static final String PBKDF2_PASSWORD_3 = "foo"; | |||
static final String PBKDF2_HASHED_ENTRY_3 = "PBKDF2WITHHMACSHA256:2d7d3ccaa277787f288e9f929247361bfc83607c6a8447bf496267512e360ba0a97b3114937213b23230072517d65a2e00695a1cbc47a732510840817f22c1bc"; | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for MD5. | |||
*/ | |||
@Test | |||
public void testInstanceOfMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("md5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("MD5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("mD5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("CMD5"); | |||
assertNotNull(pwdh); | |||
assertNotEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertFalse("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for combined MD5. | |||
*/ | |||
@Test | |||
public void testInstanceOfCombinedMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("cmd5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
pwdh = PasswordHash.instanceOf("cMD5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
pwdh = PasswordHash.instanceOf("CMD5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
pwdh = PasswordHash.instanceOf("combined-md5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
pwdh = PasswordHash.instanceOf("COMBINED-MD5"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
pwdh = PasswordHash.instanceOf("MD5"); | |||
assertNotNull(pwdh); | |||
assertNotEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertFalse("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#instanceOf(java.lang.String)} for PBKDF2. | |||
*/ | |||
@Test | |||
public void testInstanceOfPBKDF2() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_0, pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("pbkdf2"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("pbKDF2"); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceOf("md5"); | |||
assertNotNull(pwdh); | |||
assertNotEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertFalse("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
} | |||
/** | |||
* Test that no instance is returned for plaintext or unknown or not | |||
* yet implemented hashing schemes. | |||
*/ | |||
@Test | |||
public void testNoInstanceOf() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("plain"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("PLAIN"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("Plain"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("scrypt"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("bCrypt"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("BCRYPT"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf("nixe"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceOf(null); | |||
assertNull(pwdh); | |||
} | |||
/** | |||
* Test that for all known hash types an instance is created for a hashed entry | |||
* that can verify the known password. | |||
* | |||
* Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. | |||
*/ | |||
@Test | |||
public void testInstanceFor() { | |||
PasswordHash pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_0); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertTrue("Failed to match " +MD5_HASHED_ENTRY_0, pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceFor(MD5_HASHED_ENTRY_1); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.MD5, pwdh.type); | |||
assertTrue("Failed to match " +MD5_HASHED_ENTRY_1, pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_0); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertTrue("Failed to match " +CMD5_HASHED_ENTRY_0, pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
pwdh = PasswordHash.instanceFor(CMD5_HASHED_ENTRY_1); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.CMD5, pwdh.type); | |||
assertTrue("Failed to match " +CMD5_HASHED_ENTRY_1, pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_0); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_0, pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_1); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_1, pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
pwdh = PasswordHash.instanceFor(PBKDF2_HASHED_ENTRY_3); | |||
assertNotNull(pwdh); | |||
assertEquals(PasswordHash.Type.PBKDF2, pwdh.type); | |||
assertTrue("Failed to match " +PBKDF2_HASHED_ENTRY_3, pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), null)); | |||
} | |||
/** | |||
* Test that for no instance is returned for plaintext or unknown or | |||
* not yet implemented hashing schemes. | |||
* | |||
* Test method for {@link com.gitblit.utils.PasswordHash#instanceFor(java.lang.String)}. | |||
*/ | |||
@Test | |||
public void testInstanceForNaught() { | |||
PasswordHash pwdh = PasswordHash.instanceFor("password"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor("top secret"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor("pass:word"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor("PLAIN:password"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor("SCRYPT:1232rwv12w"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor("BCRYPT:urbvahiaufbvhabaiuevuzggubsbliue"); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor(""); | |||
assertNull(pwdh); | |||
pwdh = PasswordHash.instanceFor(null); | |||
assertNull(pwdh); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | |||
*/ | |||
@Test | |||
public void testIsHashedEntry() { | |||
assertTrue(MD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_0)); | |||
assertTrue(MD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1)); | |||
assertTrue(CMD5_HASHED_ENTRY_0, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_0)); | |||
assertTrue(CMD5_HASHED_ENTRY_1, PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1)); | |||
assertTrue(PBKDF2_HASHED_ENTRY_0, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_0)); | |||
assertTrue(PBKDF2_HASHED_ENTRY_1, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_1)); | |||
assertTrue(PBKDF2_HASHED_ENTRY_2, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_2)); | |||
assertTrue(PBKDF2_HASHED_ENTRY_3, PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_3)); | |||
assertFalse(MD5_PASSWORD_1, PasswordHash.isHashedEntry(MD5_PASSWORD_1)); | |||
assertFalse("topsecret", PasswordHash.isHashedEntry("topsecret")); | |||
assertFalse("top:secret", PasswordHash.isHashedEntry("top:secret")); | |||
assertFalse("secret Password", PasswordHash.isHashedEntry("secret Password")); | |||
assertFalse("Empty string", PasswordHash.isHashedEntry("")); | |||
assertFalse("Null", PasswordHash.isHashedEntry(null)); | |||
} | |||
/** | |||
* Test that hashed entry detection is not case sensitive on the hash type identifier. | |||
* | |||
* Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | |||
*/ | |||
@Test | |||
public void testIsHashedEntryCaseInsenitive() { | |||
assertTrue(MD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(MD5_HASHED_ENTRY_1.toLowerCase())); | |||
assertTrue(CMD5_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(CMD5_HASHED_ENTRY_1.toLowerCase())); | |||
assertTrue(PBKDF2_HASHED_ENTRY_1.toLowerCase(), PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_1.toLowerCase())); | |||
assertTrue(PBKDF2_HASHED_ENTRY_3.toLowerCase(), PasswordHash.isHashedEntry(PBKDF2_HASHED_ENTRY_3.toLowerCase())); | |||
} | |||
/** | |||
* Test that unknown or not yet implemented hashing schemes are not detected as hashed entries. | |||
* | |||
* Test method for {@link com.gitblit.utils.PasswordHash#isHashedEntry(java.lang.String)}. | |||
*/ | |||
@Test | |||
public void testIsHashedEntryUnknown() { | |||
assertFalse("BCRYPT:thisismypassword", PasswordHash.isHashedEntry("BCRYPT:thisismypassword")); | |||
assertFalse("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc", PasswordHash.isHashedEntry("TSTHSH:asdchabufzuzfbhbakrzburzbcuzkuzcbajhbcasjdhbckajsbc")); | |||
} | |||
/** | |||
* Test creating a hashed entry for scheme MD5. In this scheme there is no salt, so a direct | |||
* comparison to a constant value is possible. | |||
* | |||
* Test method for {@link PasswordHash#toHashedEntry(String, String)} for MD5. | |||
*/ | |||
@Test | |||
public void testToHashedEntryMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | |||
String hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, null); | |||
assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_2, null); | |||
assertTrue(MD5_HASHED_ENTRY_2.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_1, "charlie"); | |||
assertTrue(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(MD5_PASSWORD_3, CMD5_USERNAME_3); | |||
assertTrue(MD5_HASHED_ENTRY_3.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); | |||
assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry("badpassword", null); | |||
assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void testToHashedEntryMD5NullPassword() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | |||
pwdh.toHashedEntry((String)null, null); | |||
} | |||
/** | |||
* Test creating a hashed entry for scheme Combined-MD5. In this scheme there is no salt, so a direct | |||
* comparison to a constant value is possible. | |||
* | |||
* Test method for {@link PasswordHash#toHashedEntry(String, String)} for CMD5. | |||
*/ | |||
@Test | |||
public void testToHashedEntryCMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | |||
String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, CMD5_USERNAME_1); | |||
assertTrue(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_2, CMD5_USERNAME_2); | |||
assertTrue(CMD5_HASHED_ENTRY_2.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_3, CMD5_USERNAME_3); | |||
assertTrue(CMD5_HASHED_ENTRY_3.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, "charlie"); | |||
assertFalse(CMD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
hashedEntry = pwdh.toHashedEntry("badpassword", "charlie"); | |||
assertFalse(MD5_HASHED_ENTRY_1.equalsIgnoreCase(hashedEntry)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void testToHashedEntryCMD5NullPassword() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | |||
pwdh.toHashedEntry((String)null, CMD5_USERNAME_1); | |||
} | |||
/** | |||
* Test creating a hashed entry for scheme Combined-MD5, when no user is given. | |||
* This should never happen in the application, so we expect an exception to be thrown. | |||
* | |||
* Test method for {@link PasswordHash#toHashedEntry(String, String)} for broken CMD5. | |||
*/ | |||
@Test | |||
public void testToHashedEntryCMD5NoUsername() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | |||
try { | |||
String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, ""); | |||
fail("CMD5 cannot work with an empty '' username. Got: " + hashedEntry); | |||
} | |||
catch (IllegalArgumentException ignored) { /*success*/ } | |||
try { | |||
String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); | |||
fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); | |||
} | |||
catch (IllegalArgumentException ignored) { /*success*/ } | |||
try { | |||
String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, " "); | |||
fail("CMD5 cannot work with an empty ' ' username. Got: " + hashedEntry); | |||
} | |||
catch (IllegalArgumentException ignored) { /*success*/ } | |||
try { | |||
String hashedEntry = pwdh.toHashedEntry(CMD5_PASSWORD_1, null); | |||
fail("CMD5 cannot work with a null username. Got: " + hashedEntry); | |||
} | |||
catch (IllegalArgumentException ignored) { /*success*/ } | |||
} | |||
/** | |||
* Test creating a hashed entry for scheme PBKDF2. | |||
* Since this scheme uses a salt, we test by running a match. This is a bit backwards, | |||
* but recreating the PBKDF2 here seems a little overkill. | |||
* | |||
* Test method for {@link PasswordHash#toHashedEntry(String, String)} for PBKDF2. | |||
*/ | |||
@Test | |||
public void testToHashedEntryPBKDF2() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | |||
String hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_1, null); | |||
assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | |||
PasswordHash pwdhverify = PasswordHash.instanceFor(hashedEntry); | |||
assertNotNull(pwdhverify); | |||
assertTrue(PBKDF2_PASSWORD_1, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_2, ""); | |||
assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | |||
pwdhverify = PasswordHash.instanceFor(hashedEntry); | |||
assertNotNull(pwdhverify); | |||
assertTrue(PBKDF2_PASSWORD_2, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_2.toCharArray(), null)); | |||
hashedEntry = pwdh.toHashedEntry(PBKDF2_PASSWORD_0, "alpha"); | |||
assertTrue("Type identifier is incorrect.", hashedEntry.startsWith(PasswordHash.Type.PBKDF2.name())); | |||
pwdhverify = PasswordHash.instanceFor(hashedEntry); | |||
assertNotNull(pwdhverify); | |||
assertTrue(PBKDF2_PASSWORD_0, pwdhverify.matches(hashedEntry, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
} | |||
@Test(expected = IllegalArgumentException.class) | |||
public void testToHashedEntryPBKDF2NullPassword() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | |||
pwdh.toHashedEntry((String)null, null); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for MD5. | |||
*/ | |||
@Test | |||
public void testMatchesMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("MD5"); | |||
assertTrue("PWD0, Null user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | |||
assertTrue("PWD0, Empty user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "")); | |||
assertTrue("PWD0, With user", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "maxine")); | |||
assertTrue("PWD1, Null user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), null)); | |||
assertTrue("PWD1, Empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "")); | |||
assertTrue("PWD1, With user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_1.toCharArray(), "maxine")); | |||
assertTrue("PWD2", pwdh.matches(MD5_HASHED_ENTRY_2, MD5_PASSWORD_2.toCharArray(), null)); | |||
assertTrue("PWD3", pwdh.matches(MD5_HASHED_ENTRY_3, MD5_PASSWORD_3.toCharArray(), null)); | |||
assertFalse("Matched wrong password", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), null)); | |||
assertFalse("Matched wrong password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), " ")); | |||
assertFalse("Matched wrong password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), "someuser")); | |||
assertFalse("Matched empty password", pwdh.matches(MD5_HASHED_ENTRY_1, "".toCharArray(), null)); | |||
assertFalse("Matched empty password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), " ")); | |||
assertFalse("Matched empty password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, " ".toCharArray(), "someuser")); | |||
assertFalse("Matched null password", pwdh.matches(MD5_HASHED_ENTRY_1, null, null)); | |||
assertFalse("Matched null password, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, null, " ")); | |||
assertFalse("Matched null password, with user", pwdh.matches(MD5_HASHED_ENTRY_1, null, "someuser")); | |||
assertFalse("Matched wrong hashed entry", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong hashed entry, with user", pwdh.matches(MD5_HASHED_ENTRY_1, MD5_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched empty hashed entry", pwdh.matches("", MD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched empty hashed entry, with user", pwdh.matches(" ", MD5_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched null entry", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched null entry, with empty user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched null entry, with user", pwdh.matches(null, MD5_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong scheme", pwdh.matches(PBKDF2_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for Combined-MD5. | |||
*/ | |||
@Test | |||
public void testMatchesCombinedMD5() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("CMD5"); | |||
assertTrue("PWD0", pwdh.matches(CMD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
assertTrue("PWD1", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
assertTrue("PWD2", pwdh.matches(CMD5_HASHED_ENTRY_2, CMD5_PASSWORD_2.toCharArray(), CMD5_USERNAME_2)); | |||
assertTrue("PWD3", pwdh.matches(CMD5_HASHED_ENTRY_3, CMD5_PASSWORD_3.toCharArray(), CMD5_USERNAME_3)); | |||
assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, "wrongpassword".toCharArray(), CMD5_USERNAME_1)); | |||
assertFalse("Matched wrong password", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_1)); | |||
assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_0)); | |||
assertFalse("Matched wrong user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "Samantha Jones")); | |||
assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), "")); | |||
assertFalse("Matched empty user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), " ")); | |||
assertFalse("Matched null user", pwdh.matches(CMD5_HASHED_ENTRY_1, CMD5_PASSWORD_1.toCharArray(), null)); | |||
assertFalse("Matched empty hashed entry", pwdh.matches("", CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", CMD5_PASSWORD_1.toCharArray(), "")); | |||
assertFalse("Matched empty hashed entry, with null user", pwdh.matches(" ", CMD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched null entry, with user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), CMD5_USERNAME_1)); | |||
assertFalse("Matched null entry, with empty user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), "")); | |||
assertFalse("Matched null entry, with null user", pwdh.matches(null, CMD5_PASSWORD_1.toCharArray(), null)); | |||
assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong scheme", pwdh.matches(PBKDF2_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, CMD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, MD5_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} for PBKDF2. | |||
*/ | |||
@Test | |||
public void testMatchesPBKDF2() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | |||
assertTrue("PWD0, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertTrue("PWD0, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertTrue("PWD0, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "maxine")); | |||
assertTrue("PWD1, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), null)); | |||
assertTrue("PWD1, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), "")); | |||
assertTrue("PWD1, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_1.toCharArray(), "Maxim Gorki")); | |||
assertTrue("PWD2, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), null)); | |||
assertTrue("PWD2, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), "")); | |||
assertTrue("PWD2, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, PBKDF2_PASSWORD_2.toCharArray(), "Epson")); | |||
assertFalse("Matched wrong password", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), null)); | |||
assertFalse("Matched wrong password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), " ")); | |||
assertFalse("Matched wrong password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, "wrongpassword".toCharArray(), "someuser")); | |||
assertFalse("Matched empty password", pwdh.matches(PBKDF2_HASHED_ENTRY_2, "".toCharArray(), null)); | |||
assertFalse("Matched empty password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, " ".toCharArray(), " ")); | |||
assertFalse("Matched empty password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_2, " ".toCharArray(), "someuser")); | |||
assertFalse("Matched null password", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, null)); | |||
assertFalse("Matched null password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, " ")); | |||
assertFalse("Matched null password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_0, null, "someuser")); | |||
assertFalse("Matched wrong hashed entry", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong hashed entry, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_1, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched empty hashed entry", pwdh.matches("", PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched empty hashed entry, with empty user", pwdh.matches(" ", PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched empty hashed entry, with user", pwdh.matches(" ", PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched null entry", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched null entry, with empty user", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched null entry, with user", pwdh.matches(null, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | |||
assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong scheme", pwdh.matches(MD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong scheme", pwdh.matches(CMD5_HASHED_ENTRY_0, PBKDF2_PASSWORD_0.toCharArray(), CMD5_USERNAME_0)); | |||
} | |||
/** | |||
* Test method for {@link com.gitblit.utils.PasswordHash#matches(String, char[], String)} | |||
* for old existing entries with the "PBKDF2WITHHMACSHA256" type identifier. | |||
*/ | |||
@Test | |||
public void testMatchesPBKDF2Compat() { | |||
PasswordHash pwdh = PasswordHash.instanceOf("PBKDF2"); | |||
assertTrue("PWD3, Null user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), null)); | |||
assertTrue("PWD3, Empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), "")); | |||
assertTrue("PWD3, With user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_3.toCharArray(), "maxine")); | |||
assertFalse("Matched wrong password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), null)); | |||
assertFalse("Matched wrong password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), " ")); | |||
assertFalse("Matched wrong password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "bar".toCharArray(), "someuser")); | |||
assertFalse("Matched empty password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, "".toCharArray(), null)); | |||
assertFalse("Matched empty password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, " ".toCharArray(), " ")); | |||
assertFalse("Matched empty password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, " ".toCharArray(), "someuser")); | |||
assertFalse("Matched null password", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, null)); | |||
assertFalse("Matched null password, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, " ")); | |||
assertFalse("Matched null password, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, null, "someuser")); | |||
assertFalse("Matched wrong hashed entry", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), null)); | |||
assertFalse("Matched wrong hashed entry, with empty user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), "")); | |||
assertFalse("Matched wrong hashed entry, with user", pwdh.matches(PBKDF2_HASHED_ENTRY_3, PBKDF2_PASSWORD_0.toCharArray(), "someuser")); | |||
} | |||
@Test | |||
public void getEntryType() { | |||
assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("MD5:blah")); | |||
assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("md5:blah")); | |||
assertEquals(PasswordHash.Type.MD5, PasswordHash.getEntryType("mD5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("CMD5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("cmd5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("Cmd5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("combined-md5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("COMBINED-MD5:blah")); | |||
assertEquals(PasswordHash.Type.CMD5, PasswordHash.getEntryType("combined-MD5:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("pbkdf2:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("Pbkdf2:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("pbKDF2:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2WithHmacSHA256:blah")); | |||
assertEquals(PasswordHash.Type.PBKDF2, PasswordHash.getEntryType("PBKDF2WITHHMACSHA256:blah")); | |||
} | |||
@Test | |||
public void getEntryValue() { | |||
assertEquals("value", PasswordHash.getEntryValue("MD5:value")); | |||
assertEquals("plain text", PasswordHash.getEntryValue("plain text")); | |||
assertEquals("what this", PasswordHash.getEntryValue(":what this")); | |||
assertEquals("", PasswordHash.getEntryValue(":")); | |||
} | |||
} |