diff --git a/README.md b/README.md index fb23fd75..f98ce8bb 100644 --- a/README.md +++ b/README.md @@ -82,8 +82,6 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • MyBB: MYBB
  • IPB3: IPB3
  • PhpFusion: PHPFUSION
  • -
  • Xenforo SHA1: XFSHA1 (Deprecated)
  • -
  • Xenforo SHA256: XFSHA256 (Deprecated)
  • Joomla: JOOMLA
  • WBB3: WBB3*
  • SHA512: SHA512
  • @@ -92,7 +90,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • Custom MySQL tables/columns names (useful with forums databases)
  • Cached database queries!
  • -
  • Full compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!
  • +
  • Fully compatible with Citizens2, CombatTag, CombatTagPlus and ChestShop!
  • Compatible with Minecraft mods like BuildCraft or RedstoneCraft
  • Restricted users (associate a Username with an IP)
  • Protect player's inventory until a correct Authentication
  • @@ -120,7 +118,7 @@ typing commands or using the inventory. It can also kick players with uncommonly
  • Website Integration
  • Click here for an example of the Config file
  • How to convert from Rakamak -
  • Convert from FlatFile (auths.db but not the sqlite one ) to MySQL: /converter +
  • Convert from FlatFile (auths.db but not the sqlite one) to MySQL: /converter

  • @@ -139,5 +137,5 @@ GameHosting.it is leader in Italy as Game Server Provider. With its own DataCent #####Credits

    Team members: look at the team.txt file -

    Credit for old version of the plugin to: d4rkwarriors, fabe1337 , Whoami2 and pomo4ka

    +

    Credit for old version of the plugin to: d4rkwarriors, fabe1337, Whoami2 and pomo4ka

    Thanks also to: AS1LV3RN1NJA, Hoeze and eprimex

    diff --git a/src/main/java/fr/xephi/authme/AuthMe.java b/src/main/java/fr/xephi/authme/AuthMe.java index cae30a44..04ff2a32 100644 --- a/src/main/java/fr/xephi/authme/AuthMe.java +++ b/src/main/java/fr/xephi/authme/AuthMe.java @@ -43,6 +43,7 @@ import fr.xephi.authme.permission.PlayerPermission; import fr.xephi.authme.process.Management; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; @@ -101,12 +102,13 @@ public class AuthMe extends JavaPlugin { private Messages messages; private JsonCache playerBackup; private ModuleManager moduleManager; + private PasswordSecurity passwordSecurity; + private DataSource database; // Public Instances public NewAPI api; public SendMailSSL mail; public DataManager dataManager; - public DataSource database; public OtherAccounts otherAccounts; public Location essentialsSpawn; @@ -192,7 +194,6 @@ public class AuthMe extends JavaPlugin { /** * Method called when the server enables the plugin. - * @see org.bukkit.plugin.Plugin#onEnable() */ @Override public void onEnable() { @@ -209,12 +210,26 @@ public class AuthMe extends JavaPlugin { return; } - // Set up messages + // Set up messages & password security messages = Messages.getInstance(); + // Connect to the database and setup tables + try { + setupDatabase(); + } catch (Exception e) { + ConsoleLogger.writeStackTrace(e); + ConsoleLogger.showError(e.getMessage()); + ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); + stopOrUnload(); + return; + } + + passwordSecurity = new PasswordSecurity(getDataSource(), Settings.getPasswordHash, + Bukkit.getPluginManager(), Settings.supportOldPassword); + // Set up the permissions manager and command handler permsMan = initializePermissionsManager(); - commandHandler = initializeCommandHandler(permsMan, messages); + commandHandler = initializeCommandHandler(permsMan, messages, passwordSecurity); // Set up the module manager setupModuleManager(); @@ -256,16 +271,7 @@ public class AuthMe extends JavaPlugin { // Do a backup on start new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.START); - // Connect to the database and setup tables - try { - setupDatabase(); - } catch (Exception e) { - ConsoleLogger.writeStackTrace(e); - ConsoleLogger.showError(e.getMessage()); - ConsoleLogger.showError("Fatal error occurred during database connection! Authme initialization ABORTED!"); - stopOrUnload(); - return; - } + // Setup the inventory backup playerBackup = new JsonCache(); @@ -412,11 +418,12 @@ public class AuthMe extends JavaPlugin { } } - private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages) { + private CommandHandler initializeCommandHandler(PermissionsManager permissionsManager, Messages messages, + PasswordSecurity passwordSecurity) { HelpProvider helpProvider = new HelpProvider(permissionsManager); Set baseCommands = CommandInitializer.buildCommands(); CommandMapper mapper = new CommandMapper(baseCommands, messages, permissionsManager, helpProvider); - CommandService commandService = new CommandService(this, mapper, helpProvider, messages); + CommandService commandService = new CommandService(this, mapper, helpProvider, messages, passwordSecurity); return new CommandHandler(commandService); } @@ -506,11 +513,6 @@ public class AuthMe extends JavaPlugin { } } - /** - * Method onDisable. - * - * @see org.bukkit.plugin.Plugin#onDisable() - */ @Override public void onDisable() { // Save player data @@ -525,7 +527,9 @@ public class AuthMe extends JavaPlugin { new PerformBackup(plugin).doBackup(PerformBackup.BackupCause.STOP); // Unload modules - moduleManager.unloadModules(); + if (moduleManager != null) { + moduleManager.unloadModules(); + } // Close the database if (database != null) { @@ -539,16 +543,13 @@ public class AuthMe extends JavaPlugin { // Stop/unload the server/plugin as defined in the configuration public void stopOrUnload() { if (Settings.isStopEnabled) { - ConsoleLogger.showError("THE SERVER IS GOING TO SHUTDOWN AS DEFINED IN THE CONFIGURATION!"); + ConsoleLogger.showError("THE SERVER IS GOING TO SHUT DOWN AS DEFINED IN THE CONFIGURATION!"); server.shutdown(); } else { server.getPluginManager().disablePlugin(AuthMe.getInstance()); } } - /** - * Method setupDatabase. - */ public void setupDatabase() throws Exception { if (database != null) database.close(); @@ -574,14 +575,15 @@ public class AuthMe extends JavaPlugin { int accounts = database.getAccountsRegistered(); if (accounts >= 4000) { ConsoleLogger.showError("YOU'RE USING THE SQLITE DATABASE WITH " - + accounts + "+ ACCOUNTS, FOR BETTER PERFORMANCES, PLEASE UPGRADE TO MYSQL!!"); + + accounts + "+ ACCOUNTS; FOR BETTER PERFORMANCE, PLEASE UPGRADE TO MYSQL!!"); } } }); } if (Settings.getDataSource == DataSource.DataSourceType.FILE) { - ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed to SQLite... Connection will be impossible until conversion is done!"); + ConsoleLogger.showError("FlatFile backend has been detected and is now deprecated, it will be changed " + + "to SQLite... Connection will be impossible until conversion is done!"); ForceFlatToSqlite converter = new ForceFlatToSqlite(database); DataSource source = converter.run(); if (source != null) { @@ -590,12 +592,13 @@ public class AuthMe extends JavaPlugin { } // TODO: Move this to another place maybe ? - if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) - { - ConsoleLogger.showError("Your HashAlgorithm has been detected has plaintext and is now deprecrated, it will be changed and hashed now to AuthMe default hashing method"); - for (PlayerAuth auth : database.getAllAuths()) - { - auth.setHash(PasswordSecurity.getHash(HashAlgorithm.SHA256, auth.getHash(), auth.getNickname())); + if (Settings.getPasswordHash == HashAlgorithm.PLAINTEXT) { + ConsoleLogger.showError("Your HashAlgorithm has been detected as plaintext and is now deprecated; " + + "it will be changed and hashed now to the AuthMe default hashing method"); + for (PlayerAuth auth : database.getAllAuths()) { + EncryptedPassword encryptedPassword = passwordSecurity.computeHash( + HashAlgorithm.SHA256, auth.getPassword().getHash(), auth.getNickname()); + auth.setPassword(encryptedPassword); database.updatePassword(auth); } Settings.setValue("settings.security.passwordHash", "SHA256"); @@ -720,8 +723,8 @@ public class AuthMe extends JavaPlugin { } // Save Player Data - public void savePlayer(Player player) { - if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { + private void savePlayer(Player player) { + if (Utils.isNPC(player) || Utils.isUnrestricted(player)) { return; } String name = player.getName().toLowerCase(); @@ -836,10 +839,10 @@ public class AuthMe extends JavaPlugin { // Return the AuthMe spawn point private Location getAuthMeSpawn(Player player) { - if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { + if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) + && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); - } - if (Spawn.getInstance().getSpawn() != null) { + } else if (Spawn.getInstance().getSpawn() != null) { return Spawn.getInstance().getSpawn(); } return player.getWorld().getSpawnLocation(); @@ -889,8 +892,7 @@ public class AuthMe extends JavaPlugin { * */ @Deprecated - public void getVerygamesIp(final Player player) - { + public void getVerygamesIp(final Player player) { final String name = player.getName().toLowerCase(); Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable(){ @Override @@ -986,4 +988,12 @@ public class AuthMe extends JavaPlugin { return management; } + public DataSource getDataSource() { + return database; + } + + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; + } + } diff --git a/src/main/java/fr/xephi/authme/api/API.java b/src/main/java/fr/xephi/authme/api/API.java index 1f1ac69d..dac04986 100644 --- a/src/main/java/fr/xephi/authme/api/API.java +++ b/src/main/java/fr/xephi/authme/api/API.java @@ -4,7 +4,7 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -12,34 +12,37 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; -import java.security.NoSuchAlgorithmException; - /** + * Deprecated API of AuthMe. Please use {@link NewAPI} instead. */ +@Deprecated public class API { public static final String newline = System.getProperty("line.separator"); public static AuthMe instance; + private static PasswordSecurity passwordSecurity; /** - * Constructor for API. + * Constructor for the deprecated API. * * @param instance AuthMe */ @Deprecated public API(AuthMe instance) { API.instance = instance; + passwordSecurity = instance.getPasswordSecurity(); } /** - * Hook into AuthMe + * Hook into AuthMe. * * @return AuthMe instance */ @Deprecated public static AuthMe hookAuthMe() { - if (instance != null) + if (instance != null) { return instance; + } Plugin plugin = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); if (plugin == null || !(plugin instanceof AuthMe)) { return null; @@ -49,9 +52,10 @@ public class API { } /** - * @param player + * Return whether the player is authenticated. * - * @return true if player is authenticate + * @param player The player to verify + * @return true if the player is authenticated */ @Deprecated public static boolean isAuthenticated(Player player) { @@ -59,8 +63,9 @@ public class API { } /** - * @param player + * Return whether the player is unrestricted. * + * @param player The player to verify * @return true if the player is unrestricted */ @Deprecated @@ -68,13 +73,6 @@ public class API { return Utils.isUnrestricted(player); } - /** - * Method getLastLocation. - * - * @param player Player - * - * @return Location - */ @Deprecated public static Location getLastLocation(Player player) { try { @@ -92,13 +90,6 @@ public class API { } } - /** - * Method setPlayerInventory. - * - * @param player Player - * @param content ItemStack[] - * @param armor ItemStack[] - */ @Deprecated public static void setPlayerInventory(Player player, ItemStack[] content, ItemStack[] armor) { @@ -110,93 +101,72 @@ public class API { } /** - * @param playerName + * Check whether the given player name is registered. * + * @param playerName The player name to verify * @return true if player is registered */ @Deprecated public static boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return instance.database.isAuthAvailable(player); + return instance.getDataSource().isAuthAvailable(player); } /** - * @param playerName String - * @param passwordToCheck String + * Check the password for the given player. * - * @return true if the password is correct , false else + * @param playerName The name of the player + * @param passwordToCheck The password to check + * @return true if the password is correct, false otherwise */ @Deprecated - public static boolean checkPassword(String playerName, - String passwordToCheck) { - if (!isRegistered(playerName)) - return false; - String player = playerName.toLowerCase(); - PlayerAuth auth = instance.database.getAuth(player); - try { - return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName); - } catch (NoSuchAlgorithmException e) { - return false; - } + public static boolean checkPassword(String playerName, String passwordToCheck) { + return isRegistered(playerName) && passwordSecurity.comparePassword(passwordToCheck, playerName); } /** - * Register a player + * Register a player. * - * @param playerName String - * @param password String - * - * @return true if the player is register correctly + * @param playerName The name of the player + * @param password The password + * @return true if the player was registered correctly */ @Deprecated public static boolean registerPlayer(String playerName, String password) { - try { - String name = playerName.toLowerCase(); - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - if (isRegistered(name)) { - return false; - } - PlayerAuth auth = new PlayerAuth(name, hash, "198.18.0.1", 0, "your@email.com", playerName); - return instance.database.saveAuth(auth); - } catch (NoSuchAlgorithmException ex) { + String name = playerName.toLowerCase(); + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(password, name); + if (isRegistered(name)) { return false; } + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .password(encryptedPassword) + .lastLogin(0) + .realName(playerName) + .build(); + return instance.getDataSource().saveAuth(auth); } /** - * Force a player to login + * Force a player to log in. * - * @param player * player + * @param player The player to log in */ @Deprecated public static void forceLogin(Player player) { instance.getManagement().performLogin(player, "dontneed", true); } - /** - * Method getPlugin. - * - * @return AuthMe - */ @Deprecated public AuthMe getPlugin() { return instance; } /** - * @param player + * Check whether the player is an NPC. * - * @return true if player is a npc - */ - @Deprecated - public boolean isaNPC(Player player) { - return Utils.isNPC(player); - } - - /** - * @param player - * - * @return true if player is a npc + * @param player The player to verify + * @return true if player is an npc */ @Deprecated public boolean isNPC(Player player) { diff --git a/src/main/java/fr/xephi/authme/api/NewAPI.java b/src/main/java/fr/xephi/authme/api/NewAPI.java index 908f75df..ed16fd52 100644 --- a/src/main/java/fr/xephi/authme/api/NewAPI.java +++ b/src/main/java/fr/xephi/authme/api/NewAPI.java @@ -3,8 +3,7 @@ package fr.xephi.authme.api; import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.util.Utils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -12,9 +11,8 @@ import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import java.security.NoSuchAlgorithmException; - /** + * The current API of AuthMe. */ public class NewAPI { @@ -42,33 +40,36 @@ public class NewAPI { /** * Hook into AuthMe * - * @return AuthMe plugin + * @return The API object */ public static NewAPI getInstance() { - if (singleton != null) + if (singleton != null) { return singleton; + } Plugin p = Bukkit.getServer().getPluginManager().getPlugin("AuthMe"); if (p == null || !(p instanceof AuthMe)) { return null; } AuthMe authme = (AuthMe) p; - singleton = (new NewAPI(authme)); + singleton = new NewAPI(authme); return singleton; } /** - * Method getPlugin. + * Return the plugin instance. * - * @return AuthMe + * @return The AuthMe instance */ public AuthMe getPlugin() { return plugin; } /** - * @param player + * Return whether the given player is authenticated. * - * @return true if player is authenticate + * @param player The player to verify + * + * @return true if the player is authenticated */ public boolean isAuthenticated(Player player) { return PlayerCache.getInstance().isAuthenticated(player.getName()); @@ -93,15 +94,15 @@ public class NewAPI { } /** - * Method getLastLocation. + * Get the last location of a player. * - * @param player Player + * @param player Player The player to process * - * @return Location + * @return Location The location of the player */ public Location getLastLocation(Player player) { try { - PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName().toLowerCase()); + PlayerAuth auth = PlayerCache.getInstance().getAuth(player.getName()); if (auth != null) { return new Location(Bukkit.getWorld(auth.getWorld()), auth.getQuitLocX(), auth.getQuitLocY(), auth.getQuitLocZ()); @@ -115,87 +116,83 @@ public class NewAPI { } /** - * @param playerName + * Return whether the player is registered. * - * @return true if player is registered + * @param playerName The player name to check + * + * @return true if player is registered, false otherwise */ public boolean isRegistered(String playerName) { String player = playerName.toLowerCase(); - return plugin.database.isAuthAvailable(player); + return plugin.getDataSource().isAuthAvailable(player); } /** - * @param playerName String - * @param passwordToCheck String + * Check the password for the given player. * - * @return true if the password is correct , false else + * @param playerName The player to check the password for + * @param passwordToCheck The password to check + * + * @return true if the password is correct, false otherwise */ public boolean checkPassword(String playerName, String passwordToCheck) { - if (!isRegistered(playerName)) - return false; - String player = playerName.toLowerCase(); - PlayerAuth auth = plugin.database.getAuth(player); - try { - return PasswordSecurity.comparePasswordWithHash(passwordToCheck, auth.getHash(), playerName); - } catch (NoSuchAlgorithmException e) { - return false; - } + return isRegistered(playerName) && plugin.getPasswordSecurity().comparePassword(passwordToCheck, playerName); } /** - * Register a player + * Register a player. * - * @param playerName String - * @param password String + * @param playerName The player to register + * @param password The password to register the player with * - * @return true if the player is register correctly + * @return true if the player was registered successfully */ public boolean registerPlayer(String playerName, String password) { - try { - String name = playerName.toLowerCase(); - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - if (isRegistered(name)) { - return false; - } - PlayerAuth auth = new PlayerAuth(name, hash, "192.168.0.1", 0, "your@email.com", playerName); - return plugin.database.saveAuth(auth); - } catch (NoSuchAlgorithmException ex) { + String name = playerName.toLowerCase(); + EncryptedPassword result = plugin.getPasswordSecurity().computeHash(password, name); + if (isRegistered(name)) { return false; } + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .password(result) + .realName(playerName) + .build(); + return plugin.getDataSource().saveAuth(auth); } /** - * Force a player to login + * Force a player to login. * - * @param player * player + * @param player The player to log in */ public void forceLogin(Player player) { plugin.getManagement().performLogin(player, "dontneed", true); } /** - * Force a player to logout + * Force a player to logout. * - * @param player * player + * @param player The player to log out */ public void forceLogout(Player player) { plugin.getManagement().performLogout(player); } /** - * Force a player to register + * Force a player to register. * - * @param player * player - * @param password String + * @param player The player to register + * @param password The password to use */ public void forceRegister(Player player, String password) { plugin.getManagement().performRegister(player, password, null); } /** - * Force a player to unregister + * Force a player to unregister. * - * @param player * player + * @param player The player to unregister */ public void forceUnregister(Player player) { plugin.getManagement().performUnregister(player, "", true); diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java index c501e8eb..f172f221 100644 --- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java +++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java @@ -1,11 +1,10 @@ package fr.xephi.authme.cache.auth; -import fr.xephi.authme.security.HashAlgorithm; -import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.security.crypts.EncryptedPassword; +import org.bukkit.Location; import static com.google.common.base.Objects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.nullToEmpty; /** @@ -13,14 +12,13 @@ import static com.google.common.base.Strings.nullToEmpty; public class PlayerAuth { private String nickname; - private String hash; + private EncryptedPassword password; private String ip; private long lastLogin; private double x; private double y; private double z; private String world; - private String salt; private int groupId; private String email; private String realName; @@ -41,7 +39,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String ip, long lastLogin, String realName) { - this(nickname, "", "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(""), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -55,7 +53,8 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, double x, double y, double z, String world, String realName) { - this(nickname, "", "", -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, "your@email.com", realName); + this(nickname, new EncryptedPassword(""), -1, "127.0.0.1", System.currentTimeMillis(), x, y, z, world, + "your@email.com", realName); } /** @@ -68,7 +67,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -82,7 +81,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String ip, long lastLogin, String email, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, 0, 0, 0, "world", email, realName); + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", email, realName); } /** @@ -96,7 +95,7 @@ public class PlayerAuth { * @param realName String */ public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) { - this(nickname, hash, salt, -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); + this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); } /** @@ -113,8 +112,9 @@ public class PlayerAuth { * @param email String * @param realName String */ - public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { - this(nickname, hash, "", -1, ip, lastLogin, x, y, z, world, email, realName); + public PlayerAuth(String nickname, String hash, String ip, long lastLogin, double x, double y, double z, + String world, String email, String realName) { + this(nickname, new EncryptedPassword(hash), -1, ip, lastLogin, x, y, z, world, email, realName); } /** @@ -132,8 +132,10 @@ public class PlayerAuth { * @param email String * @param realName String */ - public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, double z, String world, String email, String realName) { - this(nickname, hash, salt, -1, ip, lastLogin, x, y, z, world, email, realName); + public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y, + double z, String world, String email, String realName) { + this(nickname, new EncryptedPassword(hash, salt), -1, ip, lastLogin, + x, y, z, world, email, realName); } /** @@ -147,38 +149,37 @@ public class PlayerAuth { * @param lastLogin long * @param realName String */ - public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, long lastLogin, String realName) { - this(nickname, hash, salt, groupId, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName); - } - - /** - * Constructor for PlayerAuth. - * - * @param nickname String - * @param hash String - * @param salt String - * @param groupId int - * @param ip String - * @param lastLogin long - * @param x double - * @param y double - * @param z double - * @param world String - * @param email String - * @param realName String - */ public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip, - long lastLogin, double x, double y, double z, String world, String email, - String realName) { + long lastLogin, String realName) { + this(nickname, new EncryptedPassword(hash, salt), groupId, ip, lastLogin, + 0, 0, 0, "world", "your@email.com", realName); + } + + /** + * Constructor for PlayerAuth. + * + * @param nickname String + * @param password String + * @param groupId int + * @param ip String + * @param lastLogin long + * @param x double + * @param y double + * @param z double + * @param world String + * @param email String + * @param realName String + */ + public PlayerAuth(String nickname, EncryptedPassword password, int groupId, String ip, long lastLogin, + double x, double y, double z, String world, String email, String realName) { this.nickname = nickname.toLowerCase(); - this.hash = hash; + this.password = password; this.ip = ip; this.lastLogin = lastLogin; this.x = x; this.y = y; this.z = z; this.world = world; - this.salt = salt; this.groupId = groupId; this.email = email; this.realName = realName; @@ -191,237 +192,108 @@ public class PlayerAuth { */ public void set(PlayerAuth auth) { this.setEmail(auth.getEmail()); - this.setHash(auth.getHash()); + this.setPassword(auth.getPassword()); this.setIp(auth.getIp()); this.setLastLogin(auth.getLastLogin()); this.setNickname(auth.getNickname()); this.setQuitLocX(auth.getQuitLocX()); this.setQuitLocY(auth.getQuitLocY()); this.setQuitLocZ(auth.getQuitLocZ()); - this.setSalt(auth.getSalt()); this.setWorld(auth.getWorld()); this.setRealName(auth.getRealName()); } - /** - * Method setNickname. - * - * @param nickname String - */ + public void setNickname(String nickname) { this.nickname = nickname.toLowerCase(); } - /** - * Method getNickname. - * - * @return String - */ public String getNickname() { return nickname; } - /** - * Method getRealName. - * - * @return String - */ public String getRealName() { return realName; } - /** - * Method setRealName. - * - * @param realName String - */ public void setRealName(String realName) { this.realName = realName; } - /** - * Method getGroupId. - * - * @return int - */ public int getGroupId() { return groupId; } - /** - * Method getQuitLocX. - * - * @return double - */ public double getQuitLocX() { return x; } - /** - * Method setQuitLocX. - * - * @param d double - */ public void setQuitLocX(double d) { this.x = d; } - /** - * Method getQuitLocY. - * - * @return double - */ public double getQuitLocY() { return y; } - /** - * Method setQuitLocY. - * - * @param d double - */ public void setQuitLocY(double d) { this.y = d; } - /** - * Method getQuitLocZ. - * - * @return double - */ public double getQuitLocZ() { return z; } - /** - * Method setQuitLocZ. - * - * @param d double - */ public void setQuitLocZ(double d) { this.z = d; } - /** - * Method getWorld. - * - * @return String - */ public String getWorld() { return world; } - /** - * Method setWorld. - * - * @param world String - */ public void setWorld(String world) { this.world = world; } - /** - * Method getIp. - * - * @return String - */ public String getIp() { return ip; } - /** - * Method setIp. - * - * @param ip String - */ public void setIp(String ip) { this.ip = ip; } - /** - * Method getLastLogin. - * - * @return long - */ public long getLastLogin() { return lastLogin; } - /** - * Method setLastLogin. - * - * @param lastLogin long - */ public void setLastLogin(long lastLogin) { this.lastLogin = lastLogin; } - /** - * Method getEmail. - * - * @return String - */ public String getEmail() { return email; } - /** - * Method setEmail. - * - * @param email String - */ public void setEmail(String email) { this.email = email; } - /** - * Method getSalt. - * - * @return String - */ - public String getSalt() { - return this.salt; - } - - /** - * Method setSalt. - * - * @param salt String - */ - public void setSalt(String salt) { - this.salt = salt; - } - - /** - * Method getHash. - * - * @return String - */ - public String getHash() { - if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { + public EncryptedPassword getPassword() { + // TODO #358: Check whether this check is really necessary. It's been here since the first commit. + /*if (Settings.getPasswordHash == HashAlgorithm.MD5VB) { if (salt != null && !salt.isEmpty() && Settings.getPasswordHash == HashAlgorithm.MD5VB) { return "$MD5vb$" + salt + "$" + hash; } - } - return hash; + }*/ + return password; } - /** - * Method setHash. - * - * @param hash String - */ - public void setHash(String hash) { - this.hash = hash; + public void setPassword(EncryptedPassword password) { + this.password = password; } - /** - * Method equals. - * - * @param obj Object - * - * @return boolean - */ @Override public boolean equals(Object obj) { if (!(obj instanceof PlayerAuth)) { @@ -431,11 +303,6 @@ public class PlayerAuth { return other.getIp().equals(this.ip) && other.getNickname().equals(this.nickname); } - /** - * Method hashCode. - * - * @return int - */ @Override public int hashCode() { int hashCode = 7; @@ -444,20 +311,14 @@ public class PlayerAuth { return hashCode; } - /** - * Method toString. - * - * @return String - */ @Override public String toString() { - return ("Player : " + nickname + " | " + realName + return "Player : " + nickname + " | " + realName + " ! IP : " + ip + " ! LastLogin : " + lastLogin + " ! LastPosition : " + x + "," + y + "," + z + "," + world + " ! Email : " + email - + " ! Hash : " + hash - + " ! Salt : " + salt); + + " ! Password : {" + password.getHash() + ", " + password.getSalt() + "}"; } /** @@ -472,8 +333,8 @@ public class PlayerAuth { str.append(this.realName).append(d); str.append(this.ip).append(d); str.append(this.email).append(d); - str.append(this.hash).append(d); - str.append(this.salt).append(d); + str.append(this.password.getHash()).append(d); + str.append(this.password.getSalt()).append(d); str.append(this.groupId).append(d); str.append(this.lastLogin).append(d); str.append(this.world).append(d); @@ -492,8 +353,7 @@ public class PlayerAuth { this.realName = args[1]; this.ip = args[2]; this.email = args[3]; - this.hash = args[4]; - this.salt = args[5]; + this.password = new EncryptedPassword(args[4], args[5]); this.groupId = Integer.parseInt(args[6]); this.lastLogin = Long.parseLong(args[7]); this.world = args[8]; @@ -509,8 +369,7 @@ public class PlayerAuth { public static final class Builder { private String name; private String realName; - private String hash; - private String salt; + private EncryptedPassword password; private String ip; private String world; private String email; @@ -523,8 +382,7 @@ public class PlayerAuth { public PlayerAuth build() { return new PlayerAuth( checkNotNull(name), - nullToEmpty(hash), - nullToEmpty(salt), + firstNonNull(password, new EncryptedPassword("")), groupId, firstNonNull(ip, "127.0.0.1"), lastLogin, @@ -545,14 +403,13 @@ public class PlayerAuth { return this; } - public Builder hash(String hash) { - this.hash = hash; + public Builder password(EncryptedPassword password) { + this.password = password; return this; } - public Builder salt(String salt) { - this.salt = salt; - return this; + public Builder password(String hash, String salt) { + return password(new EncryptedPassword(hash, salt)); } public Builder ip(String ip) { @@ -560,6 +417,14 @@ public class PlayerAuth { return this; } + public Builder location(Location location) { + this.x = location.getX(); + this.y = location.getY(); + this.z = location.getZ(); + this.world = location.getWorld().getName(); + return this; + } + public Builder locWorld(String world) { this.world = world; return this; diff --git a/src/main/java/fr/xephi/authme/command/CommandService.java b/src/main/java/fr/xephi/authme/command/CommandService.java index 85cf9b22..b53fc2e8 100644 --- a/src/main/java/fr/xephi/authme/command/CommandService.java +++ b/src/main/java/fr/xephi/authme/command/CommandService.java @@ -7,6 +7,7 @@ import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import java.util.List; @@ -21,6 +22,7 @@ public class CommandService { private final Messages messages; private final HelpProvider helpProvider; private final CommandMapper commandMapper; + private final PasswordSecurity passwordSecurity; /** * Constructor. @@ -30,11 +32,13 @@ public class CommandService { * @param helpProvider Help provider * @param messages Messages instance */ - public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages) { + public CommandService(AuthMe authMe, CommandMapper commandMapper, HelpProvider helpProvider, Messages messages, + PasswordSecurity passwordSecurity) { this.authMe = authMe; this.messages = messages; this.helpProvider = helpProvider; this.commandMapper = commandMapper; + this.passwordSecurity = passwordSecurity; } /** @@ -88,8 +92,16 @@ public class CommandService { * @return The used data source */ public DataSource getDataSource() { - // TODO ljacqu 20151222: Add getter for .database and rename the field to dataSource - return authMe.database; + return authMe.getDataSource(); + } + + /** + * Return the PasswordSecurity instance. + * + * @return The password security instance + */ + public PasswordSecurity getPasswordSecurity() { + return passwordSecurity; } /** diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java index 1905d838..17f3b333 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/ChangePasswordAdminCommand.java @@ -1,6 +1,5 @@ package fr.xephi.authme.command.executable.authme; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; @@ -8,13 +7,10 @@ import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.output.Messages; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -60,13 +56,6 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { @Override public void run() { DataSource dataSource = commandService.getDataSource(); - String hash; - try { - hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - } catch (NoSuchAlgorithmException e) { - commandService.send(sender, MessageKey.ERROR); - return; - } PlayerAuth auth = null; if (PlayerCache.getInstance().isAuthenticated(playerNameLowerCase)) { auth = PlayerCache.getInstance().getAuth(playerNameLowerCase); @@ -77,17 +66,17 @@ public class ChangePasswordAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.UNKNOWN_USER); return; } - auth.setHash(hash); - if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase)) { - auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); - commandService.getDataSource().updateSalt(auth); - } + + // TODO #358: Do we always pass lowercase name?? In that case we need to do that in PasswordSecurity! + EncryptedPassword encryptedPassword = commandService.getPasswordSecurity().computeHash(playerPass, playerNameLowerCase); + auth.setPassword(encryptedPassword); + if (!dataSource.updatePassword(auth)) { commandService.send(sender, MessageKey.ERROR); - return; + } else { + commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } - commandService.send(sender, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + "'s password changed"); } }); diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java index 01d6b38b..b94ce260 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/PurgeBannedPlayersCommand.java @@ -24,7 +24,7 @@ public class PurgeBannedPlayersCommand implements ExecutableCommand { } // Purge the banned players - plugin.database.purgeBanned(bannedPlayers); + plugin.getDataSource().purgeBanned(bannedPlayers); if (Settings.purgeEssentialsFile && plugin.ess != null) plugin.dataManager.purgeEssentials(bannedPlayers); if (Settings.purgePlayerDat) diff --git a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java index 0a752c44..d652bdb5 100644 --- a/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/authme/RegisterAdminCommand.java @@ -5,12 +5,11 @@ import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.ExecutableCommand; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -41,7 +40,8 @@ public class RegisterAdminCommand implements ExecutableCommand { commandService.send(sender, MessageKey.PASSWORD_IS_USERNAME_ERROR); return; } - if (playerPassLowerCase.length() < Settings.getPasswordMinLen || playerPassLowerCase.length() > Settings.passwordMaxLength) { + if (playerPassLowerCase.length() < Settings.getPasswordMinLen + || playerPassLowerCase.length() > Settings.passwordMaxLength) { commandService.send(sender, MessageKey.INVALID_PASSWORD_LENGTH); return; } @@ -53,30 +53,29 @@ public class RegisterAdminCommand implements ExecutableCommand { @Override public void run() { - try { - if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { - commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); - return; - } - String hash = PasswordSecurity.getHash(Settings.getPasswordHash, playerPass, playerNameLowerCase); - PlayerAuth auth = new PlayerAuth(playerNameLowerCase, hash, "192.168.0.1", 0L, "your@email.com", playerName); - if (PasswordSecurity.userSalt.containsKey(playerNameLowerCase) && PasswordSecurity.userSalt.get(playerNameLowerCase) != null) - auth.setSalt(PasswordSecurity.userSalt.get(playerNameLowerCase)); - else auth.setSalt(""); - if (!commandService.getDataSource().saveAuth(auth)) { - commandService.send(sender, MessageKey.ERROR); - return; - } - commandService.getDataSource().setUnlogged(playerNameLowerCase); - if (Bukkit.getPlayerExact(playerName) != null) - Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); - commandService.send(sender, MessageKey.REGISTER_SUCCESS); - ConsoleLogger.info(playerNameLowerCase + " registered"); - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - commandService.send(sender, MessageKey.ERROR); + if (commandService.getDataSource().isAuthAvailable(playerNameLowerCase)) { + commandService.send(sender, MessageKey.NAME_ALREADY_REGISTERED); + return; } + EncryptedPassword encryptedPassword = commandService.getPasswordSecurity() + .computeHash(playerPass, playerNameLowerCase); + PlayerAuth auth = PlayerAuth.builder() + .name(playerNameLowerCase) + .realName(playerName) + .password(encryptedPassword) + .build(); + if (!commandService.getDataSource().saveAuth(auth)) { + commandService.send(sender, MessageKey.ERROR); + return; + } + commandService.getDataSource().setUnlogged(playerNameLowerCase); + if (Bukkit.getPlayerExact(playerName) != null) { + Bukkit.getPlayerExact(playerName).kickPlayer("An admin just registered you, please log again"); + } else { + commandService.send(sender, MessageKey.REGISTER_SUCCESS); + ConsoleLogger.info(playerName + " registered"); + } } }); } diff --git a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java index b9e23681..d21bac77 100644 --- a/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/captcha/CaptchaCommand.java @@ -41,7 +41,7 @@ public class CaptchaCommand extends PlayerCommand { if (Settings.useCaptcha && !captcha.equals(plugin.cap.get(playerNameLowerCase))) { plugin.cap.remove(playerNameLowerCase); - String randStr = new RandomString(Settings.captchaLength).nextString(); + String randStr = RandomString.generate(Settings.captchaLength); plugin.cap.put(playerNameLowerCase, randStr); commandService.send(player, MessageKey.CAPTCHA_WRONG_ERROR, plugin.cap.get(playerNameLowerCase)); return; diff --git a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java index 63a8825d..5e2ed660 100644 --- a/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java +++ b/src/main/java/fr/xephi/authme/command/executable/email/RecoverEmailCommand.java @@ -1,20 +1,18 @@ package fr.xephi.authme.command.executable.email; import fr.xephi.authme.AuthMe; -import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.command.CommandService; import fr.xephi.authme.command.PlayerCommand; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; import org.bukkit.entity.Player; -import java.security.NoSuchAlgorithmException; import java.util.List; public class RecoverEmailCommand extends PlayerCommand { @@ -37,37 +35,32 @@ public class RecoverEmailCommand extends PlayerCommand { commandService.send(player, MessageKey.ALREADY_LOGGED_IN_ERROR); return; } - try { - RandomString rand = new RandomString(Settings.getRecoveryPassLength); - String thePass = rand.nextString(); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, thePass, playerName); - PlayerAuth auth; - if (PlayerCache.getInstance().isAuthenticated(playerName)) { - auth = PlayerCache.getInstance().getAuth(playerName); - } else if (dataSource.isAuthAvailable(playerName)) { - auth = dataSource.getAuth(playerName); - } else { - commandService.send(player, MessageKey.UNKNOWN_USER); - return; - } - if (Settings.getmailAccount.equals("") || Settings.getmailAccount.isEmpty()) { - commandService.send(player, MessageKey.ERROR); - return; - } - if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") - || auth.getEmail().equalsIgnoreCase("your@email.com")) { - commandService.send(player, MessageKey.INVALID_EMAIL); - return; - } - auth.setHash(hashNew); - dataSource.updatePassword(auth); - plugin.mail.main(auth, thePass); - commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); - } catch (NoSuchAlgorithmException | NoClassDefFoundError ex) { - ConsoleLogger.showError(StringUtils.formatException(ex)); - commandService.send(player, MessageKey.ERROR); + String thePass = RandomString.generate(Settings.getRecoveryPassLength); + EncryptedPassword hashNew = commandService.getPasswordSecurity().computeHash(thePass, playerName); + PlayerAuth auth; + if (PlayerCache.getInstance().isAuthenticated(playerName)) { + auth = PlayerCache.getInstance().getAuth(playerName); + } else if (dataSource.isAuthAvailable(playerName)) { + auth = dataSource.getAuth(playerName); + } else { + commandService.send(player, MessageKey.UNKNOWN_USER); + return; } + if (StringUtils.isEmpty(Settings.getmailAccount)) { + commandService.send(player, MessageKey.ERROR); + return; + } + + if (!playerMail.equalsIgnoreCase(auth.getEmail()) || playerMail.equalsIgnoreCase("your@email.com") + || auth.getEmail().equalsIgnoreCase("your@email.com")) { + commandService.send(player, MessageKey.INVALID_EMAIL); + return; + } + auth.setPassword(hashNew); + dataSource.updatePassword(auth); + plugin.mail.main(auth, thePass); + commandService.send(player, MessageKey.RECOVERY_EMAIL_SENT_MESSAGE); } else { commandService.send(player, MessageKey.REGISTER_EMAIL_MESSAGE); } diff --git a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java index dd592a1a..8b4ee564 100644 --- a/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java +++ b/src/main/java/fr/xephi/authme/converter/CrazyLoginConverter.java @@ -27,7 +27,7 @@ public class CrazyLoginConverter implements Converter { * @param sender CommandSender */ public CrazyLoginConverter(AuthMe instance, CommandSender sender) { - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } diff --git a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java index ab8b754f..094eb668 100644 --- a/src/main/java/fr/xephi/authme/converter/RakamakConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RakamakConverter.java @@ -6,6 +6,7 @@ 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.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.command.CommandSender; @@ -13,7 +14,6 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.Map.Entry; @@ -28,15 +28,12 @@ public class RakamakConverter implements Converter { public RakamakConverter(AuthMe instance, CommandSender sender) { this.instance = instance; - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } - public RakamakConverter getInstance() { - return this; - } - @Override + // TODO ljacqu 20151229: Restructure this into smaller portions public void run() { HashAlgorithm hash = Settings.getPasswordHash; boolean useIP = Settings.rakamakUseIp; @@ -45,7 +42,7 @@ public class RakamakConverter implements Converter { File source = new File(Settings.PLUGIN_FOLDER, fileName); File ipfiles = new File(Settings.PLUGIN_FOLDER, ipFileName); HashMap playerIP = new HashMap<>(); - HashMap playerPSW = new HashMap<>(); + HashMap playerPSW = new HashMap<>(); try { BufferedReader users; BufferedReader ipFile; @@ -61,30 +58,29 @@ public class RakamakConverter implements Converter { } } ipFile.close(); + users = new BufferedReader(new FileReader(source)); + PasswordSecurity passwordSecurity = instance.getPasswordSecurity(); while ((line = users.readLine()) != null) { if (line.contains("=")) { String[] arguments = line.split("="); - try { - playerPSW.put(arguments[0], PasswordSecurity.getHash(hash, arguments[1], arguments[0])); - } catch (NoSuchAlgorithmException e) { - ConsoleLogger.showError(e.getMessage()); - } + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(hash, arguments[1], arguments[0]); + playerPSW.put(arguments[0], encryptedPassword); + } } users.close(); - for (Entry m : playerPSW.entrySet()) { + for (Entry m : playerPSW.entrySet()) { String playerName = m.getKey(); - String psw = playerPSW.get(playerName); - String ip; - if (useIP) { - ip = playerIP.get(playerName); - } else { - ip = "127.0.0.1"; - } - PlayerAuth auth = new PlayerAuth(playerName, psw, ip, System.currentTimeMillis(), playerName); - if (PasswordSecurity.userSalt.containsKey(playerName)) - auth.setSalt(PasswordSecurity.userSalt.get(playerName)); + EncryptedPassword psw = playerPSW.get(playerName); + String ip = useIP ? playerIP.get(playerName) : "127.0.0.1"; + PlayerAuth auth = PlayerAuth.builder() + .name(playerName) + .realName(playerName) + .ip(ip) + .password(psw) + .lastLogin(System.currentTimeMillis()) + .build(); database.saveAuth(auth); } ConsoleLogger.info("Rakamak database has been imported correctly"); diff --git a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java index 14d289fa..5e85ce6e 100644 --- a/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java +++ b/src/main/java/fr/xephi/authme/converter/RoyalAuthConverter.java @@ -15,7 +15,7 @@ public class RoyalAuthConverter implements Converter { public RoyalAuthConverter(AuthMe plugin) { this.plugin = plugin; - this.data = plugin.database; + this.data = plugin.getDataSource(); } @Override diff --git a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java index cb36f66e..37df161a 100644 --- a/src/main/java/fr/xephi/authme/converter/SqliteToSql.java +++ b/src/main/java/fr/xephi/authme/converter/SqliteToSql.java @@ -21,16 +21,14 @@ public class SqliteToSql implements Converter { @Override public void run() { - if (plugin.database.getType() != DataSourceType.MYSQL) - { + if (plugin.getDataSource().getType() != DataSourceType.MYSQL) { sender.sendMessage("Please config your mySQL connection and re-run this command"); return; } try { SQLite data = new SQLite(); - for (PlayerAuth auth : data.getAllAuths()) - { - plugin.database.saveAuth(auth); + for (PlayerAuth auth : data.getAllAuths()) { + plugin.getDataSource().saveAuth(auth); } } catch (Exception e) { sender.sendMessage(plugin.getMessages().retrieve(MessageKey.ERROR)); diff --git a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java index a72c149d..16b89e5a 100644 --- a/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java +++ b/src/main/java/fr/xephi/authme/converter/vAuthFileReader.java @@ -23,7 +23,7 @@ class vAuthFileReader { */ public vAuthFileReader(AuthMe plugin) { this.plugin = plugin; - this.database = plugin.database; + this.database = plugin.getDataSource(); } public void convert() { diff --git a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java index 630d2c6f..52ee4999 100644 --- a/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java +++ b/src/main/java/fr/xephi/authme/converter/xAuthToFlat.java @@ -30,7 +30,7 @@ class xAuthToFlat { */ public xAuthToFlat(AuthMe instance, CommandSender sender) { this.instance = instance; - this.database = instance.database; + this.database = instance.getDataSource(); this.sender = sender; } diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index 8b26b08c..2351c6cb 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -270,24 +270,6 @@ public class CacheDataSource implements DataSource { return result; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public synchronized boolean updateSalt(final PlayerAuth auth) { - boolean result = source.updateSalt(auth); - if (result) { - cachedAuths.refresh(auth.getNickname()); - } - return result; - } - /** * Method getAllAuthsByName. * diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index e43dffa8..10f74b92 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -134,15 +134,6 @@ public interface DataSource { */ boolean updateEmail(PlayerAuth auth); - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - */ - boolean updateSalt(PlayerAuth auth); - void close(); void reload(); diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index b7fb5517..55646ddc 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -102,7 +102,7 @@ public class FlatFile implements DataSource { BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(source, true)); - bw.write(auth.getNickname() + ":" + auth.getHash() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n"); + bw.write(auth.getNickname() + ":" + auth.getPassword() + ":" + auth.getIp() + ":" + auth.getLastLogin() + ":" + auth.getQuitLocX() + ":" + auth.getQuitLocY() + ":" + auth.getQuitLocZ() + ":" + auth.getWorld() + ":" + auth.getEmail() + "\n"); } catch (IOException ex) { ConsoleLogger.showError(ex.getMessage()); return false; @@ -137,25 +137,26 @@ public class FlatFile implements DataSource { while ((line = br.readLine()) != null) { String[] args = line.split(":"); if (args[0].equals(auth.getNickname())) { + // Note ljacqu 20151230: This does not persist the salt; it is not supported in flat file. switch (args.length) { case 4: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); break; } case 7: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), "world", "your@email.com", args[0]); break; } case 8: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], "your@email.com", args[0]); break; } case 9: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], Long.parseLong(args[3]), Double.parseDouble(args[4]), Double.parseDouble(args[5]), Double.parseDouble(args[6]), args[7], args[8], args[0]); break; } default: { - newAuth = new PlayerAuth(args[0], auth.getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], auth.getPassword().getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); break; } } @@ -600,18 +601,6 @@ public class FlatFile implements DataSource { return true; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public boolean updateSalt(PlayerAuth auth) { - return false; - } - /** * Method getAllAuthsByName. * diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index e779afff..1af04cab 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -7,6 +7,7 @@ import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; import java.sql.*; import java.util.ArrayList; @@ -39,11 +40,6 @@ public class MySQL implements DataSource { private final List columnOthers; private HikariDataSource ds; - /** - * Constructor for MySQL. - * - * @throws ClassNotFoundException * @throws SQLException * @throws PoolInitializationException - */ public MySQL() throws ClassNotFoundException, SQLException, PoolInitializationException { this.host = Settings.getMySQLHost; this.port = Settings.getMySQLPort; @@ -96,11 +92,6 @@ public class MySQL implements DataSource { } } - /** - * Method setConnectionArguments. - * - * @throws RuntimeException - */ private synchronized void setConnectionArguments() throws RuntimeException { ds = new HikariDataSource(); ds.setPoolName("AuthMeMYSQLPool"); @@ -116,11 +107,6 @@ public class MySQL implements DataSource { ConsoleLogger.info("Connection arguments loaded, Hikari ConnectionPool ready!"); } - /** - * Method reloadArguments. - * - * @throws RuntimeException - */ private synchronized void reloadArguments() throws RuntimeException { if (ds != null) { ds.close(); @@ -129,20 +115,10 @@ public class MySQL implements DataSource { ConsoleLogger.info("Hikari ConnectionPool arguments reloaded!"); } - /** - * Method getConnection. - * - * @return Connection * @throws SQLException - */ private synchronized Connection getConnection() throws SQLException { return ds.getConnection(); } - /** - * Method setupConnection. - * - * @throws SQLException - */ private synchronized void setupConnection() throws SQLException { try (Connection con = getConnection()) { Statement st = con.createStatement(); @@ -186,6 +162,15 @@ public class MySQL implements DataSource { } rs.close(); + if (!columnSalt.isEmpty()) { + rs = md.getColumns(null, null, tableName, columnSalt); + if (!rs.next()) { + st.executeUpdate("ALTER TABLE " + tableName + + " ADD COLUMN " + columnSalt + " VARCHAR(255);"); + } + rs.close(); + } + rs = md.getColumns(null, null, tableName, columnIp); if (!rs.next()) { st.executeUpdate("ALTER TABLE " + tableName @@ -244,13 +229,6 @@ public class MySQL implements DataSource { ConsoleLogger.info("MySQL Setup finished"); } - /** - * Method isAuthAvailable. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isAuthAvailable(String) - */ @Override public synchronized boolean isAuthAvailable(String user) { try (Connection con = getConnection()) { @@ -266,13 +244,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method getAuth. - * - * @param user String - * - * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) - */ @Override public synchronized PlayerAuth getAuth(String user) { PlayerAuth pAuth; @@ -284,13 +255,12 @@ public class MySQL implements DataSource { if (!rs.next()) { return null; } - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; - int id = rs.getInt(columnID); + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -298,21 +268,10 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); rs.close(); pst.close(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); - pst.setInt(1, id); - rs = pst.executeQuery(); - if (rs.next()) { - Blob blob = rs.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.writeStackTrace(ex); @@ -321,15 +280,6 @@ public class MySQL implements DataSource { return pAuth; } - /** - * Method saveAuth. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#saveAuth(PlayerAuth) - */ @Override public synchronized boolean saveAuth(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -338,7 +288,7 @@ public class MySQL implements DataSource { ResultSet rs; String sql; - boolean useSalt = !columnSalt.isEmpty() || !auth.getSalt().isEmpty(); + boolean useSalt = !columnSalt.isEmpty() || !StringUtils.isEmpty(auth.getPassword().getSalt()); sql = "INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName @@ -346,12 +296,12 @@ public class MySQL implements DataSource { + ") VALUES (?,?,?,?,?" + (useSalt ? ",?" : "") + ");"; pst = con.prepareStatement(sql); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, auth.getPassword().getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); pst.setString(5, auth.getRealName()); if (useSalt) { - pst.setString(6, auth.getSalt()); + pst.setString(6, auth.getPassword().getSalt()); } pst.executeUpdate(); pst.close(); @@ -502,25 +452,6 @@ public class MySQL implements DataSource { } rs.close(); pst.close(); - } else if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - pst = con.prepareStatement("SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"); - pst.setString(1, auth.getNickname()); - rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - // Insert password in the correct table - pst2 = con.prepareStatement("INSERT INTO xf_user_authenticate (user_id, scheme_class, data) VALUES (?,?,?);"); - pst2.setInt(1, id); - pst2.setString(2, "XenForo_Authentication_Core12"); - byte[] bytes = auth.getHash().getBytes(); - Blob blob = con.createBlob(); - blob.setBytes(1, bytes); - pst2.setBlob(3, blob); - pst2.executeUpdate(); - pst2.close(); - } - rs.close(); - pst.close(); } return true; } catch (SQLException ex) { @@ -530,52 +461,27 @@ public class MySQL implements DataSource { return false; } - /** - * Method updatePassword. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updatePassword(PlayerAuth) - */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { try (Connection con = getConnection()) { - String sql = "UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, auth.getHash()); - pst.setString(2, auth.getNickname()); + boolean useSalt = !columnSalt.isEmpty(); + PreparedStatement pst; + if (useSalt) { + String sql = String.format("UPDATE %s SET %s = ?, %s = ? WHERE %s = ?;", + tableName, columnPassword, columnSalt, columnName); + pst = con.prepareStatement(sql); + pst.setString(1, auth.getPassword().getHash()); + pst.setString(2, auth.getPassword().getSalt()); + pst.setString(3, auth.getNickname()); + } else { + String sql = String.format("UPDATE %s SET %s = ? WHERE %s = ?;", + tableName, columnPassword, columnName); + pst = con.prepareStatement(sql); + pst.setString(1, auth.getPassword().getHash()); + pst.setString(2, auth.getNickname()); + } pst.executeUpdate(); pst.close(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"; - pst = con.prepareStatement(sql); - pst.setString(1, auth.getNickname()); - ResultSet rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - // Insert password in the correct table - sql = "UPDATE xf_user_authenticate SET data=? WHERE " + columnID + "=?;"; - PreparedStatement pst2 = con.prepareStatement(sql); - byte[] bytes = auth.getHash().getBytes(); - Blob blob = con.createBlob(); - blob.setBytes(1, bytes); - pst2.setBlob(1, blob); - pst2.setInt(2, id); - pst2.executeUpdate(); - pst2.close(); - // ... - sql = "UPDATE xf_user_authenticate SET scheme_class=? WHERE " + columnID + "=?;"; - pst2 = con.prepareStatement(sql); - pst2.setString(1, "XenForo_Authentication_Core12"); - pst2.setInt(2, id); - pst2.executeUpdate(); - pst2.close(); - } - rs.close(); - pst.close(); - } return true; } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -584,15 +490,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateSession. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSession(PlayerAuth) - */ @Override public synchronized boolean updateSession(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -612,15 +509,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method purgeDatabase. - * - * @param until long - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#purgeDatabase(long) - */ @Override public synchronized int purgeDatabase(long until) { int result = 0; @@ -636,15 +524,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method autoPurgeDatabase. - * - * @param until long - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#autoPurgeDatabase(long) - */ @Override public synchronized List autoPurgeDatabase(long until) { List list = new ArrayList<>(); @@ -666,37 +545,11 @@ public class MySQL implements DataSource { return list; } - /** - * Method removeAuth. - * - * @param user String - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#removeAuth(String) - */ @Override public synchronized boolean removeAuth(String user) { user = user.toLowerCase(); try (Connection con = getConnection()) { - String sql; - PreparedStatement pst; - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - sql = "SELECT " + columnID + " FROM " + tableName + " WHERE " + columnName + "=?;"; - pst = con.prepareStatement(sql); - pst.setString(1, user); - ResultSet rs = pst.executeQuery(); - if (rs.next()) { - int id = rs.getInt(columnID); - sql = "DELETE FROM xf_user_authenticate WHERE " + columnID + "=" + id; - Statement st = con.createStatement(); - st.executeUpdate(sql); - st.close(); - } - rs.close(); - pst.close(); - } - pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;"); + PreparedStatement pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;"); pst.setString(1, user); pst.executeUpdate(); return true; @@ -707,15 +560,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateQuitLoc. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateQuitLoc(PlayerAuth) - */ @Override public synchronized boolean updateQuitLoc(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -738,15 +582,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method getIps. - * - * @param ip String - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#getIps(String) - */ @Override public synchronized int getIps(String ip) { int countIp = 0; @@ -767,15 +602,6 @@ public class MySQL implements DataSource { return countIp; } - /** - * Method updateEmail. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateEmail(PlayerAuth) - */ @Override public synchronized boolean updateEmail(PlayerAuth auth) { try (Connection con = getConnection()) { @@ -793,40 +619,6 @@ public class MySQL implements DataSource { return false; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean - * - * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public synchronized boolean updateSalt(PlayerAuth auth) { - if (columnSalt.isEmpty()) { - return false; - } - try (Connection con = getConnection()) { - String sql = "UPDATE " + tableName + " SET " + columnSalt + " =? WHERE " + columnName + "=?;"; - PreparedStatement pst = con.prepareStatement(sql); - pst.setString(1, auth.getSalt()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - pst.close(); - return true; - } catch (SQLException ex) { - ConsoleLogger.showError(ex.getMessage()); - ConsoleLogger.writeStackTrace(ex); - } - return false; - } - - /** - * Method reload. - * - * @see fr.xephi.authme.datasource.DataSource#reload() - */ @Override public void reload() { try { @@ -839,11 +631,6 @@ public class MySQL implements DataSource { } } - /** - * Method close. - * - * @see fr.xephi.authme.datasource.DataSource#close() - */ @Override public synchronized void close() { if (ds != null && !ds.isClosed()) { @@ -851,15 +638,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAllAuthsByName. - * - * @param auth PlayerAuth - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByName(PlayerAuth) - */ @Override public synchronized List getAllAuthsByName(PlayerAuth auth) { List result = new ArrayList<>(); @@ -880,15 +658,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method getAllAuthsByIp. - * - * @param ip String - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByIp(String) - */ @Override public synchronized List getAllAuthsByIp(String ip) { List result = new ArrayList<>(); @@ -909,15 +678,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method getAllAuthsByEmail. - * - * @param email String - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuthsByEmail(String) - */ @Override public synchronized List getAllAuthsByEmail(String email){ List countEmail = new ArrayList<>(); @@ -938,13 +698,6 @@ public class MySQL implements DataSource { return countEmail; } - /** - * Method purgeBanned. - * - * @param banned List - * - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ @Override public synchronized void purgeBanned(List banned) { try (Connection con = getConnection()) { @@ -960,23 +713,11 @@ public class MySQL implements DataSource { } } - /** - * Method getType. - * - * @return DataSourceType * @see fr.xephi.authme.datasource.DataSource#getType() - */ @Override public DataSourceType getType() { return DataSourceType.MYSQL; } - /** - * Method isLogged. - * - * @param user String - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#isLogged(String) - */ @Override public boolean isLogged(String user) { boolean isLogged = false; @@ -993,13 +734,6 @@ public class MySQL implements DataSource { return isLogged; } - /** - * Method setLogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setLogged(String) - */ @Override public void setLogged(String user) { try (Connection con = getConnection()) { @@ -1015,13 +749,6 @@ public class MySQL implements DataSource { } } - /** - * Method setUnlogged. - * - * @param user String - * - * @see fr.xephi.authme.datasource.DataSource#setUnlogged(String) - */ @Override public void setUnlogged(String user) { try (Connection con = getConnection()) { @@ -1037,11 +764,6 @@ public class MySQL implements DataSource { } } - /** - * Method purgeLogged. - * - * @see fr.xephi.authme.datasource.DataSource#purgeLogged() - */ @Override public void purgeLogged() { try (Connection con = getConnection()) { @@ -1057,13 +779,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAccountsRegistered. - * - * @return int - * - * @see fr.xephi.authme.datasource.DataSource#getAccountsRegistered() - */ @Override public int getAccountsRegistered() { int result = 0; @@ -1082,14 +797,6 @@ public class MySQL implements DataSource { return result; } - /** - * Method updateName. - * - * @param oldOne String - * @param newOne String - * - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ @Override public void updateName(String oldOne, String newOne) { try (Connection con = getConnection()) { @@ -1104,13 +811,6 @@ public class MySQL implements DataSource { } } - /** - * Method getAllAuths. - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getAllAuths() - */ @Override public List getAllAuths() { List auths = new ArrayList<>(); @@ -1119,12 +819,12 @@ public class MySQL implements DataSource { ResultSet rs = st.executeQuery("SELECT * FROM " + tableName); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); while (rs.next()) { - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -1132,21 +832,9 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - int id = rs.getInt(columnID); - pst.setInt(1, id); - ResultSet rs2 = pst.executeQuery(); - if (rs2.next()) { - Blob blob = rs2.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - rs2.close(); - } auths.add(pAuth); } pst.close(); @@ -1159,13 +847,6 @@ public class MySQL implements DataSource { return auths; } - /** - * Method getLoggedPlayers. - * - * @return List - * - * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() - */ @Override public List getLoggedPlayers() { List auths = new ArrayList<>(); @@ -1174,12 +855,12 @@ public class MySQL implements DataSource { ResultSet rs = st.executeQuery("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); PreparedStatement pst = con.prepareStatement("SELECT data FROM xf_user_authenticate WHERE " + columnID + "=?;"); while (rs.next()) { - String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : ""; - int group = !salt.isEmpty() && !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; + int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; PlayerAuth pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) - .hash(rs.getString(columnPassword)) + .password(rs.getString(columnPassword), salt) .lastLogin(rs.getLong(columnLastLogin)) .ip(rs.getString(columnIp)) .locWorld(rs.getString(lastlocWorld)) @@ -1187,21 +868,9 @@ public class MySQL implements DataSource { .locY(rs.getDouble(lastlocY)) .locZ(rs.getDouble(lastlocZ)) .email(rs.getString(columnEmail)) - .salt(salt) .groupId(group) .build(); - if (Settings.getPasswordHash == HashAlgorithm.XENFORO) { - int id = rs.getInt(columnID); - pst.setInt(1, id); - ResultSet rs2 = pst.executeQuery(); - if (rs2.next()) { - Blob blob = rs2.getBlob("data"); - byte[] bytes = blob.getBytes(1, (int) blob.length()); - pAuth.setHash(new String(bytes)); - } - rs2.close(); - } auths.add(pAuth); } } catch (Exception ex) { diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java index 9c4301ae..2d04662b 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -2,7 +2,9 @@ package fr.xephi.authme.datasource; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; import java.sql.*; import java.util.ArrayList; @@ -90,6 +92,13 @@ public class SQLite implements DataSource { st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnPassword + " VARCHAR(255) NOT NULL;"); } rs.close(); + if (!columnSalt.isEmpty()) { + rs = con.getMetaData().getColumns(null, null, tableName, columnSalt); + if (!rs.next()) { + st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnSalt + " VARCHAR(255);"); + } + rs.close(); + } rs = con.getMetaData().getColumns(null, null, tableName, columnIp); if (!rs.next()) { st.executeUpdate("ALTER TABLE " + tableName + " ADD COLUMN " + columnIp + " VARCHAR(40) NOT NULL;"); @@ -174,15 +183,7 @@ public class SQLite implements DataSource { pst.setString(1, user); rs = pst.executeQuery(); if (rs.next()) { - if (rs.getString(columnIp).isEmpty()) { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "192.168.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - return new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } + return buildAuthFromResultSet(rs); } else { return null; } @@ -206,21 +207,29 @@ public class SQLite implements DataSource { public synchronized boolean saveAuth(PlayerAuth auth) { PreparedStatement pst = null; try { - if (columnSalt.isEmpty() && auth.getSalt().isEmpty()) { - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); + EncryptedPassword password = auth.getPassword(); + if (columnSalt.isEmpty()) { + if (!StringUtils.isEmpty(auth.getPassword().getSalt())) { + ConsoleLogger.showError("Warning! Detected hashed password with separate salt but the salt column " + + "is not set in the config!"); + } + pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + + "," + columnIp + "," + columnLastLogin + "," + columnRealName + ") VALUES (?,?,?,?,?);"); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, password.getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); pst.setString(5, auth.getRealName()); pst.executeUpdate(); } else { - pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName + ") VALUES (?,?,?,?,?,?);"); + pst = con.prepareStatement("INSERT INTO " + tableName + "(" + columnName + "," + columnPassword + "," + + columnIp + "," + columnLastLogin + "," + columnSalt + "," + columnRealName + + ") VALUES (?,?,?,?,?,?);"); pst.setString(1, auth.getNickname()); - pst.setString(2, auth.getHash()); + pst.setString(2, password.getHash()); pst.setString(3, auth.getIp()); pst.setLong(4, auth.getLastLogin()); - pst.setString(5, auth.getSalt()); + pst.setString(5, password.getSalt()); pst.setString(6, auth.getRealName()); pst.executeUpdate(); } @@ -244,9 +253,19 @@ public class SQLite implements DataSource { public synchronized boolean updatePassword(PlayerAuth auth) { PreparedStatement pst = null; try { - pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnPassword + "=? WHERE " + columnName + "=?;"); - pst.setString(1, auth.getHash()); - pst.setString(2, auth.getNickname()); + EncryptedPassword password = auth.getPassword(); + boolean useSalt = !columnSalt.isEmpty(); + String sql = "UPDATE " + tableName + " SET " + columnPassword + " = ?" + + (useSalt ? ", " + columnSalt + " = ?" : "") + + " WHERE " + columnName + " = ?"; + pst = con.prepareStatement(sql); + pst.setString(1, password.getHash()); + if (useSalt) { + pst.setString(2, password.getSalt()); + pst.setString(3, auth.getNickname()); + } else { + pst.setString(2, auth.getNickname()); + } pst.executeUpdate(); } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -398,6 +417,7 @@ public class SQLite implements DataSource { ResultSet rs = null; int countIp = 0; try { + // TODO ljacqu 20151230: Simply fetch COUNT(1) and return that pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnIp + "=?;"); pst.setString(1, ip); rs = pst.executeQuery(); @@ -438,33 +458,6 @@ public class SQLite implements DataSource { return true; } - /** - * Method updateSalt. - * - * @param auth PlayerAuth - * - * @return boolean * @see fr.xephi.authme.datasource.DataSource#updateSalt(PlayerAuth) - */ - @Override - public boolean updateSalt(PlayerAuth auth) { - if (columnSalt.isEmpty()) { - return false; - } - PreparedStatement pst = null; - try { - pst = con.prepareStatement("UPDATE " + tableName + " SET " + columnSalt + "=? WHERE " + columnName + "=?;"); - pst.setString(1, auth.getSalt()); - pst.setString(2, auth.getNickname()); - pst.executeUpdate(); - } catch (SQLException ex) { - ConsoleLogger.showError(ex.getMessage()); - return false; - } finally { - close(pst); - } - return true; - } - /** * Method close. * @@ -611,13 +604,6 @@ public class SQLite implements DataSource { } } - /** - * Method purgeBanned. - * - * @param banned List - * - * @see fr.xephi.authme.datasource.DataSource#purgeBanned(List) - */ @Override public void purgeBanned(List banned) { PreparedStatement pst = null; @@ -761,14 +747,6 @@ public class SQLite implements DataSource { return result; } - /** - * Method updateName. - * - * @param oldOne String - * @param newOne String - * - * @see fr.xephi.authme.datasource.DataSource#updateName(String, String) - */ @Override public void updateName(String oldOne, String newOne) { PreparedStatement pst = null; @@ -787,7 +765,7 @@ public class SQLite implements DataSource { /** * Method getAllAuths. * - * @return List * @see fr.xephi.authme.datasource.DataSource#getAllAuths() + * @return List */ @Override public List getAllAuths() { @@ -798,17 +776,8 @@ public class SQLite implements DataSource { pst = con.prepareStatement("SELECT * FROM " + tableName + ";"); rs = pst.executeQuery(); while (rs.next()) { - PlayerAuth pAuth; - if (rs.getString(columnIp).isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } - auths.add(pAuth); + PlayerAuth auth = buildAuthFromResultSet(rs); + auths.add(auth); } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -822,7 +791,7 @@ public class SQLite implements DataSource { /** * Method getLoggedPlayers. * - * @return List * @see fr.xephi.authme.datasource.DataSource#getLoggedPlayers() + * @return List */ @Override public List getLoggedPlayers() { @@ -833,17 +802,8 @@ public class SQLite implements DataSource { pst = con.prepareStatement("SELECT * FROM " + tableName + " WHERE " + columnLogged + "=1;"); rs = pst.executeQuery(); while (rs.next()) { - PlayerAuth pAuth; - if (rs.getString(columnIp).isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), "127.0.0.1", rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - if (!columnSalt.isEmpty()) { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnSalt), rs.getInt(columnGroup), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } else { - pAuth = new PlayerAuth(rs.getString(columnName), rs.getString(columnPassword), rs.getString(columnIp), rs.getLong(columnLastLogin), rs.getDouble(lastlocX), rs.getDouble(lastlocY), rs.getDouble(lastlocZ), rs.getString(lastlocWorld), rs.getString(columnEmail), rs.getString(columnRealName)); - } - } - auths.add(pAuth); + PlayerAuth auth = buildAuthFromResultSet(rs); + auths.add(auth); } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); @@ -853,4 +813,25 @@ public class SQLite implements DataSource { } return auths; } + + private PlayerAuth buildAuthFromResultSet(ResultSet row) throws SQLException { + String salt = !columnSalt.isEmpty() ? row.getString(columnSalt) : null; + + PlayerAuth.Builder authBuilder = PlayerAuth.builder() + .name(row.getString(columnName)) + .email(row.getString(columnEmail)) + .realName(row.getString(columnRealName)) + .password(row.getString(columnPassword), salt) + .lastLogin(row.getLong(columnLastLogin)) + .locX(row.getDouble(lastlocX)) + .locY(row.getDouble(lastlocY)) + .locZ(row.getDouble(lastlocZ)) + .locWorld(row.getString(lastlocWorld)); + + String ip = row.getString(columnIp); + if (!ip.isEmpty()) { + authBuilder.ip(ip); + } + return authBuilder.build(); + } } diff --git a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java index f20fd7fd..572d1c9a 100644 --- a/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java +++ b/src/main/java/fr/xephi/authme/events/PasswordEncryptionEvent.java @@ -5,75 +5,41 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; /** - *

    - * This event is called when we need to compare or get an hash password, for set - * a custom EncryptionMethod - *

    + * This event is called when we need to compare or hash password and allows + * third-party listeners to change the encryption method. This is typically + * done with the {@link fr.xephi.authme.security.HashAlgorithm#CUSTOM} setting. * * @author Xephi59 - * @version $Revision: 1.0 $ - * @see fr.xephi.authme.security.crypts.EncryptionMethod */ public class PasswordEncryptionEvent extends Event { private static final HandlerList handlers = new HandlerList(); - private EncryptionMethod method = null; - private String playerName = ""; + private EncryptionMethod method; + private String playerName; - /** - * Constructor for PasswordEncryptionEvent. - * - * @param method EncryptionMethod - * @param playerName String - */ public PasswordEncryptionEvent(EncryptionMethod method, String playerName) { super(false); this.method = method; this.playerName = playerName; } - /** - * Method getHandlerList. - * - * @return HandlerList - */ public static HandlerList getHandlerList() { return handlers; } - /** - * Method getHandlers. - * - * @return HandlerList - */ @Override public HandlerList getHandlers() { return handlers; } - /** - * Method getMethod. - * - * @return EncryptionMethod - */ public EncryptionMethod getMethod() { return method; } - /** - * Method setMethod. - * - * @param method EncryptionMethod - */ public void setMethod(EncryptionMethod method) { this.method = method; } - /** - * Method getPlayerName. - * - * @return String - */ public String getPlayerName() { return playerName; } diff --git a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java index f5d3e0ae..731de626 100644 --- a/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java +++ b/src/main/java/fr/xephi/authme/hooks/BungeeCordMessage.java @@ -6,6 +6,8 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.security.crypts.EncryptedPassword; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; @@ -24,15 +26,6 @@ public class BungeeCordMessage implements PluginMessageListener { this.plugin = plugin; } - /** - * Method onPluginMessageReceived. - * - * @param channel String - * @param player Player - * @param message byte[] - * - * @see org.bukkit.plugin.messaging.PluginMessageListener#onPluginMessageReceived(String, Player, byte[]) - */ @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { if (!channel.equals("BungeeCord")) { @@ -50,21 +43,22 @@ public class BungeeCordMessage implements PluginMessageListener { final String[] args = str.split(";"); final String act = args[0]; final String name = args[1]; + final DataSource dataSource = plugin.getDataSource(); plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - PlayerAuth auth = plugin.database.getAuth(name); + PlayerAuth auth = dataSource.getAuth(name); if (auth == null) { return; } if ("login".equals(act)) { PlayerCache.getInstance().updatePlayer(auth); - plugin.database.setLogged(name); + dataSource.setLogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged in from one of your server!"); } else if ("logout".equals(act)) { PlayerCache.getInstance().removePlayer(name); - plugin.database.setUnlogged(name); + dataSource.setUnlogged(name); ConsoleLogger.info("Player " + auth.getNickname() + " has logged out from one of your server!"); } else if ("register".equals(act)) { @@ -72,11 +66,10 @@ public class BungeeCordMessage implements PluginMessageListener { + " has registered out from one of your server!"); } else if ("changepassword".equals(act)) { final String password = args[2]; - auth.setHash(password); - if (args.length == 4) - auth.setSalt(args[3]); + final String salt = args.length >= 4 ? args[3] : null; + auth.setPassword(new EncryptedPassword(password, salt)); PlayerCache.getInstance().updatePlayer(auth); - plugin.database.updatePassword(auth); + dataSource.updatePassword(auth); } } diff --git a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java index 701308ee..42a11b3f 100644 --- a/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java +++ b/src/main/java/fr/xephi/authme/listener/AuthMePlayerListener.java @@ -89,7 +89,7 @@ public class AuthMePlayerListener implements Listener { plugin.getServer().getScheduler().runTaskAsynchronously(plugin, new Runnable() { @Override public void run() { - if (plugin.database.isAuthAvailable(player.getName().toLowerCase())) { + if (plugin.getDataSource().isAuthAvailable(player.getName().toLowerCase())) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { @@ -221,8 +221,9 @@ public class AuthMePlayerListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onPreLogin(AsyncPlayerPreLoginEvent event) { - PlayerAuth auth = plugin.database.getAuth(event.getName()); - if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && !auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) { + PlayerAuth auth = plugin.getDataSource().getAuth(event.getName()); + if (auth != null && auth.getRealName() != null && !auth.getRealName().isEmpty() && + !auth.getRealName().equals("Player") && !auth.getRealName().equals(event.getName())) { event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER); event.setKickMessage("You should join using username: " + ChatColor.AQUA + auth.getRealName() + ChatColor.RESET + "\nnot: " + ChatColor.RED + event.getName()); // TODO: write a better message @@ -231,7 +232,7 @@ public class AuthMePlayerListener implements Listener { if (auth != null && auth.getRealName().equals("Player")) { auth.setRealName(event.getName()); - plugin.database.saveAuth(auth); + plugin.getDataSource().saveAuth(auth); } if (auth == null && Settings.enableProtection) { @@ -302,7 +303,7 @@ public class AuthMePlayerListener implements Listener { } final String name = player.getName().toLowerCase(); - boolean isAuthAvailable = plugin.database.isAuthAvailable(name); + boolean isAuthAvailable = plugin.getDataSource().isAuthAvailable(name); if (Settings.isKickNonRegisteredEnabled && !isAuthAvailable) { if (Settings.antiBotInAction) { @@ -475,9 +476,13 @@ public class AuthMePlayerListener implements Listener { Player player = event.getPlayer(); String name = player.getName().toLowerCase(); Location spawn = plugin.getSpawnLocation(player); - if (Settings.isSaveQuitLocationEnabled && plugin.database.isAuthAvailable(name)) { - PlayerAuth auth = new PlayerAuth(name, spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getWorld().getName(), player.getName()); - plugin.database.updateQuitLoc(auth); + if (Settings.isSaveQuitLocationEnabled && plugin.getDataSource().isAuthAvailable(name)) { + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(player.getName()) + .location(spawn) + .build(); + plugin.getDataSource().updateQuitLoc(auth); } if (spawn != null && spawn.getWorld() != null) { event.setRespawnLocation(spawn); diff --git a/src/main/java/fr/xephi/authme/process/Management.java b/src/main/java/fr/xephi/authme/process/Management.java index 58ce248f..6d262308 100644 --- a/src/main/java/fr/xephi/authme/process/Management.java +++ b/src/main/java/fr/xephi/authme/process/Management.java @@ -33,7 +33,7 @@ public class Management { @Override public void run() { - new AsynchronousLogin(player, password, forceLogin, plugin, plugin.database).process(); + new AsynchronousLogin(player, password, forceLogin, plugin, plugin.getDataSource()).process(); } }); } @@ -43,7 +43,7 @@ public class Management { @Override public void run() { - new AsynchronousLogout(player, plugin, plugin.database).process(); + new AsynchronousLogout(player, plugin, plugin.getDataSource()).process(); } }); } @@ -53,7 +53,7 @@ public class Management { @Override public void run() { - new AsyncRegister(player, password, email, plugin, plugin.database).process(); + new AsyncRegister(player, password, email, plugin, plugin.getDataSource()).process(); } }); } @@ -73,7 +73,7 @@ public class Management { @Override public void run() { - new AsynchronousJoin(player, plugin, plugin.database).process(); + new AsynchronousJoin(player, plugin, plugin.getDataSource()).process(); } }); @@ -84,7 +84,7 @@ public class Management { @Override public void run() { - new AsynchronousQuit(player, plugin, plugin.database, isKick).process(); + new AsynchronousQuit(player, plugin, plugin.getDataSource(), isKick).process(); } }); diff --git a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java index 214b8215..7208f60a 100644 --- a/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java +++ b/src/main/java/fr/xephi/authme/process/email/AsyncChangeEmail.java @@ -40,7 +40,7 @@ public class AsyncChangeEmail { if (Settings.getmaxRegPerEmail > 0) { if (!plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) - && plugin.database.getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { + && plugin.getDataSource().getAllAuthsByEmail(newEmail).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } @@ -68,7 +68,7 @@ public class AsyncChangeEmail { } String old = auth.getEmail(); auth.setEmail(newEmail); - if (!plugin.database.updateEmail(auth)) { + if (!plugin.getDataSource().updateEmail(auth)) { m.send(player, MessageKey.ERROR); auth.setEmail(old); return; @@ -81,7 +81,7 @@ public class AsyncChangeEmail { } m.send(player, MessageKey.EMAIL_CHANGED_SUCCESS); } else { - if (plugin.database.isAuthAvailable(playerName)) { + if (plugin.getDataSource().isAuthAvailable(playerName)) { m.send(player, MessageKey.LOGIN_MESSAGE); } else { if (Settings.emailRegistration) { diff --git a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java index fe47be51..8297224b 100644 --- a/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java +++ b/src/main/java/fr/xephi/authme/process/login/AsynchronousLogin.java @@ -8,7 +8,6 @@ import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.AuthMeAsyncPreLoginEvent; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.security.RandomString; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; @@ -26,12 +25,11 @@ import java.util.List; */ public class AsynchronousLogin { - private static final RandomString rdm = new RandomString(Settings.captchaLength); - protected final Player player; - protected final String name; - protected final String realName; - protected final String password; - protected final boolean forceLogin; + private final Player player; + private final String name; + private final String realName; + private final String password; + private final boolean forceLogin; private final AuthMe plugin; private final DataSource database; private final Messages m; @@ -70,7 +68,7 @@ public class AsynchronousLogin { plugin.captcha.putIfAbsent(name, i); } if (plugin.captcha.containsKey(name) && plugin.captcha.get(name) > Settings.maxLoginTry) { - plugin.cap.putIfAbsent(name, rdm.nextString()); + plugin.cap.putIfAbsent(name, RandomString.generate(Settings.captchaLength)); m.send(player, MessageKey.USAGE_CAPTCHA, plugin.cap.get(name)); return true; } @@ -120,8 +118,7 @@ public class AsynchronousLogin { return null; } - if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) - { + if (Settings.preventOtherCase && !player.getName().equals(pAuth.getRealName())) { // TODO: Add a message like : MessageKey.INVALID_NAME_CASE m.send(player, MessageKey.USERNAME_ALREADY_ONLINE_ERROR); return null; @@ -138,19 +135,19 @@ public class AsynchronousLogin { if (pAuth == null || needsCaptcha()) return; - String hash = pAuth.getHash(); String email = pAuth.getEmail(); - boolean passwordVerified = true; - if (!forceLogin) - try { - passwordVerified = PasswordSecurity.comparePasswordWithHash(password, hash, realName); - } catch (Exception ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); - return; - } + boolean passwordVerified = forceLogin || plugin.getPasswordSecurity() + .comparePassword(password, pAuth.getPassword(), realName); + if (passwordVerified && player.isOnline()) { - PlayerAuth auth = new PlayerAuth(name, hash, getIP(), new Date().getTime(), email, realName); + PlayerAuth auth = PlayerAuth.builder() + .name(name) + .realName(realName) + .ip(getIP()) + .lastLogin(new Date().getTime()) + .email(email) + .password(pAuth.getPassword()) + .build(); database.updateSession(auth); if (Settings.useCaptcha) { diff --git a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java index fb102ec9..3f2cbd0e 100644 --- a/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java +++ b/src/main/java/fr/xephi/authme/process/register/AsyncRegister.java @@ -8,7 +8,7 @@ import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PlayerPermission; -import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; @@ -44,7 +44,10 @@ public class AsyncRegister { } else if (!Settings.isRegistrationEnabled) { m.send(player, MessageKey.REGISTRATION_DISABLED); return false; - } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + } else if (passLow.contains("delete") || passLow.contains("where") || passLow.contains("insert") + || passLow.contains("modify") || passLow.contains("from") || passLow.contains("select") + || passLow.contains(";") || passLow.contains("null") || !passLow.matches(Settings.getPassRegex)) { + // TODO #308: Remove check for SQL keywords m.send(player, MessageKey.PASSWORD_MATCH_ERROR); return false; } else if (passLow.equalsIgnoreCase(player.getName())) { @@ -87,26 +90,21 @@ public class AsyncRegister { } } - private void emailRegister() throws Exception { + private void emailRegister() { if (Settings.getmaxRegPerEmail > 0 && !plugin.getPermissionsManager().hasPermission(player, PlayerPermission.ALLOW_MULTIPLE_ACCOUNTS) && database.getAllAuthsByEmail(email).size() >= Settings.getmaxRegPerEmail) { m.send(player, MessageKey.MAX_REGISTER_EXCEEDED); return; } - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) + .location(player.getLocation()) .email(email) - .salt(salt != null ? salt : "") .build(); if (!database.saveAuth(auth)) { @@ -122,18 +120,13 @@ public class AsyncRegister { } private void passwordRegister() throws Exception { - final String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, password, name); - final String salt = PasswordSecurity.userSalt.get(name); + final EncryptedPassword encryptedPassword = plugin.getPasswordSecurity().computeHash(password, name); PlayerAuth auth = PlayerAuth.builder() .name(name) .realName(player.getName()) - .hash(hashNew) + .password(encryptedPassword) .ip(ip) - .locWorld(player.getLocation().getWorld().getName()) - .locX(player.getLocation().getX()) - .locY(player.getLocation().getY()) - .locZ(player.getLocation().getZ()) - .salt(salt != null ? salt : "") + .location(player.getLocation()) .build(); if (!database.saveAuth(auth)) { diff --git a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java index 53c54b3e..58c17694 100644 --- a/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java +++ b/src/main/java/fr/xephi/authme/process/unregister/AsynchronousUnregister.java @@ -2,11 +2,11 @@ package fr.xephi.authme.process.unregister; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; +import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.settings.Settings; @@ -20,16 +20,12 @@ import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitTask; -import java.security.NoSuchAlgorithmException; - -/** - */ public class AsynchronousUnregister { - protected final Player player; - protected final String name; - protected final String password; - protected final boolean force; + private final Player player; + private final String name; + private final String password; + private final boolean force; private final AuthMe plugin; private final Messages m; private final JsonCache playerCache; @@ -52,65 +48,59 @@ public class AsynchronousUnregister { this.playerCache = new JsonCache(); } - /** - * Method getIp. - * - * @return String - */ protected String getIp() { return plugin.getIP(player); } public void process() { - try { - if (force || PasswordSecurity.comparePasswordWithHash(password, PlayerCache.getInstance().getAuth(name).getHash(), player.getName())) { - if (!plugin.database.removeAuth(name)) { - m.send(player, MessageKey.ERROR); - return; + PlayerAuth cachedAuth = PlayerCache.getInstance().getAuth(name); + if (force || plugin.getPasswordSecurity().comparePassword( + password, cachedAuth.getPassword(), player.getName())) { + if (!plugin.getDataSource().removeAuth(name)) { + m.send(player, MessageKey.ERROR); + return; + } + int timeOut = Settings.getRegistrationTimeout * 20; + if (Settings.isForcedRegistrationEnabled) { + Utils.teleportToSpawn(player); + player.saveData(); + PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); + if (!Settings.getRegisteredGroup.isEmpty()) { + Utils.setGroup(player, GroupType.UNREGISTERED); } - int timeOut = Settings.getRegistrationTimeout * 20; - if (Settings.isForcedRegistrationEnabled) { - Utils.teleportToSpawn(player); - player.saveData(); - PlayerCache.getInstance().removePlayer(player.getName().toLowerCase()); - if (!Settings.getRegisteredGroup.isEmpty()) { - Utils.setGroup(player, GroupType.UNREGISTERED); - } - LimboCache.getInstance().addLimboPlayer(player); - LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); - int interval = Settings.getWarnMessageInterval; - BukkitScheduler scheduler = plugin.getServer().getScheduler(); - if (timeOut != 0) { - BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, - new TimeoutTask(plugin, name, player), timeOut); - limboPlayer.setTimeoutTaskId(id); - } - limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, - new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); - m.send(player, MessageKey.UNREGISTERED_SUCCESS); - ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - return; - } - if (!Settings.unRegisteredGroup.isEmpty()) { - Utils.setGroup(player, Utils.GroupType.UNREGISTERED); - } - PlayerCache.getInstance().removePlayer(name); - // check if Player cache File Exist and delete it, preventing - // duplication of items - if (playerCache.doesCacheExist(player)) { - playerCache.removeCache(player); - } - // Apply blind effect - if (Settings.applyBlindEffect) { - player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + LimboCache.getInstance().addLimboPlayer(player); + LimboPlayer limboPlayer = LimboCache.getInstance().getLimboPlayer(name); + int interval = Settings.getWarnMessageInterval; + BukkitScheduler scheduler = plugin.getServer().getScheduler(); + if (timeOut != 0) { + BukkitTask id = scheduler.runTaskLaterAsynchronously(plugin, + new TimeoutTask(plugin, name, player), timeOut); + limboPlayer.setTimeoutTaskId(id); } + limboPlayer.setMessageTaskId(scheduler.runTaskAsynchronously(plugin, + new MessageTask(plugin, name, m.retrieve(MessageKey.REGISTER_MESSAGE), interval))); m.send(player, MessageKey.UNREGISTERED_SUCCESS); ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); - Utils.teleportToSpawn(player); - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + return; } - } catch (NoSuchAlgorithmException ignored) { + if (!Settings.unRegisteredGroup.isEmpty()) { + Utils.setGroup(player, Utils.GroupType.UNREGISTERED); + } + PlayerCache.getInstance().removePlayer(name); + // check if Player cache File Exist and delete it, preventing + // duplication of items + if (playerCache.doesCacheExist(player)) { + playerCache.removeCache(player); + } + // Apply blind effect + if (Settings.applyBlindEffect) { + player.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, timeOut, 2)); + } + m.send(player, MessageKey.UNREGISTERED_SUCCESS); + ConsoleLogger.info(player.getDisplayName() + " unregistered himself"); + Utils.teleportToSpawn(player); + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 315fa9fd..24a702dd 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -3,40 +3,42 @@ package fr.xephi.authme.security; import fr.xephi.authme.security.crypts.EncryptionMethod; /** + * The list of hash algorithms supported by AuthMe. The implementing class must define a public + * constructor which takes either no arguments, or a DataSource object (when the salt is stored + * separately, writes to the database are necessary). */ public enum HashAlgorithm { - MD5(fr.xephi.authme.security.crypts.MD5.class), - SHA1(fr.xephi.authme.security.crypts.SHA1.class), - SHA256(fr.xephi.authme.security.crypts.SHA256.class), - WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), - XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), - MD5VB(fr.xephi.authme.security.crypts.MD5VB.class), - PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), - @Deprecated - PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), - MYBB(fr.xephi.authme.security.crypts.MYBB.class), - IPB3(fr.xephi.authme.security.crypts.IPB3.class), - PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), - SMF(fr.xephi.authme.security.crypts.SMF.class), - XENFORO(fr.xephi.authme.security.crypts.XF.class), - SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), - JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), BCRYPT(fr.xephi.authme.security.crypts.BCRYPT.class), - WBB3(fr.xephi.authme.security.crypts.WBB3.class), - WBB4(fr.xephi.authme.security.crypts.WBB4.class), - SHA512(fr.xephi.authme.security.crypts.SHA512.class), + BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), DOUBLEMD5(fr.xephi.authme.security.crypts.DOUBLEMD5.class), + IPB3(fr.xephi.authme.security.crypts.IPB3.class), + JOOMLA(fr.xephi.authme.security.crypts.JOOMLA.class), + MD5(fr.xephi.authme.security.crypts.MD5.class), + MD5VB(fr.xephi.authme.security.crypts.MD5VB.class), + MYBB(fr.xephi.authme.security.crypts.MYBB.class), PBKDF2(fr.xephi.authme.security.crypts.CryptPBKDF2.class), PBKDF2DJANGO(fr.xephi.authme.security.crypts.CryptPBKDF2Django.class), - WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), + PHPBB(fr.xephi.authme.security.crypts.PHPBB.class), + PHPFUSION(fr.xephi.authme.security.crypts.PHPFUSION.class), + @Deprecated + PLAINTEXT(fr.xephi.authme.security.crypts.PLAINTEXT.class), ROYALAUTH(fr.xephi.authme.security.crypts.ROYALAUTH.class), - CRAZYCRYPT1(fr.xephi.authme.security.crypts.CRAZYCRYPT1.class), - BCRYPT2Y(fr.xephi.authme.security.crypts.BCRYPT2Y.class), + SALTED2MD5(fr.xephi.authme.security.crypts.SALTED2MD5.class), SALTEDSHA512(fr.xephi.authme.security.crypts.SALTEDSHA512.class), + SHA1(fr.xephi.authme.security.crypts.SHA1.class), + SHA256(fr.xephi.authme.security.crypts.SHA256.class), + SHA512(fr.xephi.authme.security.crypts.SHA512.class), + SMF(fr.xephi.authme.security.crypts.SMF.class), + WBB3(fr.xephi.authme.security.crypts.WBB3.class), + WBB4(fr.xephi.authme.security.crypts.WBB4.class), + WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), + WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), + XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), CUSTOM(null); - final Class clazz; + private final Class clazz; /** * Constructor for HashAlgorithm. diff --git a/src/main/java/fr/xephi/authme/security/HashUtils.java b/src/main/java/fr/xephi/authme/security/HashUtils.java new file mode 100644 index 00000000..c4fb4edf --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/HashUtils.java @@ -0,0 +1,85 @@ +package fr.xephi.authme.security; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * Hashing utilities (interface for common hashing algorithms). + */ +public final class HashUtils { + + private HashUtils() { + } + + /** + * Generate the SHA-1 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-1 digest + */ + public static String sha1(String message) { + return hash(message, MessageDigestAlgorithm.SHA1); + } + + /** + * Generate the SHA-256 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-256 digest + */ + public static String sha256(String message) { + return hash(message, MessageDigestAlgorithm.SHA256); + } + + /** + * Generate the SHA-512 digest of the given message. + * + * @param message The message to hash + * @return The resulting SHA-512 digest + */ + public static String sha512(String message) { + return hash(message, MessageDigestAlgorithm.SHA512); + } + + /** + * Generate the MD5 digest of the given message. + * + * @param message The message to hash + * @return The resulting MD5 digest + */ + public static String md5(String message) { + return hash(message, MessageDigestAlgorithm.MD5); + } + + /** + * Return a {@link MessageDigest} instance for the given algorithm. + * + * @param algorithm The desired algorithm + * @return MessageDigest instance for the given algorithm + */ + public static MessageDigest getDigest(MessageDigestAlgorithm algorithm) { + try { + return MessageDigest.getInstance(algorithm.getKey()); + } catch (NoSuchAlgorithmException e) { + throw new UnsupportedOperationException("Your system seems not to support the hash algorithm '" + + algorithm.getKey() + "'"); + } + } + + /** + * Hash the message with the given algorithm and return the hash in its hexadecimal notation. + * + * @param message The message to hash + * @param algorithm The algorithm to hash the message with + * @return The digest in its hexadecimal representation + */ + private static String hash(String message, MessageDigestAlgorithm algorithm) { + MessageDigest md = getDigest(algorithm); + md.reset(); + md.update(message.getBytes()); + byte[] digest = md.digest(); + return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + } + +} diff --git a/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java new file mode 100644 index 00000000..51ff2a5d --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/MessageDigestAlgorithm.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.security; + +import java.security.MessageDigest; + +/** + * The Java-supported names to get a {@link MessageDigest} instance with. + * + * @see + * Crypto Spec Appendix A: Standard Names + */ +public enum MessageDigestAlgorithm { + + MD5("MD5"), + + SHA1("SHA-1"), + + SHA256("SHA-256"), + + SHA512("SHA-512"); + + private final String key; + + MessageDigestAlgorithm(String key) { + this.key = key; + } + + public String getKey() { + return key; + } +} diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 1ffa423d..ba1d2e02 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,182 +1,126 @@ package fr.xephi.authme.security; -import fr.xephi.authme.AuthMe; import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.events.PasswordEncryptionEvent; -import fr.xephi.authme.security.crypts.BCRYPT; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; -import fr.xephi.authme.settings.Settings; -import org.bukkit.Bukkit; - -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.HashMap; +import org.bukkit.plugin.PluginManager; /** + * Manager class for password-related operations. */ public class PasswordSecurity { - public static final HashMap userSalt = new HashMap<>(); - private static final SecureRandom rnd = new SecureRandom(); + private final DataSource dataSource; + private final HashAlgorithm algorithm; + private final PluginManager pluginManager; + private final boolean supportOldAlgorithm; - public static String createSalt(int length) - throws NoSuchAlgorithmException { - byte[] msg = new byte[40]; - rnd.nextBytes(msg); - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - byte[] digest = sha1.digest(msg); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)).substring(0, length); + public PasswordSecurity(DataSource dataSource, HashAlgorithm algorithm, + PluginManager pluginManager, boolean supportOldAlgorithm) { + this.dataSource = dataSource; + this.algorithm = algorithm; + this.pluginManager = pluginManager; + this.supportOldAlgorithm = supportOldAlgorithm; } - public static String getHash(HashAlgorithm alg, String password, - String playerName) throws NoSuchAlgorithmException { - EncryptionMethod method; - try { - if (alg != HashAlgorithm.CUSTOM) - method = alg.getClazz().newInstance(); - else method = null; - } catch (InstantiationException | IllegalAccessException e) { - throw new NoSuchAlgorithmException("Problem with hash algorithm '" + alg + "'", e); - } - String salt = ""; - switch (alg) { - case SHA256: - salt = createSalt(16); - break; - case MD5VB: - salt = createSalt(16); - break; - case XAUTH: - salt = createSalt(12); - break; - case MYBB: - salt = createSalt(8); - userSalt.put(playerName, salt); - break; - case IPB3: - salt = createSalt(5); - userSalt.put(playerName, salt); - break; - case PHPFUSION: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SALTED2MD5: - salt = createSalt(Settings.saltLength); - userSalt.put(playerName, salt); - break; - case JOOMLA: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case BCRYPT: - salt = BCRYPT.gensalt(Settings.bCryptLog2Rounds); - userSalt.put(playerName, salt); - break; - case WBB3: - salt = createSalt(40); - userSalt.put(playerName, salt); - break; - case WBB4: - salt = BCRYPT.gensalt(8); - userSalt.put(playerName, salt); - break; - case PBKDF2DJANGO: - case PBKDF2: - salt = createSalt(12); - userSalt.put(playerName, salt); - break; - case SMF: - return method.computeHash(password, null, playerName); - case PHPBB: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case BCRYPT2Y: - salt = createSalt(16); - userSalt.put(playerName, salt); - break; - case SALTEDSHA512: - salt = createSalt(32); - userSalt.put(playerName, salt); - break; - case MD5: - case SHA1: - case WHIRLPOOL: - case PLAINTEXT: - case XENFORO: - case SHA512: - case ROYALAUTH: - case CRAZYCRYPT1: - case DOUBLEMD5: - case WORDPRESS: - case CUSTOM: - break; - default: - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - } - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - method = event.getMethod(); - if (method == null) - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - return method.computeHash(password, salt, playerName); + public EncryptedPassword computeHash(String password, String playerName) { + return computeHash(algorithm, password, playerName); } - public static boolean comparePasswordWithHash(String password, String hash, - String playerName) throws NoSuchAlgorithmException { - HashAlgorithm algorithm = Settings.getPasswordHash; - EncryptionMethod method; - try { - if (algorithm != HashAlgorithm.CUSTOM) { - method = algorithm.getClazz().newInstance(); - } else { - method = null; - } + public EncryptedPassword computeHash(HashAlgorithm algorithm, String password, String playerName) { + EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); + return method.computeHash(password, playerName); + } - PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); - Bukkit.getPluginManager().callEvent(event); - method = event.getMethod(); - - if (method == null) - throw new NoSuchAlgorithmException("Unknown hash algorithm"); - - if (method.comparePassword(hash, password, playerName)) - return true; - - if (Settings.supportOldPassword) { - if (compareWithAllEncryptionMethod(password, hash, playerName)) - return true; - } - } catch (InstantiationException | IllegalAccessException e) { - throw new NoSuchAlgorithmException("Problem with this hash algorithm"); + public boolean comparePassword(String password, String playerName) { + // TODO ljacqu 20151230: Defining a dataSource.getPassword() method would be more efficient + PlayerAuth auth = dataSource.getAuth(playerName); + if (auth != null) { + return comparePassword(password, auth.getPassword(), playerName); } return false; } - private static boolean compareWithAllEncryptionMethod(String password, - String hash, String playerName) { - for (HashAlgorithm algo : HashAlgorithm.values()) { - if (algo != HashAlgorithm.CUSTOM) { - try { - EncryptionMethod method = algo.getClazz().newInstance(); - if (method.comparePassword(hash, password, playerName)) { - PlayerAuth nAuth = AuthMe.getInstance().database.getAuth(playerName); - if (nAuth != null) { - nAuth.setHash(getHash(Settings.getPasswordHash, password, playerName)); - nAuth.setSalt(userSalt.containsKey(playerName) ? userSalt.get(playerName) : ""); - AuthMe.getInstance().database.updatePassword(nAuth); - AuthMe.getInstance().database.updateSalt(nAuth); - } - return true; - } - } catch (Exception ignored) { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + EncryptionMethod method = initializeEncryptionMethod(algorithm, playerName); + // User is not in data source, so the result will invariably be wrong because an encryption + // method with hasSeparateSalt() == true NEEDS the salt to evaluate the password + String salt = encryptedPassword.getSalt(); + if (method.hasSeparateSalt() && salt == null) { + return false; + } + + return method.comparePassword(password, encryptedPassword, playerName) + || supportOldAlgorithm && compareWithAllEncryptionMethods(password, encryptedPassword, playerName); + } + + /** + * Compare the given hash with all available encryption methods to support + * the migration to a new encryption method. Upon a successful match, the password + * will be hashed with the new encryption method and persisted. + * + * @param password The clear-text password to check + * @param encryptedPassword The encrypted password to test the clear-text password against + * @param playerName The name of the player + * @return True if the + */ + private boolean compareWithAllEncryptionMethods(String password, EncryptedPassword encryptedPassword, + String playerName) { + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + if (method != null && method.comparePassword(password, encryptedPassword, playerName)) { + hashPasswordForNewAlgorithm(password, playerName); + return true; } } } return false; } + + /** + * Get the encryption method from the given {@link HashAlgorithm} value and emit a + * {@link PasswordEncryptionEvent}. The encryption method from the event is then returned, + * which may have been changed by an external listener. + * + * @param algorithm The algorithm to retrieve the encryption method for + * @param playerName The name of the player a password will be hashed for + * @return The encryption method + */ + private EncryptionMethod initializeEncryptionMethod(HashAlgorithm algorithm, String playerName) { + EncryptionMethod method = initializeEncryptionMethodWithoutEvent(algorithm); + PasswordEncryptionEvent event = new PasswordEncryptionEvent(method, playerName); + pluginManager.callEvent(event); + return event.getMethod(); + } + + /** + * Initialize the encryption method corresponding to the given hash algorithm. + * + * @param algorithm The algorithm to retrieve the encryption method for + * @return The associated encryption method + */ + private static EncryptionMethod initializeEncryptionMethodWithoutEvent(HashAlgorithm algorithm) { + try { + return HashAlgorithm.CUSTOM.equals(algorithm) + ? null + : algorithm.getClazz().newInstance(); + } catch (InstantiationException | IllegalAccessException 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) { + PlayerAuth auth = dataSource.getAuth(playerName); + if (auth != null) { + EncryptedPassword encryptedPassword = initializeEncryptionMethod(algorithm, playerName) + .computeHash(password, playerName); + auth.setPassword(encryptedPassword); + dataSource.updatePassword(auth); + } + } + } diff --git a/src/main/java/fr/xephi/authme/security/RandomString.java b/src/main/java/fr/xephi/authme/security/RandomString.java index 8b1b8d63..40274305 100644 --- a/src/main/java/fr/xephi/authme/security/RandomString.java +++ b/src/main/java/fr/xephi/authme/security/RandomString.java @@ -1,13 +1,16 @@ package fr.xephi.authme.security; import java.security.SecureRandom; -import java.util.Calendar; import java.util.Random; -public class RandomString { +/** + * Utility for generating random strings. + */ +public final class RandomString { private static final char[] chars = new char[36]; private static final Random RANDOM = new SecureRandom(); + private static final int HEX_MAX_INDEX = 16; static { for (int idx = 0; idx < 10; ++idx) { @@ -18,30 +21,37 @@ public class RandomString { } } - private final Random random = new Random(); - - private final char[] buf; - - public RandomString(int length) { - if (length < 1) - throw new IllegalArgumentException("length < 1: " + length); - buf = new char[length]; - random.setSeed(Calendar.getInstance().getTimeInMillis()); - } - - public String nextString() { - for (int idx = 0; idx < buf.length; ++idx) - buf[idx] = chars[random.nextInt(chars.length)]; - return new String(buf); + private RandomString() { } + /** + * Generate a string of the given length consisting of random characters within the range [0-9a-z]. + * + * @param length The length of the random string to generate + * @return The random string + */ public static String generate(int length) { + return generate(length, chars.length); + } + + /** + * Generate a random hexadecimal string of the given length. In other words, the generated string + * contains characters only within the range [0-9a-f]. + * + * @param length The length of the random string to generate + * @return The random hexadecimal string + */ + public static String generateHex(int length) { + return generate(length, HEX_MAX_INDEX); + } + + private static String generate(int length, int maxIndex) { if (length < 0) { throw new IllegalArgumentException("Length must be positive but was " + length); } StringBuilder sb = new StringBuilder(length); for (int i = 0; i < length; ++i) { - sb.append(chars[RANDOM.nextInt(chars.length)]); + sb.append(chars[RANDOM.nextInt(maxIndex)]); } return sb.toString(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java index c94644e9..6b660d7b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -13,8 +13,13 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.HasSalt; +import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.settings.Settings; + import java.io.UnsupportedEncodingException; -import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; /** @@ -59,11 +64,13 @@ import java.security.SecureRandom; * @author Damien Miller * @version 0.2 */ +@Recommendation(Usage.RECOMMENDED) // provided the salt length is >= 8 +@HasSalt(value = SaltType.TEXT) // length depends on Settings.bCryptLog2Rounds public class BCRYPT implements EncryptionMethod { // BCrypt parameters private static final int GENSALT_DEFAULT_LOG2_ROUNDS = 10; - private static final int BCRYPT_SALT_LEN = 16; + protected static final int BCRYPT_SALT_LEN = 16; // Blowfish parameters private static final int BLOWFISH_NUM_ROUNDS = 16; @@ -508,14 +515,28 @@ public class BCRYPT implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return hashpw(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return checkpw(password, hash); + public EncryptedPassword computeHash(String password, String name) { + String salt = generateSalt(); + return new EncryptedPassword(hashpw(password, salt), null); + } + + @Override + public boolean comparePassword(String password, EncryptedPassword hash, String name) { + return checkpw(password, hash.getHash()); + } + + @Override + public String generateSalt() { + return BCRYPT.gensalt(Settings.bCryptLog2Rounds); + } + + @Override + public boolean hasSeparateSalt() { + return false; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java index d90af6e4..57f3894f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT2Y.java @@ -1,26 +1,34 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class BCRYPT2Y implements EncryptionMethod { +@Recommendation(Usage.RECOMMENDED) +public class BCRYPT2Y extends HexSaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - if (salt.length() == 22) + public String computeHash(String password, String salt, String name) { + if (salt.length() == 22) { salt = "$2y$10$" + salt; - return (BCRYPT.hashpw(password, salt)); + } + return BCRYPT.hashpw(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String ok = hash.substring(0, 29); - if (ok.length() != 29) + public boolean comparePassword(String password, EncryptedPassword encrypted, String unusedName) { + String hash = encrypted.getHash(); + if (hash.length() != 60) { return false; - return hash.equals(computeHash(password, ok, playerName)); + } + // The salt is the first 29 characters of the hash + + String salt = hash.substring(0, 29); + return hash.equals(computeHash(password, salt, null)); + } + + @Override + public int getSaltLength() { + return 22; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java index 336d8a4a..73fae5db 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1.java @@ -1,18 +1,24 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.MessageDigestAlgorithm; +import fr.xephi.authme.security.crypts.description.Usage; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.SaltType; +import fr.xephi.authme.security.crypts.description.HasSalt; + import java.nio.charset.Charset; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -/** - */ -public class CRAZYCRYPT1 implements EncryptionMethod { +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.USERNAME) +public class CRAZYCRYPT1 extends UsernameSaltMethod { - private static final char[] CRYPTCHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - protected final Charset charset = Charset.forName("UTF-8"); + private static final char[] CRYPTCHARS = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + private final Charset charset = Charset.forName("UTF-8"); - - public static String byteArrayToHexString(final byte... args) { + private static String byteArrayToHexString(final byte... args) { final char[] chars = new char[args.length * 2]; for (int i = 0; i < args.length; i++) { chars[i * 2] = CRYPTCHARS[(args[i] >> 4) & 0xF]; @@ -22,21 +28,11 @@ public class CRAZYCRYPT1 implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public EncryptedPassword computeHash(String password, String name) { final String text = "ÜÄaeut//&/=I " + password + "7421€547" + name + "__+IÄIH§%NK " + password; - try { - final MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(text.getBytes(charset), 0, text.length()); - return byteArrayToHexString(md.digest()); - } catch (final NoSuchAlgorithmException e) { - return null; - } + final MessageDigest md = HashUtils.getDigest(MessageDigestAlgorithm.SHA512); + md.update(text.getBytes(charset), 0, text.length()); + return new EncryptedPassword(byteArrayToHexString(md.digest())); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, null, playerName)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java index 728023be..a3f2ea39 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2.java @@ -1,18 +1,17 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; -import java.security.NoSuchAlgorithmException; import java.util.Arrays; -/** - */ -public class CryptPBKDF2 implements EncryptionMethod { +@Recommendation(Usage.DOES_NOT_WORK) +public class CryptPBKDF2 extends HexSaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String result = "pbkdf2_sha256$10000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000); PBKDF2Engine engine = new PBKDF2Engine(params); @@ -21,9 +20,8 @@ public class CryptPBKDF2 implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String[] line = hash.split("\\$"); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String[] line = encryptedPassword.getHash().split("\\$"); String salt = line[2]; String derivedKey = line[3]; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 10000, derivedKey.getBytes()); @@ -31,4 +29,9 @@ public class CryptPBKDF2 implements EncryptionMethod { return engine.verifyKey(password); } + @Override + public int getSaltLength() { + return 12; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java index 36e6dfe3..e16b4c77 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java +++ b/src/main/java/fr/xephi/authme/security/crypts/CryptPBKDF2Django.java @@ -1,18 +1,16 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; import fr.xephi.authme.security.pbkdf2.PBKDF2Engine; import fr.xephi.authme.security.pbkdf2.PBKDF2Parameters; import javax.xml.bind.DatatypeConverter; -import java.security.NoSuchAlgorithmException; -/** - */ -public class CryptPBKDF2Django implements EncryptionMethod { +@AsciiRestricted +public class CryptPBKDF2Django extends HexSaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String result = "pbkdf2_sha256$15000$" + salt + "$"; PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000); PBKDF2Engine engine = new PBKDF2Engine(params); @@ -21,9 +19,8 @@ public class CryptPBKDF2Django implements EncryptionMethod { } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String[] line = hash.split("\\$"); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String[] line = encryptedPassword.getHash().split("\\$"); String salt = line[2]; byte[] derivedKey = DatatypeConverter.parseBase64Binary(line[3]); PBKDF2Parameters params = new PBKDF2Parameters("HmacSHA256", "ASCII", salt.getBytes(), 15000, derivedKey); @@ -31,4 +28,9 @@ public class CryptPBKDF2Django implements EncryptionMethod { return engine.verifyKey(password); } + @Override + public int getSaltLength() { + return 12; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java index 58d30a66..b7823a2b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/DOUBLEMD5.java @@ -1,32 +1,12 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; -/** - */ -public class DOUBLEMD5 implements EncryptionMethod { - - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +public class DOUBLEMD5 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password)); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public String computeHash(String password) { + return md5(md5(password)); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java new file mode 100644 index 00000000..3425801c --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptedPassword.java @@ -0,0 +1,48 @@ +package fr.xephi.authme.security.crypts; + +/** + * The result of a hash computation. See {@link #salt} for details. + */ +public class EncryptedPassword { + + /** The generated hash. */ + private final String hash; + /** + * The generated salt; may be null if no salt is used or if the salt is included + * in the hash output. The salt is only not null if {@link EncryptionMethod#hasSeparateSalt()} + * returns true for the associated encryption method. + *

    + * When the field is not null, it must be stored into the salt column of the data source + * and retrieved again to compare a password with the hash. + */ + private final String salt; + + /** + * Constructor. + * + * @param hash The computed hash + * @param salt The generated salt + */ + public EncryptedPassword(String hash, String salt) { + this.hash = hash; + this.salt = salt; + } + + /** + * Constructor for a hash with no separate salt. + * + * @param hash The computed hash + */ + public EncryptedPassword(String hash) { + this(hash, null); + } + + public String getHash() { + return hash; + } + + public String getSalt() { + return salt; + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java index f925c10d..9381113c 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java +++ b/src/main/java/fr/xephi/authme/security/crypts/EncryptionMethod.java @@ -1,35 +1,59 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - /** * Public interface for custom password encryption methods. */ public interface EncryptionMethod { /** - * Hash the given password with the given salt for the given player. + * Hash the given password for the given player name. * - * @param password The clear-text password to hash - * @param salt The salt to add to the hash - * @param name The player's name (sometimes required for storing the salt separately in the database) + * @param password The password to hash + * @param name The name of the player (sometimes required to generate a salt with) * - * @return The hashed password + * @return The hash result for the password. + * @see EncryptedPassword */ - String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException; + EncryptedPassword computeHash(String password, String name); /** - * Check whether a given hash matches the clear-text password. + * Hash the given password with the given salt for the given player. * - * @param hash The hash to verify - * @param password The clear-text password to verify the hash against - * @param playerName The player name to do the check for (sometimes required for retrieving - * the salt from the database) + * @param password The password to hash + * @param salt The salt to add to the hash + * @param name The player's name (sometimes required to generate a salt with) + * + * @return The hashed password + * @see #hasSeparateSalt() + */ + String computeHash(String password, String salt, String name); + + /** + * Check whether the given hash matches the clear-text password. + * + * @param password The clear-text password to verify + * @param encryptedPassword The hash to check the password against + * @param name The player name to do the check for (sometimes required for generating the salt) * * @return True if the password matches, false otherwise */ - boolean comparePassword(String hash, String password, String playerName) - throws NoSuchAlgorithmException; + boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name); + + /** + * Generate a new salt to hash a password with. + * + * @return The generated salt, null if the method does not use a random text-based salt + */ + String generateSalt(); + + /** + * Return whether the encryption method requires the salt to be stored separately and + * passed again to {@link #comparePassword(String, EncryptedPassword, String)}. Note that + * an encryption method returning {@code false} does not imply that it uses no salt; it + * may be embedded into the hash or it may use the username as salt. + * + * @return True if the salt has to be stored and retrieved separately, false otherwise + */ + boolean hasSeparateSalt(); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java new file mode 100644 index 00000000..2427e431 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/HexSaltedMethod.java @@ -0,0 +1,40 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.security.RandomString; +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; + +/** + * Common type for encryption methods which use a random String of hexadecimal characters + * and store the salt with the hash itself. + */ +@Recommendation(Usage.ACCEPTABLE) +@HasSalt(SaltType.TEXT) // See saltLength() for length +public abstract class HexSaltedMethod implements EncryptionMethod { + + public abstract int getSaltLength(); + + @Override + public abstract String computeHash(String password, String salt, String name); + + @Override + public EncryptedPassword computeHash(String password, String name) { + String salt = generateSalt(); + return new EncryptedPassword(computeHash(password, salt, null)); + } + + @Override + public abstract boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name); + + @Override + public String generateSalt() { + return RandomString.generateHex(getSaltLength()); + } + + @Override + public boolean hasSeparateSalt() { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java index 93abdaf1..85789b88 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/IPB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/IPB3.java @@ -1,34 +1,25 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; +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 java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; -/** - */ -public class IPB3 implements EncryptionMethod { +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(value = SaltType.TEXT, length = 5) +public class IPB3 extends SeparateSaltMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return md5(md5(salt) + md5(password)); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); + public String generateSalt() { + return RandomString.generateHex(5); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, playerName)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java index 41f6fe35..4e42958f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java +++ b/src/main/java/fr/xephi/authme/security/crypts/JOOMLA.java @@ -1,32 +1,27 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class JOOMLA implements EncryptionMethod { +@Recommendation(Usage.RECOMMENDED) +public class JOOMLA extends HexSaltedMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return HashUtils.md5(password + salt) + ":" + salt; } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password + salt) + ":" + salt; + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String unusedName) { + String hash = encryptedPassword.getHash(); + String[] hashParts = hash.split(":"); + return hashParts.length == 2 && hash.equals(computeHash(password, hashParts[1], null)); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = hash.split(":")[1]; - return hash.equals(getMD5(password + salt) + ":" + salt); + public int getSaltLength() { + return 32; } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5.java b/src/main/java/fr/xephi/authme/security/crypts/MD5.java index 8e20dbf4..22164a53 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5.java @@ -1,31 +1,16 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; +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; -/** - */ -public class MD5 implements EncryptionMethod { - - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +public class MD5 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(password); + public String computeHash(String password) { + return HashUtils.md5(password); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java index e54f8ad3..39a08b7f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MD5VB.java @@ -1,33 +1,24 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; -/** - */ -public class MD5VB implements EncryptionMethod { +public class MD5VB extends HexSaltedMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return "$MD5vb$" + salt + "$" + md5(md5(password) + salt); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$MD5vb$" + salt + "$" + getMD5(getMD5(password) + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + String hash = encryptedPassword.getHash(); String[] line = hash.split("\\$"); - return hash.equals(computeHash(password, line[2], "")); + return line.length == 4 && hash.equals(computeHash(password, line[2], name)); + } + + @Override + public int getSaltLength() { + return 16; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java index 7232dd18..97418640 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/MYBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/MYBB.java @@ -1,34 +1,19 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; -/** - */ -public class MYBB implements EncryptionMethod { +public class MYBB extends SeparateSaltMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return md5(md5(salt) + md5(password)); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(salt) + getMD5(password)); + public String generateSalt() { + return RandomString.generateHex(8); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, playerName)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java index ff2e256e..c7d85b6a 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPBB.java @@ -4,25 +4,26 @@ */ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.MessageDigestAlgorithm; + import java.io.UnsupportedEncodingException; -import java.security.GeneralSecurityException; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; /** * @author stefano */ -public class PHPBB implements EncryptionMethod { +public class PHPBB extends HexSaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - public static String md5(String data) { + private static String md5(String data) { try { byte[] bytes = data.getBytes("ISO-8859-1"); - MessageDigest md5er = MessageDigest.getInstance("MD5"); + MessageDigest md5er = HashUtils.getDigest(MessageDigestAlgorithm.MD5); byte[] hash = md5er.digest(bytes); return bytes2hex(hash); - } catch (GeneralSecurityException | UnsupportedEncodingException e) { + } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } @@ -58,7 +59,7 @@ public class PHPBB implements EncryptionMethod { return buf.toString(); } - public String phpbb_hash(String password, String salt) { + private String phpbb_hash(String password, String salt) { String random_state = salt; StringBuilder random = new StringBuilder(); int count = 6; @@ -109,7 +110,7 @@ public class PHPBB implements EncryptionMethod { return output.toString(); } - String _hash_crypt_private(String password, String setting) { + private String _hash_crypt_private(String password, String setting) { String output = "*"; if (!setting.substring(0, 3).equals("$H$")) return output; @@ -130,21 +131,26 @@ public class PHPBB implements EncryptionMethod { return output; } - public boolean phpbb_check_hash(String password, String hash) { - if (hash.length() == 34) + private boolean phpbb_check_hash(String password, String hash) { + if (hash.length() == 34) { return _hash_crypt_private(password, hash).equals(hash); - else return md5(password).equals(hash); + } + return md5(password).equals(hash); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return phpbb_hash(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return phpbb_check_hash(password, hash); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return phpbb_check_hash(password, encryptedPassword.getHash()); } + + @Override + public int getSaltLength() { + return 16; + } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java index 4b8646c0..2950fe67 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PHPFUSION.java @@ -1,36 +1,27 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; -import java.math.BigInteger; import java.security.InvalidKeyException; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -/** - */ -public class PHPFUSION implements EncryptionMethod { - - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +@Recommendation(Usage.DO_NOT_USE) +@AsciiRestricted +public class PHPFUSION extends SeparateSaltMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - String digest = null; + public String computeHash(String password, String salt, String name) { String algo = "HmacSHA256"; - String keyString = getSHA1(salt); + String keyString = HashUtils.sha1(salt); try { - SecretKeySpec key = new SecretKeySpec((keyString).getBytes("UTF-8"), algo); + SecretKeySpec key = new SecretKeySpec(keyString.getBytes("UTF-8"), algo); Mac mac = Mac.getInstance(algo); mac.init(key); byte[] bytes = mac.doFinal(password.getBytes("ASCII")); @@ -42,19 +33,16 @@ public class PHPFUSION implements EncryptionMethod { } hash.append(hex); } - digest = hash.toString(); + return hash.toString(); } catch (UnsupportedEncodingException | InvalidKeyException | NoSuchAlgorithmException e) { - //ingore + throw new UnsupportedOperationException("Cannot create PHPFUSION hash for " + name, e); } - - return digest; } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); + public String generateSalt() { + return RandomString.generateHex(12); } + } diff --git a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java index 8dec71ad..dc2cb3b4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/PLAINTEXT.java @@ -1,21 +1,11 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; - -/** - */ -public class PLAINTEXT implements EncryptionMethod { +@Deprecated +public class PLAINTEXT extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password) { return password; } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(password); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java index 41e658ec..db089e36 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/ROYALAUTH.java @@ -1,35 +1,16 @@ package fr.xephi.authme.security.crypts; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; -/** - */ -public class ROYALAUTH implements EncryptionMethod { +public class ROYALAUTH extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - for (int i = 0; i < 25; i++) - password = hash(password, salt); + public String computeHash(String password) { + for (int i = 0; i < 25; i++) { + // TODO ljacqu 20151228: HashUtils#sha512 gets a new message digest each time... + password = HashUtils.sha512(password); + } return password; } - public String hash(String password, String salt) - throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-512"); - md.update(password.getBytes()); - byte byteData[] = md.digest(); - StringBuilder sb = new StringBuilder(); - for (byte aByteData : byteData) - sb.append(Integer.toString((aByteData & 0xff) + 0x100, 16).substring(1)); - return sb.toString(); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equalsIgnoreCase(computeHash(password, "", "")); - } - } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java index 1de37d50..613bcd3f 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTED2MD5.java @@ -1,34 +1,26 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; +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 java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.md5; -/** - */ -public class SALTED2MD5 implements EncryptionMethod { +@Recommendation(Usage.ACCEPTABLE) // presuming that length is something sensible (>= 8) +@HasSalt(value = SaltType.TEXT) // length defined by Settings.saltLength +public class SALTED2MD5 extends SeparateSaltMethod { - private static String getMD5(String message) - throws NoSuchAlgorithmException { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - md5.reset(); - md5.update(message.getBytes()); - byte[] digest = md5.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return md5(md5(password) + salt); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getMD5(getMD5(password) + salt); + public String generateSalt() { + return RandomString.generateHex(Settings.saltLength); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(getMD5(getMD5(password) + salt)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java index e336a13f..d9952ee4 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SALTEDSHA512.java @@ -1,34 +1,20 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.HashUtils; +import fr.xephi.authme.security.RandomString; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +@Recommendation(Usage.RECOMMENDED) +public class SALTEDSHA512 extends SeparateSaltMethod { -/** - */ -public class SALTEDSHA512 implements EncryptionMethod { - - private static String getSHA512(String message) - throws NoSuchAlgorithmException { - MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - sha512.reset(); - sha512.update(message.getBytes()); - byte[] digest = sha512.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return HashUtils.sha512(password + salt); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); + public String generateSalt() { + return RandomString.generateHex(32); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java index f41c01e9..080910ec 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA1.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA1.java @@ -1,32 +1,12 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; -/** - */ -public class SHA1 implements EncryptionMethod { - - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +public class SHA1 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(password); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); + public String computeHash(String password) { + return HashUtils.sha1(password); } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java index 834b208f..efe2c8dd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA256.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA256.java @@ -1,33 +1,28 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class SHA256 implements EncryptionMethod { +import static fr.xephi.authme.security.HashUtils.sha256; - private static String getSHA256(String message) - throws NoSuchAlgorithmException { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - sha256.reset(); - sha256.update(message.getBytes()); - byte[] digest = sha256.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); +@Recommendation(Usage.RECOMMENDED) +public class SHA256 extends HexSaltedMethod { + + @Override + public String computeHash(String password, String salt, String name) { + return "$SHA$" + salt + "$" + sha256(sha256(password) + salt); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return "$SHA$" + salt + "$" + getSHA256(getSHA256(password) + salt); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + String hash = encryptedPassword.getHash(); String[] line = hash.split("\\$"); - return hash.equals(computeHash(password, line[2], "")); + return line.length == 4 && hash.equals(computeHash(password, line[2], "")); + } + + @Override + public int getSaltLength() { + return 16; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java index 888406a1..81f1e026 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SHA512.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SHA512.java @@ -1,31 +1,12 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; -/** - */ -public class SHA512 implements EncryptionMethod { - - private static String getSHA512(String message) - throws NoSuchAlgorithmException { - MessageDigest sha512 = MessageDigest.getInstance("SHA-512"); - sha512.reset(); - sha512.update(message.getBytes()); - byte[] digest = sha512.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); - } +public class SHA512 extends UnsaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA512(password); + public String computeHash(String password) { + return HashUtils.sha512(password); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SMF.java b/src/main/java/fr/xephi/authme/security/crypts/SMF.java index d2d4f74d..c1d33661 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/SMF.java +++ b/src/main/java/fr/xephi/authme/security/crypts/SMF.java @@ -1,31 +1,11 @@ package fr.xephi.authme.security.crypts; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.HashUtils; -/** - */ -public class SMF implements EncryptionMethod { +public class SMF extends UsernameSaltMethod { - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + public EncryptedPassword computeHash(String password, String name) { + return new EncryptedPassword(HashUtils.sha1(name.toLowerCase() + password)); } - @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(name.toLowerCase() + password); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, null, playerName)); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java new file mode 100644 index 00000000..c7402fcc --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/SeparateSaltMethod.java @@ -0,0 +1,30 @@ +package fr.xephi.authme.security.crypts; + +/** + * Common supertype for encryption methods which store their salt separately from the hash. + */ +public abstract class SeparateSaltMethod implements EncryptionMethod { + + @Override + public abstract String computeHash(String password, String salt, String name); + + @Override + public abstract String generateSalt(); + + @Override + public EncryptedPassword computeHash(String password, String name) { + String salt = generateSalt(); + return new EncryptedPassword(computeHash(password, salt, name), salt); + } + + @Override + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password, encryptedPassword.getSalt(), null)); + } + + @Override + public boolean hasSeparateSalt() { + return true; + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java new file mode 100644 index 00000000..6900b80f --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/UnsaltedMethod.java @@ -0,0 +1,41 @@ +package fr.xephi.authme.security.crypts; + +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; + +/** + * Common type for encryption methods which do not use any salt whatsoever. + */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.NONE) +public abstract class UnsaltedMethod implements EncryptionMethod { + + public abstract String computeHash(String password); + + @Override + public EncryptedPassword computeHash(String password, String name) { + return new EncryptedPassword(computeHash(password)); + } + + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password); + } + + @Override + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password)); + } + + @Override + public String generateSalt() { + return null; + } + + @Override + public boolean hasSeparateSalt() { + return false; + } +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java new file mode 100644 index 00000000..c7b46b11 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/UsernameSaltMethod.java @@ -0,0 +1,39 @@ +package fr.xephi.authme.security.crypts; + +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; + +/** + * Common supertype of encryption methods that use a player's username + * (or something based on it) as embedded salt. + */ +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(SaltType.USERNAME) +public abstract class UsernameSaltMethod implements EncryptionMethod { + + @Override + public abstract EncryptedPassword computeHash(String password, String name); + + @Override + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + return encryptedPassword.getHash().equals(computeHash(password, name).getHash()); + } + + @Override + public String computeHash(String password, String salt, String name) { + return computeHash(password, name).getHash(); + } + + @Override + public String generateSalt() { + return null; + } + + @Override + public boolean hasSeparateSalt() { + return false; + } + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java index a3fe0d5e..6cc12300 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB3.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB3.java @@ -1,34 +1,19 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.AuthMe; +import fr.xephi.authme.security.RandomString; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import static fr.xephi.authme.security.HashUtils.sha1; -/** - */ -public class WBB3 implements EncryptionMethod { +public class WBB3 extends SeparateSaltMethod { - private static String getSHA1(String message) - throws NoSuchAlgorithmException { - MessageDigest sha1 = MessageDigest.getInstance("SHA1"); - sha1.reset(); - sha1.update(message.getBytes()); - byte[] digest = sha1.digest(); - return String.format("%0" + (digest.length << 1) + "x", new BigInteger(1, digest)); + @Override + public String computeHash(String password, String salt, String name) { + return sha1(salt.concat(sha1(salt.concat(sha1(password))))); } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSHA1(salt.concat(getSHA1(salt.concat(getSHA1(password))))); + public String generateSalt() { + return RandomString.generateHex(40); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(computeHash(password, salt, "")); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java index 05272886..9f67d499 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WBB4.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WBB4.java @@ -1,21 +1,34 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class WBB4 implements EncryptionMethod { +@Recommendation(Usage.DOES_NOT_WORK) +public class WBB4 extends HexSaltedMethod { @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { return BCRYPT.getDoubleHash(password, salt); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return BCRYPT.checkpw(password, hash, 2); + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + return BCRYPT.checkpw(password, encryptedPassword.getHash(), 2); + } + + @Override + public String generateSalt() { + return BCRYPT.gensalt(8); + } + + /** + * Note that {@link #generateSalt()} is overridden for this class. + * + * @return The salt length + */ + @Override + public int getSaltLength() { + return 8; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java index 0ce0c2e5..522fb45b 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WHIRLPOOL.java @@ -59,12 +59,9 @@ package fr.xephi.authme.security.crypts; * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import java.security.NoSuchAlgorithmException; import java.util.Arrays; -/** - */ -public class WHIRLPOOL implements EncryptionMethod { +public class WHIRLPOOL extends UnsaltedMethod { /** * The message digest size (in bits) @@ -382,9 +379,7 @@ public class WHIRLPOOL implements EncryptionMethod { } } - @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password) { byte[] digest = new byte[DIGESTBYTES]; NESSIEinit(); NESSIEadd(password); @@ -392,9 +387,4 @@ public class WHIRLPOOL implements EncryptionMethod { return display(digest); } - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - return hash.equals(computeHash(password, "", "")); - } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java index 3147ddbb..5b198056 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java +++ b/src/main/java/fr/xephi/authme/security/crypts/WORDPRESS.java @@ -1,14 +1,22 @@ package fr.xephi.authme.security.crypts; +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 java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; -/** - */ -public class WORDPRESS implements EncryptionMethod { +// TODO #391: Wordpress algorithm fails sometimes. Fix it and change the Recommendation to "ACCEPTABLE" if appropriate +@Recommendation(Usage.DO_NOT_USE) +@HasSalt(value = SaltType.TEXT, length = 9) +// Note ljacqu 20151228: Wordpress is actually a salted algorithm but salt generation is handled internally +// and isn't exposed to the outside, so we treat it as an unsalted implementation +public class WORDPRESS extends UnsaltedMethod { private static final String itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private final SecureRandom randomGen = new SecureRandom(); @@ -102,16 +110,15 @@ public class WORDPRESS implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password) { byte random[] = new byte[6]; - this.randomGen.nextBytes(random); + randomGen.nextBytes(random); return crypt(password, gensaltPrivate(stringToUtf8(new String(random)))); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String name) { + String hash = encryptedPassword.getHash(); String comparedHash = crypt(password, hash); return comparedHash.equals(hash); } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java index aa287ddd..ff5236bd 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java +++ b/src/main/java/fr/xephi/authme/security/crypts/XAUTH.java @@ -1,12 +1,12 @@ package fr.xephi.authme.security.crypts; -import java.security.NoSuchAlgorithmException; +import fr.xephi.authme.security.crypts.description.Recommendation; +import fr.xephi.authme.security.crypts.description.Usage; -/** - */ -public class XAUTH implements EncryptionMethod { +@Recommendation(Usage.RECOMMENDED) +public class XAUTH extends HexSaltedMethod { - public static String getWhirlpool(String message) { + private static String getWhirlpool(String message) { WHIRLPOOL w = new WHIRLPOOL(); byte[] digest = new byte[WHIRLPOOL.DIGESTBYTES]; w.NESSIEinit(); @@ -16,19 +16,23 @@ public class XAUTH implements EncryptionMethod { } @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { + public String computeHash(String password, String salt, String name) { String hash = getWhirlpool(salt + password).toLowerCase(); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); return hash.substring(0, saltPos) + salt + hash.substring(saltPos); } @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { + public boolean comparePassword(String password, EncryptedPassword encryptedPassword, String playerName) { + String hash = encryptedPassword.getHash(); int saltPos = (password.length() >= hash.length() ? hash.length() - 1 : password.length()); - String salt = hash.substring(saltPos, saltPos + 12); - return hash.equals(computeHash(password, salt, "")); + String saltFromHash = hash.substring(saltPos, saltPos + 12); + return hash.equals(computeHash(password, saltFromHash, null)); + } + + @Override + public int getSaltLength() { + return 12; } } diff --git a/src/main/java/fr/xephi/authme/security/crypts/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java deleted file mode 100644 index 00a23c8e..00000000 --- a/src/main/java/fr/xephi/authme/security/crypts/XF.java +++ /dev/null @@ -1,56 +0,0 @@ -package fr.xephi.authme.security.crypts; - -import fr.xephi.authme.AuthMe; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - */ -public class XF implements EncryptionMethod { - - @Override - public String computeHash(String password, String salt, String name) - throws NoSuchAlgorithmException { - return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt)); - } - - @Override - public boolean comparePassword(String hash, String password, - String playerName) throws NoSuchAlgorithmException { - String salt = AuthMe.getInstance().database.getAuth(playerName).getSalt(); - return hash.equals(regmatch("\"hash\";.:..:\"(.*)\";.:.:\"salt\"", salt)); - } - - private String getSha256(String password) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(password.getBytes()); - byte byteData[] = md.digest(); - StringBuilder sb = new StringBuilder(); - for (byte element : byteData) { - sb.append(Integer.toString((element & 0xff) + 0x100, 16).substring(1)); - } - StringBuilder hexString = new StringBuilder(); - for (byte element : byteData) { - String hex = Integer.toHexString(0xff & element); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - - private String regmatch(String pattern, String line) { - List allMatches = new ArrayList<>(); - Matcher m = Pattern.compile(pattern).matcher(line); - while (m.find()) { - allMatches.add(m.group(1)); - } - return allMatches.get(0); - } -} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java b/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java new file mode 100644 index 00000000..f515717e --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/AsciiRestricted.java @@ -0,0 +1,15 @@ +package fr.xephi.authme.security.crypts.description; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Denotes an encryption algorithm that is restricted to the ASCII charset. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface AsciiRestricted { + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java new file mode 100644 index 00000000..1baf1e19 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/HasSalt.java @@ -0,0 +1,22 @@ +package fr.xephi.authme.security.crypts.description; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Describes the type of salt the encryption algorithm uses. This is purely for documentation + * purposes and is ignored by the code. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HasSalt { + + /** The type of the salt. */ + SaltType value(); + + /** For text salts, the length of the salt. */ + int length() default 0; + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java new file mode 100644 index 00000000..f37c3eac --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Recommendation.java @@ -0,0 +1,18 @@ +package fr.xephi.authme.security.crypts.description; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a hash algorithm with the usage recommendation, see {@link Usage}. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Recommendation { + + /** The recommendation for using the hash algorithm. */ + Usage value(); + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java new file mode 100644 index 00000000..7d6b225c --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/SaltType.java @@ -0,0 +1,17 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * The type of salt used by an encryption algorithm. + */ +public enum SaltType { + + /** Random, newly generated text. */ + TEXT, + + /** Salt is based on the username, including variations and repetitions. */ + USERNAME, + + /** No salt. */ + NONE + +} diff --git a/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java new file mode 100644 index 00000000..ecf37a98 --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/description/Usage.java @@ -0,0 +1,20 @@ +package fr.xephi.authme.security.crypts.description; + +/** + * Usage recommendation that can be provided for a hash algorithm. + */ +public enum Usage { + + /** The hash algorithm appears to be cryptographically secure and is one of the algorithms recommended by AuthMe. */ + RECOMMENDED, + + /** There are safer algorithms that can be chosen but using the algorithm is generally OK. */ + ACCEPTABLE, + + /** Hash algorithm is not recommended to be used. Use only if required by another system. */ + DO_NOT_USE, + + /** The algorithm does not work properly; do not use. */ + DOES_NOT_WORK + +} diff --git a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java index c5c7df3b..d85149b6 100644 --- a/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java +++ b/src/main/java/fr/xephi/authme/task/ChangePasswordTask.java @@ -1,20 +1,18 @@ package fr.xephi.authme.task; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; -import fr.xephi.authme.security.PasswordSecurity; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; +import fr.xephi.authme.security.PasswordSecurity; +import fr.xephi.authme.security.crypts.EncryptedPassword; import fr.xephi.authme.settings.Settings; import org.bukkit.entity.Player; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; - -import java.security.NoSuchAlgorithmException; - public class ChangePasswordTask implements Runnable { private final AuthMe plugin; @@ -32,48 +30,40 @@ public class ChangePasswordTask implements Runnable { @Override public void run() { Messages m = plugin.getMessages(); - try { - final String name = player.getName().toLowerCase(); - String hashNew = PasswordSecurity.getHash(Settings.getPasswordHash, newPassword, name); - PlayerAuth auth = PlayerCache.getInstance().getAuth(name); - if (PasswordSecurity.comparePasswordWithHash(oldPassword, auth.getHash(), player.getName())) { - auth.setHash(hashNew); - if (PasswordSecurity.userSalt.containsKey(name) && PasswordSecurity.userSalt.get(name) != null) { - auth.setSalt(PasswordSecurity.userSalt.get(name)); - } else { - auth.setSalt(""); - } - if (!plugin.database.updatePassword(auth)) { - m.send(player, MessageKey.ERROR); - return; - } - plugin.database.updateSalt(auth); - PlayerCache.getInstance().updatePlayer(auth); - m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); - ConsoleLogger.info(player.getName() + " changed his password"); - if (Settings.bungee) - { - final String hash = auth.getHash(); - final String salt = auth.getSalt(); - plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + PasswordSecurity passwordSecurity = plugin.getPasswordSecurity(); - @Override - public void run() { - ByteArrayDataOutput out = ByteStreams.newDataOutput(); - out.writeUTF("Forward"); - out.writeUTF("ALL"); - out.writeUTF("AuthMe"); - out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); - player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); - } - }); - } - } else { - m.send(player, MessageKey.WRONG_PASSWORD); + final String name = player.getName().toLowerCase(); + PlayerAuth auth = PlayerCache.getInstance().getAuth(name); + if (passwordSecurity.comparePassword(oldPassword, auth.getPassword(), player.getName())) { + EncryptedPassword encryptedPassword = passwordSecurity.computeHash(newPassword, name); + auth.setPassword(encryptedPassword); + + if (!plugin.getDataSource().updatePassword(auth)) { + m.send(player, MessageKey.ERROR); + return; } - } catch (NoSuchAlgorithmException ex) { - ConsoleLogger.showError(ex.getMessage()); - m.send(player, MessageKey.ERROR); + + PlayerCache.getInstance().updatePlayer(auth); + m.send(player, MessageKey.PASSWORD_CHANGED_SUCCESS); + ConsoleLogger.info(player.getName() + " changed his password"); + if (Settings.bungee) { + final String hash = encryptedPassword.getHash(); + final String salt = encryptedPassword.getSalt(); + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable(){ + + @Override + public void run() { + ByteArrayDataOutput out = ByteStreams.newDataOutput(); + out.writeUTF("Forward"); + out.writeUTF("ALL"); + out.writeUTF("AuthMe"); + out.writeUTF("changepassword;" + name + ";" + hash + ";" + salt); + player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray()); + } + }); + } + } else { + m.send(player, MessageKey.WRONG_PASSWORD); } } } diff --git a/src/main/java/fr/xephi/authme/util/Utils.java b/src/main/java/fr/xephi/authme/util/Utils.java index 1388917d..bab7c7ee 100644 --- a/src/main/java/fr/xephi/authme/util/Utils.java +++ b/src/main/java/fr/xephi/authme/util/Utils.java @@ -143,13 +143,9 @@ public final class Utils { return true; } - if (!Settings.isForcedRegistrationEnabled) { - // TODO ljacqu 20151123: Use a getter to retrieve things from AuthMe - if (!plugin.database.isAuthAvailable(player.getName())) { - return true; - } + if (!Settings.isForcedRegistrationEnabled && !plugin.getDataSource().isAuthAvailable(player.getName())) { + return true; } - return false; } @@ -159,15 +155,6 @@ public final class Utils { && (Settings.getUnrestrictedName.contains(player.getName().toLowerCase())); } - /** - * Method packCoords. - * - * @param x double - * @param y double - * @param z double - * @param w String - * @param pl Player - */ public static void packCoords(double x, double y, double z, String w, final Player pl) { World theWorld; if (w.equals("unavailableworld")) { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 7f151c49..a98d9739 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -181,7 +181,7 @@ settings: # Example unLoggedinGroup: NotLogged unLoggedinGroup: unLoggedinGroup # possible values: MD5, SHA1, SHA256, WHIRLPOOL, XAUTH, MD5VB, PHPBB, - # MYBB, IPB3, PHPFUSION, SMF, XENFORO, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, + # MYBB, IPB3, PHPFUSION, SMF, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, # DOUBLEMD5, PBKDF2, PBKDF2DJANGO, WORDPRESS, ROYALAUTH, CUSTOM(for developpers only) passwordHash: SHA256 # salt length for the SALTED2MD5 MD5(MD5(password)+salt) diff --git a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java index 7780db7a..3b457291 100644 --- a/src/test/java/fr/xephi/authme/command/CommandServiceTest.java +++ b/src/test/java/fr/xephi/authme/command/CommandServiceTest.java @@ -2,10 +2,12 @@ package fr.xephi.authme.command; import fr.xephi.authme.AuthMe; import fr.xephi.authme.command.help.HelpProvider; +import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.output.MessageKey; import fr.xephi.authme.output.Messages; import fr.xephi.authme.permission.PermissionsManager; import fr.xephi.authme.process.Management; +import fr.xephi.authme.security.PasswordSecurity; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.junit.Before; @@ -32,6 +34,7 @@ public class CommandServiceTest { private CommandMapper commandMapper; private HelpProvider helpProvider; private Messages messages; + private PasswordSecurity passwordSecurity; private CommandService commandService; @Before @@ -40,7 +43,8 @@ public class CommandServiceTest { commandMapper = mock(CommandMapper.class); helpProvider = mock(HelpProvider.class); messages = mock(Messages.class); - commandService = new CommandService(authMe, commandMapper, helpProvider, messages); + passwordSecurity = mock(PasswordSecurity.class); + commandService = new CommandService(authMe, commandMapper, helpProvider, messages, passwordSecurity); } @Test @@ -110,9 +114,25 @@ public class CommandServiceTest { } @Test - @Ignore public void shouldGetDataSource() { - // TODO ljacqu 20151226: Cannot mock calls to fields + // given + DataSource dataSource = mock(DataSource.class); + given(authMe.getDataSource()).willReturn(dataSource); + + // when + DataSource result = commandService.getDataSource(); + + // then + assertThat(result, equalTo(dataSource)); + } + + @Test + public void shouldGetPasswordSecurity() { + // given/when + PasswordSecurity passwordSecurity = commandService.getPasswordSecurity(); + + // then + assertThat(passwordSecurity, equalTo(this.passwordSecurity)); } @Test diff --git a/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java new file mode 100644 index 00000000..c5cfea95 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/HashAlgorithmIntegrationTest.java @@ -0,0 +1,61 @@ +package fr.xephi.authme.security; + +import fr.xephi.authme.security.crypts.EncryptedPassword; +import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.StringUtils; +import fr.xephi.authme.util.WrapperMock; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Set; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * Integration test for {@link HashAlgorithm}. + */ +public class HashAlgorithmIntegrationTest { + + @BeforeClass + public static void setUpWrapper() { + WrapperMock.createInstance(); + Settings.bCryptLog2Rounds = 8; + Settings.saltLength = 16; + } + + @Test + public void shouldHaveUniqueClassForEntries() { + // given + Set> classes = new HashSet<>(); + + // when / then + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + if (classes.contains(algorithm.getClazz())) { + fail("Found class '" + algorithm.getClazz() + "' twice!"); + } + classes.add(algorithm.getClazz()); + } + } + } + + @Test + public void shouldBeAbleToInstantiateEncryptionAlgorithms() throws InstantiationException, IllegalAccessException { + // given / when / then + for (HashAlgorithm algorithm : HashAlgorithm.values()) { + if (!HashAlgorithm.CUSTOM.equals(algorithm)) { + EncryptionMethod method = algorithm.getClazz().newInstance(); + EncryptedPassword encryptedPassword = method.computeHash("pwd", "name"); + assertThat("Salt should not be null if method.hasSeparateSalt(), and vice versa. Method: '" + + method + "'", StringUtils.isEmpty(encryptedPassword.getSalt()), equalTo(!method.hasSeparateSalt())); + assertThat("Hash should not be empty for method '" + method + "'", + StringUtils.isEmpty(encryptedPassword.getHash()), equalTo(false)); + } + } + } + +} diff --git a/src/test/java/fr/xephi/authme/security/HashUtilsTest.java b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java new file mode 100644 index 00000000..02237cde --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/HashUtilsTest.java @@ -0,0 +1,116 @@ +package fr.xephi.authme.security; + +import org.junit.Test; + +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; + +/** + * Test for {@link HashUtils}. + */ +public class HashUtilsTest { + + /** + * List of passwords whose hash is provided to the class to test against. + */ + public static final String[] GIVEN_PASSWORDS = {"", "password", "PassWord1", "&^%te$t?Pw@_"}; + + @Test + public void shouldHashMd5() { + // given + String[] correctHashes = { + "d41d8cd98f00b204e9800998ecf8427e", // empty string + "5f4dcc3b5aa765d61d8327deb882cf99", // password + "f2126d405f46ed603ff5b2950f062c96", // PassWord1 + "0833dcd2bc741f90c46bbac5498fd08f" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.md5(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldHashSha1() { + // given + String[] correctHashes = { + "da39a3ee5e6b4b0d3255bfef95601890afd80709", // empty string + "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", // password + "285d0c707f9644b75e1a87a62f25d0efb56800f0", // PassWord1 + "a42ef8e61e890af80461ca5dcded25cbfcf407a4" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha1(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldHashSha256() { + // given + String[] correctHashes = { + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", // empty string + "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8", // password + "c04265d72b749debd67451c083785aa572742e3222e86884de16485fa14b55e7", // PassWord1 + "005e3d7439d3e9a60a9d74aa1c763b36bfebec8e434ab6c5efab3df37eb2dae6" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha256(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + + @Test + public void shouldHashSha512() { + // given + String[] correctHashes = { + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", // empty string + "b109f3bbbc244eb82441917ed06d618b9008dd09b3befd1b5e07394c706a8bb980b1d7785e5976ec049b46df5f1326af5a2ea6d103fd07c95385ffab0cacbc86", // password + "ae9942149995a8171391625b36da134d5e288c721650d7c8d2d464fb49a49f3f551e4916ab1e097d9dd1201b01d69b1dccdefa3d2524a66092fb61b3df6e7e71", // PassWord1 + "8c4f3df78db191142d819a72c16058b9e1ea41ae9b1649e1184eb89e30344c51c9c71039c483cf2f1b76b51480d8459d7eb3cfbaa24b07f2041d1551af4ead75" // &^%te$t?Pw@_ + }; + + // when + List result = new ArrayList<>(); + for (String password : GIVEN_PASSWORDS) { + result.add(HashUtils.sha512(password)); + } + + // then + assertThat(result, contains(correctHashes)); + } + + @Test + public void shouldRetrieveMd5Instance() { + // given + MessageDigestAlgorithm algorithm = MessageDigestAlgorithm.MD5; + + // when + MessageDigest digest = HashUtils.getDigest(algorithm); + + // then + assertThat(digest.getAlgorithm(), equalTo("MD5")); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java new file mode 100644 index 00000000..ae8e49e7 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -0,0 +1,224 @@ +package fr.xephi.authme.security; + +import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.datasource.DataSource; +import fr.xephi.authme.events.PasswordEncryptionEvent; +import fr.xephi.authme.security.crypts.EncryptedPassword; +import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.JOOMLA; +import fr.xephi.authme.security.crypts.PHPBB; +import org.bukkit.event.Event; +import org.bukkit.plugin.PluginManager; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +/** + * Test for {@link PasswordSecurity}. + */ +public class PasswordSecurityTest { + + private PluginManager pluginManager; + private DataSource dataSource; + private EncryptionMethod method; + private Class caughtClassInEvent; + + @Before + public void setUpMocks() { + pluginManager = mock(PluginManager.class); + dataSource = mock(DataSource.class); + method = mock(EncryptionMethod.class); + caughtClassInEvent = null; + + // When the password encryption event is emitted, replace the encryption method with our mock. + doAnswer(new Answer() { + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + if (arguments[0] instanceof PasswordEncryptionEvent) { + PasswordEncryptionEvent event = (PasswordEncryptionEvent) arguments[0]; + caughtClassInEvent = event.getMethod() != null ? event.getMethod().getClass() : null; + event.setMethod(method); + } + return null; + } + }).when(pluginManager).callEvent(any(Event.class)); + } + + @Test + public void shouldReturnPasswordMatch() { + // given + EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null); + String playerName = "Tester"; + String clearTextPass = "myPassTest"; + + PlayerAuth auth = mock(PlayerAuth.class); + given(auth.getPassword()).willReturn(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(true); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.BCRYPT, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(true)); + verify(dataSource).getAuth(playerName); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + } + + @Test + public void shouldReturnPasswordMismatch() { + // given + EncryptedPassword password = new EncryptedPassword("$TEST$10$SOME_HASH", null); + String playerName = "My_PLayer"; + String clearTextPass = "passw0Rd1"; + + PlayerAuth auth = mock(PlayerAuth.class); + given(auth.getPassword()).willReturn(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.CUSTOM, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(false)); + verify(dataSource).getAuth(playerName); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + } + + @Test + public void shouldReturnFalseIfPlayerDoesNotExist() { + // given + String playerName = "bobby"; + String clearTextPass = "tables"; + + given(dataSource.getAuth(playerName)).willReturn(null); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, false); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(false)); + verify(dataSource).getAuth(playerName); + verify(pluginManager, never()).callEvent(any(Event.class)); + verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString()); + } + + @Test + public void shouldTryOtherMethodsForFailedPassword() { + // given + // BCRYPT2Y hash for "Test" + EncryptedPassword password = + new EncryptedPassword("$2y$10$2e6d2193f43501c926e25elvWlPmWczmrfrnbZV0dUZGITjYjnkkW"); + String playerName = "somePlayer"; + String clearTextPass = "Test"; + // MD5 hash for "Test" + EncryptedPassword newPassword = new EncryptedPassword("0cbc6611f5540bd0809a388dc95a615b"); + + PlayerAuth auth = mock(PlayerAuth.class); + doCallRealMethod().when(auth).getPassword(); + doCallRealMethod().when(auth).setPassword(any(EncryptedPassword.class)); + auth.setPassword(password); + given(dataSource.getAuth(playerName)).willReturn(auth); + given(method.comparePassword(clearTextPass, password, playerName)).willReturn(false); + given(method.computeHash(clearTextPass, playerName)).willReturn(newPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, true); + + // when + boolean result = security.comparePassword(clearTextPass, playerName); + + // then + assertThat(result, equalTo(true)); + verify(dataSource, times(2)).getAuth(playerName); + verify(pluginManager, times(2)).callEvent(any(PasswordEncryptionEvent.class)); + verify(method).comparePassword(clearTextPass, password, playerName); + verify(auth).setPassword(newPassword); + + ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); + verify(dataSource).updatePassword(captor.capture()); + assertThat(captor.getValue().getPassword(), equalTo(newPassword)); + } + + @Test + public void shouldHashPassword() { + // given + String password = "MyP@ssword"; + String username = "theUserInTest"; + EncryptedPassword encryptedPassword = new EncryptedPassword("$T$est#Hash", "__someSalt__"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true); + + // when + EncryptedPassword result = security.computeHash(password, username); + + // then + assertThat(result, equalTo(encryptedPassword)); + ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); + verify(pluginManager).callEvent(captor.capture()); + PasswordEncryptionEvent event = captor.getValue(); + assertThat(JOOMLA.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(event.getPlayerName(), equalTo(username)); + } + + @Test + public void shouldHashPasswordWithGivenAlgorithm() { + // given + String password = "TopSecretPass#112525"; + String username = "someone12"; + EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash", "__someSalt__"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.JOOMLA, pluginManager, true); + + // when + EncryptedPassword result = security.computeHash(HashAlgorithm.PHPBB, password, username); + + // then + assertThat(result, equalTo(encryptedPassword)); + ArgumentCaptor captor = ArgumentCaptor.forClass(PasswordEncryptionEvent.class); + verify(pluginManager).callEvent(captor.capture()); + PasswordEncryptionEvent event = captor.getValue(); + assertThat(PHPBB.class.equals(caughtClassInEvent), equalTo(true)); + assertThat(event.getPlayerName(), equalTo(username)); + } + + @Test + public void shouldSkipCheckIfMandatorySaltIsUnavailable() { + // given + String password = "?topSecretPass\\"; + String username = "someone12"; + EncryptedPassword encryptedPassword = new EncryptedPassword("~T!est#Hash"); + given(method.computeHash(password, username)).willReturn(encryptedPassword); + given(method.hasSeparateSalt()).willReturn(true); + PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.XAUTH, pluginManager, true); + + // when + boolean result = security.comparePassword(password, encryptedPassword, username); + + // then + assertThat(result, equalTo(false)); + verify(dataSource, never()).getAuth(anyString()); + verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); + verify(method, never()).comparePassword(anyString(), any(EncryptedPassword.class), anyString()); + } + +} diff --git a/src/test/java/fr/xephi/authme/security/RandomStringTest.java b/src/test/java/fr/xephi/authme/security/RandomStringTest.java new file mode 100644 index 00000000..938f095c --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/RandomStringTest.java @@ -0,0 +1,55 @@ +package fr.xephi.authme.security; + +import org.junit.Test; + +import java.util.regex.Pattern; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +/** + * Test for {@link RandomString}. + */ +public class RandomStringTest { + + @Test + public void shouldGenerateRandomStrings() { + // given + int[] lengths = {0, 1, 19, 142, 1872}; + Pattern badChars = Pattern.compile(".*[^0-9a-z].*"); + + // when / then + for (int length : lengths) { + String result = RandomString.generate(length); + assertThat("Result '" + result + "' should have length " + length, + result.length(), equalTo(length)); + assertThat("Result '" + result + "' should only have characters a-z, 0-9", + badChars.matcher(result).matches(), equalTo(false)); + } + } + + @Test + public void shouldGenerateRandomHexString() { + // given + int[] lengths = {0, 1, 21, 160, 1784}; + Pattern badChars = Pattern.compile(".*[^0-9a-f].*"); + + // when / then + for (int length : lengths) { + String result = RandomString.generateHex(length); + assertThat("Result '" + result + "' should have length " + length, + result.length(), equalTo(length)); + assertThat("Result '" + result + "' should only have characters a-f, 0-9", + badChars.matcher(result).matches(), equalTo(false)); + } + } + + @Test(expected = IllegalArgumentException.class) + public void shouldThrowForInvalidLength() { + // given/when + RandomString.generate(-3); + + // then - throw exception + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java index ea18df9a..cb81164b 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/AbstractEncryptionMethodTest.java @@ -1,13 +1,16 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.PasswordSecurity; +import com.google.common.collect.ImmutableList; +import fr.xephi.authme.security.crypts.description.AsciiRestricted; import org.junit.Test; -import java.security.NoSuchAlgorithmException; import java.util.HashMap; +import java.util.List; import java.util.Map; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; /** @@ -27,30 +30,48 @@ public abstract class AbstractEncryptionMethodTest { * List of passwords that are hashed at runtime and then tested against; this verifies that hashes that are * generated are valid. */ - private static final String[] INTERNAL_PASSWORDS = {"test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"}; + private static final List INTERNAL_PASSWORDS = + ImmutableList.of("test1234", "Ab_C73", "(!#&$~`_-Aa0", "Ûïé1&?+A"); /** The encryption method to test. */ private EncryptionMethod method; /** Map with the hashes against which the entries in GIVEN_PASSWORDS are tested. */ - private Map hashes; + private Map hashes; /** * Create a new test for the given encryption method. * * @param method The encryption method to test - * @param hash0 The pre-generated hash for the first {@link #GIVEN_PASSWORDS} - * @param hash1 The pre-generated hash for the second {@link #GIVEN_PASSWORDS} - * @param hash2 The pre-generated hash for the third {@link #GIVEN_PASSWORDS} - * @param hash3 The pre-generated hash for the fourth {@link #GIVEN_PASSWORDS} + * @param computedHashes The pre-generated hashes for the elements in {@link #GIVEN_PASSWORDS} */ - public AbstractEncryptionMethodTest(EncryptionMethod method, String hash0, String hash1, - String hash2, String hash3) { + public AbstractEncryptionMethodTest(EncryptionMethod method, String... computedHashes) { + if (method.hasSeparateSalt()) { + throw new UnsupportedOperationException("Test must be initialized with EncryptedPassword objects if " + + "the salt is stored separately. Use the other constructor"); + } else if (computedHashes.length != GIVEN_PASSWORDS.length) { + throw new UnsupportedOperationException("Expected " + GIVEN_PASSWORDS.length + " hashes"); + } this.method = method; + hashes = new HashMap<>(); - hashes.put(GIVEN_PASSWORDS[0], hash0); - hashes.put(GIVEN_PASSWORDS[1], hash1); - hashes.put(GIVEN_PASSWORDS[2], hash2); - hashes.put(GIVEN_PASSWORDS[3], hash3); + for (int i = 0; i < GIVEN_PASSWORDS.length; ++i) { + hashes.put(GIVEN_PASSWORDS[i], new EncryptedPassword(computedHashes[i])); + } + } + + public AbstractEncryptionMethodTest(EncryptionMethod method, EncryptedPassword result0, EncryptedPassword result1, + EncryptedPassword result2, EncryptedPassword result3) { + if (!method.hasSeparateSalt()) { + throw new UnsupportedOperationException("Salt is not stored separately, so test should be initialized" + + " with the password hashes only. Use the other constructor"); + } + this.method = method; + + hashes = new HashMap<>(); + hashes.put(GIVEN_PASSWORDS[0], result0); + hashes.put(GIVEN_PASSWORDS[1], result1); + hashes.put(GIVEN_PASSWORDS[2], result2); + hashes.put(GIVEN_PASSWORDS[3], result3); } @Test @@ -75,31 +96,36 @@ public abstract class AbstractEncryptionMethodTest { @Test public void testPasswordEquality() { - for (String password : INTERNAL_PASSWORDS) { - try { - String hash = method.computeHash(password, getSalt(method), USERNAME); - assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", - method.comparePassword(hash, password, USERNAME)); - if (!password.equals(password.toLowerCase())) { - assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", - method.comparePassword(hash, password.toLowerCase(), USERNAME)); - } - if (!password.equals(password.toUpperCase())) { - assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", - method.comparePassword(hash, password.toUpperCase(), USERNAME)); - } - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e); + List internalPasswords = method.getClass().isAnnotationPresent(AsciiRestricted.class) + ? INTERNAL_PASSWORDS.subList(0, INTERNAL_PASSWORDS.size() - 1) + : INTERNAL_PASSWORDS; + + for (String password : internalPasswords) { + final String salt = method.generateSalt(); + final String hash = method.computeHash(password, salt, USERNAME); + EncryptedPassword encryptedPassword = new EncryptedPassword(hash, salt); + + // Check that the computeHash(password, salt, name) method has the same output for the returned salt + if (testHashEqualityForSameSalt()) { + assertThat("Computing a hash with the same salt will generate the same hash", + hash, equalTo(method.computeHash(password, salt, USERNAME))); + } + + assertTrue("Generated hash for '" + password + "' should match password (hash = '" + hash + "')", + method.comparePassword(password, encryptedPassword, USERNAME)); + if (!password.equals(password.toLowerCase())) { + assertFalse("Lower-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(password.toLowerCase(), encryptedPassword, USERNAME)); + } + if (!password.equals(password.toUpperCase())) { + assertFalse("Upper-case of '" + password + "' should not match generated hash '" + hash + "'", + method.comparePassword(password.toUpperCase(), encryptedPassword, USERNAME)); } } } private boolean doesGivenHashMatch(String password, EncryptionMethod method) { - try { - return method.comparePassword(hashes.get(password), password, USERNAME); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("EncryptionMethod '" + method + "' threw exception", e); - } + return method.comparePassword(password, hashes.get(password), USERNAME); } // @org.junit.Test public void a() { AbstractEncryptionMethodTest.generateTest(); } @@ -116,43 +142,30 @@ public abstract class AbstractEncryptionMethodTest { if (password.equals(GIVEN_PASSWORDS[GIVEN_PASSWORDS.length - 1])) { delim = "); "; } - try { - System.out.println("\t\t\"" + method.computeHash(password, getSalt(method), USERNAME) + + if (method.hasSeparateSalt()) { + EncryptedPassword encryptedPassword = method.computeHash(password, USERNAME); + System.out.println(String.format("\t\tnew EncryptedPassword(\"%s\", \"%s\")%s// %s", + encryptedPassword.getHash(), encryptedPassword.getSalt(), delim, password)); + } else { + System.out.println("\t\t\"" + method.computeHash(password, USERNAME).getHash() + "\"" + delim + "// " + password); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("Could not generate hash", e); } } System.out.println("\t}"); System.out.println("\n}"); } - // TODO #358: Remove this method and use the new salt method on the interface - private static String getSalt(EncryptionMethod method) { - try { - if (method instanceof BCRYPT) { - return BCRYPT.gensalt(); - } else if (method instanceof MD5 || method instanceof WORDPRESS || method instanceof SMF - || method instanceof SHA512 || method instanceof SHA1 || method instanceof ROYALAUTH - || method instanceof DOUBLEMD5) { - return ""; - } else if (method instanceof JOOMLA || method instanceof SALTEDSHA512) { - return PasswordSecurity.createSalt(32); - } else if (method instanceof SHA256 || method instanceof PHPBB || method instanceof WHIRLPOOL - || method instanceof MD5VB || method instanceof BCRYPT2Y) { - return PasswordSecurity.createSalt(16); - } else if (method instanceof WBB3) { - return PasswordSecurity.createSalt(40); - } else if (method instanceof XAUTH || method instanceof CryptPBKDF2Django - || method instanceof CryptPBKDF2) { - return PasswordSecurity.createSalt(12); - } else if (method instanceof WBB4) { - return BCRYPT.gensalt(8); - } - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - throw new IllegalStateException("Unknown EncryptionMethod for salt generation"); + /** + * Return whether an encryption algorithm should be tested that it generates the same + * hash for the same salt. If {@code true}, we call {@link EncryptionMethod#computeHash(String, String)} + * and verify that {@link EncryptionMethod#computeHash(String, String, String)} generates + * the same hash for the salt returned in the first call. + * + * @return Whether or not to test that the hash is the same for the same salt + */ + protected boolean testHashEqualityForSameSalt() { + return true; } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java index d49c1d3c..ac34dea0 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BCRYPT2YTest.java @@ -1,23 +1,16 @@ package fr.xephi.authme.security.crypts; -import fr.xephi.authme.security.PasswordSecurity; -import org.junit.Ignore; -import org.junit.Test; - -import java.security.NoSuchAlgorithmException; - /** * Test for {@link BCRYPT2Y}. */ -@Ignore -// TODO #369: Fix hash & add standard test -public class BCRYPT2YTest { +public class BCRYPT2YTest extends AbstractEncryptionMethodTest { - @Test - public void shouldCreateHash() throws NoSuchAlgorithmException { - String salt = PasswordSecurity.createSalt(16); // As defined in PasswordSecurity - EncryptionMethod method = new BCRYPT2Y(); - System.out.println(method.computeHash("password", salt, "testPlayer")); + public BCRYPT2YTest() { + super(new BCRYPT2Y(), + "$2y$10$da641e404b982edf1c7c0uTU9BcKzfA2vWKV05q6r.dCvm/93wqVK", // password + "$2y$10$e52c48a76f5b86f5da899uiK/HYocyPsfQXESNbP278rIz08LKEP2", // PassWord1 + "$2y$10$be6f11548dc5fb4088410ONdC0dXnJ04y1RHcJh5fVF3XK5d.qgqK", // &^%te$t?Pw@_ + "$2y$10$a8097db1fa4423b93f1b2eF6rMAGFkSX178fpROf/OvCFtrDebp6K"); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java index 2d133d40..65ea3b68 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -1,10 +1,20 @@ package fr.xephi.authme.security.crypts; +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.WrapperMock; +import org.junit.Before; + /** * Test for {@link BCRYPT}. */ public class BcryptTest extends AbstractEncryptionMethodTest { + @Before + public void setUpSettings() { + WrapperMock.createInstance(); + Settings.bCryptLog2Rounds = 8; + } + public BcryptTest() { super(new BCRYPT(), "$2a$10$6iATmYgwJVc3YONhVcZFve3Cfb5GnwvKhJ20r.hMjmcNkIT9.Uh9K", // password diff --git a/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java new file mode 100644 index 00000000..fa27de3b --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/CRAZYCRYPT1Test.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link CRAZYCRYPT1}. + */ +public class CRAZYCRYPT1Test extends AbstractEncryptionMethodTest { + + public CRAZYCRYPT1Test() { + super(new CRAZYCRYPT1(), + "d5c76eb36417d4e97ec62609619e40a9e549a2598d0dab5a7194fd997a9305af78de2b93f958e150d19dd1e7f821043379ddf5f9c7f352bf27df91ae4913f3e8", // password + "49c63f827c88196871e344e589bd46cc4fa6db3c27801bbad5374c0d216381977627c1d76f2114667d5dd117e046f7493eb06e4f461f4f848aa08f6f40a3e934", // PassWord1 + "6fefb0233bab6e6efb9c16f82cb0d8f569488905e2dae0e7c9dde700e7363da67213d37c44bc15f4a05854c9c21e5688389d416413c7309398aa96cb1f341d08", // &^%te$t?Pw@_ + "46f51cde7657fdec9848bad0fd8e7fb97783cf5335f94dbb5260899ab0b04022a52d651b1c45345328850178e7165308c8c213040b0864de66018a0b769d37cb"); // âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java b/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java index f2f22edf..afff6bf8 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/CryptPBKDF2DjangoTest.java @@ -1,12 +1,8 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link CryptPBKDF2Django}. */ -@Ignore -// TODO ljacqu 20151220: testPasswordEquality fails - password matches hash for uppercase password...? public class CryptPBKDF2DjangoTest extends AbstractEncryptionMethodTest { public CryptPBKDF2DjangoTest() { diff --git a/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java new file mode 100644 index 00000000..0752b984 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/IPB3Test.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link IPB3}. + */ +public class IPB3Test extends AbstractEncryptionMethodTest { + + public IPB3Test() { + super(new IPB3(), + new EncryptedPassword("f8ecea1ce42b5babef369ff7692dbe3f", "1715b"), //password + new EncryptedPassword("40a93731a931352e0619cdf09b975040", "ba91c"), //PassWord1 + new EncryptedPassword("a77ca982373946d5800430bd2947ba11", "a7725"), //&^%te$t?Pw@_ + new EncryptedPassword("383d7b9e2b707d6e894ec7b30e3032c3", "fa9fd")); //âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java new file mode 100644 index 00000000..01e3491d --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/MYBBTest.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link MYBB}. + */ +public class MYBBTest extends AbstractEncryptionMethodTest { + + public MYBBTest() { + super(new MYBB(), + new EncryptedPassword("57c7a16d860833db5030738f5a465d2b", "acdc14e6"), //password + new EncryptedPassword("08fbdf721f2c42d9780b7d66df0ba830", "792fd7fb"), //PassWord1 + new EncryptedPassword("d602f38fb59ad9e185d5604f5d4ddb36", "4b5534a4"), //&^%te$t?Pw@_ + new EncryptedPassword("b3c39410d0ab8ae2a65c257820797fad", "e5a6cb14")); //âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java index 761fe7bb..0c9d67cf 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/Md5Test.java @@ -7,11 +7,10 @@ public class Md5Test extends AbstractEncryptionMethodTest { public Md5Test() { super(new MD5(), - "5f4dcc3b5aa765d61d8327deb882cf99", // password - "f2126d405f46ed603ff5b2950f062c96", // PassWord1 - "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ - "d1accd961cb7b688c87278191c1dfed3" // âË_3(íù* - ); + "5f4dcc3b5aa765d61d8327deb882cf99", // password + "f2126d405f46ed603ff5b2950f062c96", // PassWord1 + "0833dcd2bc741f90c46bbac5498fd08f", // &^%te$t?Pw@_ + "d1accd961cb7b688c87278191c1dfed3"); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java new file mode 100644 index 00000000..66641470 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/PHPFUSIONTest.java @@ -0,0 +1,16 @@ +package fr.xephi.authme.security.crypts; + +/** + * Test for {@link PHPFUSION}. + */ +public class PHPFUSIONTest extends AbstractEncryptionMethodTest { + + public PHPFUSIONTest() { + super(new PHPFUSION(), + new EncryptedPassword("f7a606c4eb3fcfbc382906476e05b06f21234a77d1a4eacc0f93f503deb69e70", "6cd1c97c55cb"), // password + new EncryptedPassword("8a9b7bb706a3347e5f684a7cb905bfb26b9a0d099358064139ab3ed1a66aeb2b", "d6012370b73f"), // PassWord1 + new EncryptedPassword("43f2f23f44c8f89e2dbf06050bc8c77dbcdf71a7b5d28c87ec657d474e63d62d", "f75400a209a4"), // &^%te$t?Pw@_ + new EncryptedPassword("4e7f4eb7e3653d7460f1cf590def4153c6fcdf8b8e16fb95538fdf9e54a95245", "d552e0f5b23a")); // âË_3(íù* + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java new file mode 100644 index 00000000..56f01de3 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTED2MD5Test.java @@ -0,0 +1,26 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.settings.Settings; +import fr.xephi.authme.util.WrapperMock; +import org.junit.Before; + +/** + * Test for {@link SALTED2MD5}. + */ +public class SALTED2MD5Test extends AbstractEncryptionMethodTest { + + @Before + public void setUpAlgorithm() { + WrapperMock.createInstance(); + Settings.saltLength = 8; + } + + public SALTED2MD5Test() { + super(new SALTED2MD5(), + new EncryptedPassword("9f3d13dc01a6fe61fd669954174399f3", "9b5f5749"), // password + new EncryptedPassword("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"), // PassWord1 + new EncryptedPassword("38dcb83cc68424afe3cda012700c2bb1", "eb2c3394"), // &^%te$t?Pw@_ + new EncryptedPassword("ad25606eae5b760c8a2469d65578ac39", "04eee598")); // âË_3(íù*) + } + +} diff --git a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java index 851d8b85..f2976cc6 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/SALTEDSHA512Test.java @@ -1,20 +1,16 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link SALTEDSHA512}. */ -@Ignore -// TODO ljacqu 20151220: Currently cannot test because of closely coupled database call inside of class public class SALTEDSHA512Test extends AbstractEncryptionMethodTest { public SALTEDSHA512Test() { super(new SALTEDSHA512(), - "c8efe95e1ab02d9a0e7c7d11d4ac3cc068a8405b5810aac3a1b8b01927ab059563438131dc995156739daf74db40ffdc79b78f6aec9b2a468fe106b88c66c204", // password - "74c61af1bcbb3293cdc0959c7323d50be28c167eddc7a1b7eb029e38263c2cfb6eb090f41370a65249752aa316fa851091c2bd8420302e87d383529beea735b4", // PassWord1 - "08eefcca4a17876441ebe61a02e8bc62cab7502dd87f8ec3b7f82edb2adace791b8dad31e74c5513cf99be502b732f5c5efffb239f4590d5c600d066a7037908", // &^%te$t?Pw@_ - "a122490c4c7c18ad665b5ac9617c948741468a787a2ba42c6fd2530ea1d7874681b8575ee9a8907c42ff65dac69e4ada2852789759c17d51865ca915b259a65a"); // âË_3(íù* + new EncryptedPassword("dea7a37cecf5384ae8e347fd1411efb51364b6ba1b328695de3b354612c1d7010807e8b7051c40f740e498490e1f133e2c2408327d13fbdd68e1b1f6d548e624", "29f8a3c52147f987fee7ba3e0fb311bd"), // password + new EncryptedPassword("7c06225aac574d2dc7c81a2ed306637adf025715f52083e05bdab014faaa234e24a97d0e69ea0108dfa77cc9228e58be319ee677e679b5d1ad168d40e50a42f6", "8ea37b85d020b98f60c0fe9b8ec9296c"), // PassWord1 + new EncryptedPassword("55711adbe03c9616f3505f0d57077fdd528c32243eb6f9840c1a6ff9e553940d6b89790750ebd52ebda63ca793fbe9980d54057af40836820c648750fe22d49c", "9f58079631ef21d32b4710694f1f461b"), // &^%te$t?Pw@_ + new EncryptedPassword("29dc5be8702975ea4563ed3de5b145e2d2f1c37ae661bbe0d3e94d964402cf09d539d65f3b90ff6921ea3d40727f76fb38fb34d1e5c2d62238c4e0203efc372f", "048bb76168265d906f1fd1f81d0616a9")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java index 99474f76..2a5d685f 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WBB3Test.java @@ -1,20 +1,16 @@ package fr.xephi.authme.security.crypts; -import org.junit.Ignore; - /** * Test for {@link WBB3}. */ -@Ignore -// TODO #364 ljacqu 20151220: Unignore test after fixing closely coupled DB dependency public class WBB3Test extends AbstractEncryptionMethodTest { public WBB3Test() { super(new WBB3(), - "ca426c4d20a82cd24c7bb07d94d69f3757e3d07d", // password - "72d59d27674a3cace2600ff152ba8b46274e27e9", // PassWord1 - "23daf26602e52591156968a14c2a6592b5be4743", // &^%te$t?Pw@_ - "d3908efe4a15314066391dd8572883c70b16fd8a"); // âË_3(íù* + new EncryptedPassword("8df818ef7d56075ab2744f74b98ad68a375ccac4", "b7415b355492ea60314f259a35733a3092c03e3f"), // password + new EncryptedPassword("106da5cf5df92cb845e12cf62cbdb5235b6dc693", "6110f19b2b52910dccf592a19c59126873f42e69"), // PassWord1 + new EncryptedPassword("940a9fb7acec0178c6691e8b3c14bd7d789078b1", "f9dd501ff3d1bf74904f9e89649e378429af56e7"), // &^%te$t?Pw@_ + new EncryptedPassword("0fa12e8d96c9e95f73aa91f3b76f8cdc815ec8a5", "736be8669f6159ddb2d5b47a3e6428cdb8b324de")); // âË_3(íù* } } diff --git a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java index cf762412..49a16d65 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/WORDPRESSTest.java @@ -12,4 +12,10 @@ public class WORDPRESSTest extends AbstractEncryptionMethodTest { "$P$BjzPjjzPjrAOyB1V0WFdpisgCTFx.N/", // &^%te$t?Pw@_ "$P$BjzPjxxyjp2QdKcab/oTW8l/W0AgE21"); // âË_3(íù* } + + @Override + protected boolean testHashEqualityForSameSalt() { + // We need to skip the test because Wordpress uses an "internal salt" that is not exposed to the outside + return false; + } }