#449 Remove use of legacy settings in encryption methods
This commit is contained in:
@@ -338,7 +338,7 @@ public class AuthMe extends JavaPlugin {
|
||||
}
|
||||
database.reload();
|
||||
messages.reload(newSettings.getMessagesFile());
|
||||
passwordSecurity.reload(newSettings);
|
||||
passwordSecurity.reload();
|
||||
spawnLoader.initialize(newSettings);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import fr.xephi.authme.AuthMe;
|
||||
import fr.xephi.authme.ConsoleLogger;
|
||||
import fr.xephi.authme.cache.auth.PlayerAuth;
|
||||
import fr.xephi.authme.datasource.DataSource;
|
||||
import fr.xephi.authme.security.HashAlgorithm;
|
||||
import fr.xephi.authme.security.PasswordSecurity;
|
||||
import fr.xephi.authme.security.crypts.HashedPassword;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
@@ -35,7 +34,6 @@ public class RakamakConverter implements Converter {
|
||||
@Override
|
||||
// TODO ljacqu 20151229: Restructure this into smaller portions
|
||||
public void run() {
|
||||
HashAlgorithm hash = Settings.getPasswordHash;
|
||||
boolean useIP = Settings.rakamakUseIp;
|
||||
String fileName = Settings.rakamakUsers;
|
||||
String ipFileName = Settings.rakamakUsersIp;
|
||||
@@ -64,7 +62,7 @@ public class RakamakConverter implements Converter {
|
||||
while ((line = users.readLine()) != null) {
|
||||
if (line.contains("=")) {
|
||||
String[] arguments = line.split("=");
|
||||
HashedPassword hashedPassword = passwordSecurity.computeHash(hash, arguments[1], arguments[0]);
|
||||
HashedPassword hashedPassword = passwordSecurity.computeHash(arguments[1], arguments[0]);
|
||||
playerPSW.put(arguments[0], hashedPassword);
|
||||
|
||||
}
|
||||
|
||||
@@ -8,46 +8,75 @@ import fr.xephi.authme.settings.NewSetting;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
/**
|
||||
* Manager class for password-related operations.
|
||||
*/
|
||||
public class PasswordSecurity {
|
||||
|
||||
private final NewSetting settings;
|
||||
private HashAlgorithm algorithm;
|
||||
private boolean supportOldAlgorithm;
|
||||
private final DataSource dataSource;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
public PasswordSecurity(DataSource dataSource, NewSetting settings, PluginManager pluginManager) {
|
||||
this.settings = settings;
|
||||
this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
|
||||
this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH);
|
||||
this.dataSource = dataSource;
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the hash of the configured algorithm for the given password and username.
|
||||
*
|
||||
* @param password The password to hash
|
||||
* @param playerName The player's name
|
||||
*
|
||||
* @return The password hash
|
||||
*/
|
||||
public HashedPassword computeHash(String password, String playerName) {
|
||||
return computeHash(algorithm, password, playerName);
|
||||
}
|
||||
|
||||
public HashedPassword computeHash(HashAlgorithm algorithm, String password, String playerName) {
|
||||
String playerLowerCase = playerName.toLowerCase();
|
||||
EncryptionMethod method = initializeEncryptionMethod(algorithm, playerLowerCase);
|
||||
EncryptionMethod method = initializeEncryptionMethodWithEvent(algorithm, playerLowerCase);
|
||||
return method.computeHash(password, playerLowerCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given password matches the player's stored password.
|
||||
*
|
||||
* @param password The password to check
|
||||
* @param playerName The player to check for
|
||||
*
|
||||
* @return True if the password is correct, false otherwise
|
||||
*/
|
||||
public boolean comparePassword(String password, String playerName) {
|
||||
HashedPassword auth = dataSource.getPassword(playerName);
|
||||
return auth != null && comparePassword(password, auth, playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given password matches the given hashed password.
|
||||
*
|
||||
* @param password The password to check
|
||||
* @param hashedPassword The hashed password to check against
|
||||
* @param playerName The player to check for
|
||||
*
|
||||
* @return True if the password matches, false otherwise
|
||||
*/
|
||||
public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) {
|
||||
EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName);
|
||||
EncryptionMethod method = initializeEncryptionMethodWithEvent(algorithm, playerName);
|
||||
String playerLowerCase = playerName.toLowerCase();
|
||||
return methodMatches(method, password, hashedPassword, playerLowerCase)
|
||||
|| supportOldAlgorithm && compareWithAllEncryptionMethods(password, hashedPassword, playerLowerCase);
|
||||
}
|
||||
|
||||
public void reload(NewSetting settings) {
|
||||
/**
|
||||
* Reload the configuration.
|
||||
*/
|
||||
public void reload() {
|
||||
this.algorithm = settings.getProperty(SecuritySettings.PASSWORD_HASH);
|
||||
this.supportOldAlgorithm = settings.getProperty(SecuritySettings.SUPPORT_OLD_PASSWORD_HASH);
|
||||
}
|
||||
@@ -63,11 +92,10 @@ public class PasswordSecurity {
|
||||
*
|
||||
* @return True if there was a password match with another encryption method, false otherwise
|
||||
*/
|
||||
private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword,
|
||||
String playerName) {
|
||||
private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, String playerName) {
|
||||
for (HashAlgorithm algorithm : HashAlgorithm.values()) {
|
||||
if (!HashAlgorithm.CUSTOM.equals(algorithm)) {
|
||||
EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm);
|
||||
EncryptionMethod method = initializeEncryptionMethod(algorithm, settings);
|
||||
if (methodMatches(method, password, hashedPassword, playerName)) {
|
||||
hashPasswordForNewAlgorithm(password, playerName);
|
||||
return true;
|
||||
@@ -85,6 +113,7 @@ public class PasswordSecurity {
|
||||
* @param password The password to check
|
||||
* @param hashedPassword The hash to check against
|
||||
* @param playerName The name of the player
|
||||
*
|
||||
* @return True if the password matched, false otherwise
|
||||
*/
|
||||
private static boolean methodMatches(EncryptionMethod method, String password,
|
||||
@@ -103,33 +132,45 @@ public class PasswordSecurity {
|
||||
*
|
||||
* @return The encryption method
|
||||
*/
|
||||
private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) {
|
||||
EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm);
|
||||
private EncryptionMethod initializeEncryptionMethodWithEvent(HashAlgorithm algorithm, String playerName) {
|
||||
EncryptionMethod method = initializeEncryptionMethod(algorithm, settings);
|
||||
PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName);
|
||||
pluginManager.callEvent(event);
|
||||
return event.getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the encryption method corresponding to the given hash algorithm.
|
||||
* Initialize the encryption method associated with the given hash algorithm.
|
||||
*
|
||||
* @param algorithm The algorithm to retrieve the encryption method for
|
||||
* @param settings The settings instance to pass to the constructor if required
|
||||
*
|
||||
* @return The associated encryption method
|
||||
* @return The associated encryption method, or null if CUSTOM / deprecated
|
||||
*/
|
||||
private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) {
|
||||
public static EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm,
|
||||
NewSetting settings) {
|
||||
try {
|
||||
return HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)
|
||||
? null
|
||||
: algorithm.getClazz().newInstance();
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
if (HashAlgorithm.CUSTOM.equals(algorithm) || HashAlgorithm.PLAINTEXT.equals(algorithm)) {
|
||||
return null;
|
||||
}
|
||||
Constructor<?> constructor = algorithm.getClazz().getConstructors()[0];
|
||||
Class<?>[] parameters = constructor.getParameterTypes();
|
||||
if (parameters.length == 0) {
|
||||
return (EncryptionMethod) constructor.newInstance();
|
||||
} else if (parameters.length == 1 && parameters[0] == NewSetting.class) {
|
||||
return (EncryptionMethod) constructor.newInstance(settings);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Did not find default constructor or constructor with settings "
|
||||
+ "parameter in class " + algorithm.getClazz().getSimpleName());
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new UnsupportedOperationException("Constructor for '" + algorithm.getClazz().getSimpleName()
|
||||
+ "' could not be invoked. (Is there no default constructor?)", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void hashPasswordForNewAlgorithm(String password, String playerName) {
|
||||
HashedPassword hashedPassword = initializeEncryptionMethod(algorithm, playerName)
|
||||
HashedPassword hashedPassword = initializeEncryptionMethodWithEvent(algorithm, playerName)
|
||||
.computeHash(password, playerName);
|
||||
dataSource.updatePassword(playerName, hashedPassword);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.NewSetting;
|
||||
import fr.xephi.authme.settings.properties.HooksSettings;
|
||||
import fr.xephi.authme.util.StringUtils;
|
||||
|
||||
|
||||
@@ -13,6 +14,12 @@ import fr.xephi.authme.util.StringUtils;
|
||||
@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds
|
||||
public class BCRYPT implements EncryptionMethod {
|
||||
|
||||
private final int bCryptLog2Rounds;
|
||||
|
||||
public BCRYPT(NewSetting settings) {
|
||||
this.bCryptLog2Rounds = settings.getProperty(HooksSettings.BCRYPT_LOG2_ROUND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return BCryptService.hashpw(password, salt);
|
||||
@@ -36,7 +43,7 @@ public class BCRYPT implements EncryptionMethod {
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return BCryptService.gensalt(Settings.bCryptLog2Rounds);
|
||||
return BCryptService.gensalt(bCryptLog2Rounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,10 @@ package fr.xephi.authme.security.crypts;
|
||||
|
||||
/**
|
||||
* Public interface for custom password encryption methods.
|
||||
* <p>
|
||||
* Note that {@link fr.xephi.authme.security.PasswordSecurity} requires classes implementing this interface
|
||||
* to either have the default constructor or an accessible constructor with one parameter of type
|
||||
* {@link fr.xephi.authme.settings.NewSetting}.
|
||||
*/
|
||||
public interface EncryptionMethod {
|
||||
|
||||
@@ -31,9 +35,9 @@ public interface EncryptionMethod {
|
||||
/**
|
||||
* Check whether the given hash matches the clear-text password.
|
||||
*
|
||||
* @param password The clear-text password to verify
|
||||
* @param password The clear-text password to verify
|
||||
* @param hashedPassword The hash to check the password against
|
||||
* @param name The player name to do the check for (sometimes required for generating the salt)
|
||||
* @param name The player name to do the check for (sometimes required for generating the salt)
|
||||
*
|
||||
* @return True if the password matches, false otherwise
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,8 @@ import fr.xephi.authme.security.crypts.description.HasSalt;
|
||||
import fr.xephi.authme.security.crypts.description.Recommendation;
|
||||
import fr.xephi.authme.security.crypts.description.SaltType;
|
||||
import fr.xephi.authme.security.crypts.description.Usage;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.NewSetting;
|
||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||
|
||||
import static fr.xephi.authme.security.HashUtils.md5;
|
||||
|
||||
@@ -13,6 +14,12 @@ import static fr.xephi.authme.security.HashUtils.md5;
|
||||
@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength
|
||||
public class SALTED2MD5 extends SeparateSaltMethod {
|
||||
|
||||
private final int saltLength;
|
||||
|
||||
public SALTED2MD5(NewSetting settings) {
|
||||
saltLength = settings.getProperty(SecuritySettings.DOUBLE_MD5_SALT_LENGTH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String computeHash(String password, String salt, String name) {
|
||||
return md5(md5(password) + salt);
|
||||
@@ -20,7 +27,7 @@ public class SALTED2MD5 extends SeparateSaltMethod {
|
||||
|
||||
@Override
|
||||
public String generateSalt() {
|
||||
return RandomString.generateHex(Settings.saltLength);
|
||||
return RandomString.generateHex(saltLength);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,8 +45,7 @@ public final class Settings {
|
||||
rakamakUsers, rakamakUsersIp, defaultWorld, crazyloginFileName;
|
||||
public static int getWarnMessageInterval, getSessionTimeout,
|
||||
getRegistrationTimeout, getMaxNickLength, getMinNickLength,
|
||||
getNonActivatedGroup, maxLoginTry, captchaLength, saltLength,
|
||||
bCryptLog2Rounds, getMaxLoginPerIp;
|
||||
getNonActivatedGroup, maxLoginTry, captchaLength, getMaxLoginPerIp;
|
||||
protected static FileConfiguration configFile;
|
||||
|
||||
/**
|
||||
@@ -108,11 +107,9 @@ public final class Settings {
|
||||
removePassword = configFile.getBoolean("Security.console.removePassword", true);
|
||||
maxLoginTry = configFile.getInt("Security.captcha.maxLoginTry", 5);
|
||||
captchaLength = configFile.getInt("Security.captcha.captchaLength", 5);
|
||||
saltLength = configFile.getInt("settings.security.doubleMD5SaltLength", 8);
|
||||
multiverse = load(HooksSettings.MULTIVERSE);
|
||||
bungee = load(HooksSettings.BUNGEECORD);
|
||||
getForcedWorlds = configFile.getStringList("settings.restrictions.ForceSpawnOnTheseWorlds");
|
||||
bCryptLog2Rounds = configFile.getInt("ExternalBoardOptions.bCryptLog2Round", 10);
|
||||
defaultWorld = configFile.getString("Purge.defaultWorld", "world");
|
||||
enableProtection = configFile.getBoolean("Protection.enableProtection", false);
|
||||
countries = configFile.getStringList("Protection.countries");
|
||||
|
||||
Reference in New Issue
Block a user