diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java index cfb17c06f..e573c6d04 100644 --- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java @@ -10,6 +10,7 @@ import com.google.common.cache.RemovalNotification; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; +import fr.xephi.authme.security.crypts.HashedPassword; import java.util.ArrayList; import java.util.List; @@ -57,6 +58,25 @@ public class CacheDataSource implements DataSource { return getAuth(user) != null; } + @Override + public HashedPassword getPassword(String user) { + user = user.toLowerCase(); + Optional pAuthOpt = cachedAuths.getIfPresent(user); + if (pAuthOpt != null && pAuthOpt.isPresent()) { + return pAuthOpt.get().getPassword(); + } + return source.getPassword(user); + } + + /** + * Method getAuth. + * + * @param user String + * + * @return PlayerAuth + * + * @see fr.xephi.authme.datasource.DataSource#getAuth(String) + */ @Override public synchronized PlayerAuth getAuth(String user) { user = user.toLowerCase(); @@ -81,6 +101,16 @@ public class CacheDataSource implements DataSource { return result; } + @Override + public boolean updatePassword(String user, HashedPassword password) { + user = user.toLowerCase(); + boolean result = source.updatePassword(user, password); + if (result) { + cachedAuths.refresh(user); + } + return result; + } + @Override public boolean updateSession(PlayerAuth auth) { boolean result = source.updateSession(auth); diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java index bee64eb5a..2f466defc 100644 --- a/src/main/java/fr/xephi/authme/datasource/DataSource.java +++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java @@ -1,8 +1,9 @@ package fr.xephi.authme.datasource; -import java.util.List; - import fr.xephi.authme.cache.auth.PlayerAuth; +import fr.xephi.authme.security.crypts.HashedPassword; + +import java.util.List; /** */ @@ -17,6 +18,15 @@ public interface DataSource { */ boolean isAuthAvailable(String user); + /** + * Method getPassword. + * + * @param user String + * + * @return String + */ + HashedPassword getPassword(String user); + /** * Method getAuth. * @@ -53,6 +63,8 @@ public interface DataSource { */ boolean updatePassword(PlayerAuth auth); + boolean updatePassword(String user, HashedPassword password); + /** * Method purgeDatabase. * diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java index 3ba57424a..4d7b4b38e 100644 --- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java +++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java @@ -1,5 +1,12 @@ package fr.xephi.authme.datasource; +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.crypts.HashedPassword; +import fr.xephi.authme.settings.Settings; + import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -10,12 +17,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -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.settings.Settings; - /** */ @Deprecated @@ -87,6 +88,15 @@ public class FlatFile implements DataSource { return false; } + @Override + public HashedPassword getPassword(String user) { + PlayerAuth auth = getAuth(user); + if (auth != null) { + return auth.getPassword(); + } + return null; + } + /** * Method saveAuth. * @@ -126,7 +136,13 @@ public class FlatFile implements DataSource { */ @Override public synchronized boolean updatePassword(PlayerAuth auth) { - if (!isAuthAvailable(auth.getNickname())) { + return updatePassword(auth.getNickname(), auth.getPassword()); + } + + @Override + public boolean updatePassword(String user, HashedPassword password) { + user = user.toLowerCase(); + if (!isAuthAvailable(user)) { return false; } PlayerAuth newAuth = null; @@ -136,27 +152,27 @@ public class FlatFile implements DataSource { String line; while ((line = br.readLine()) != null) { String[] args = line.split(":"); - if (args[0].equals(auth.getNickname())) { + if (args[0].equals(user)) { // 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.getPassword().getHash(), args[2], Long.parseLong(args[3]), 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], password.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.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]); + newAuth = new PlayerAuth(args[0], password.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.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]); + newAuth = new PlayerAuth(args[0], password.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.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]); + newAuth = new PlayerAuth(args[0], password.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.getPassword().getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); + newAuth = new PlayerAuth(args[0], password.getHash(), args[2], 0, 0, 0, 0, "world", "your@email.com", args[0]); break; } } @@ -178,7 +194,7 @@ public class FlatFile implements DataSource { } } if (newAuth != null) { - removeAuth(auth.getNickname()); + removeAuth(user); saveAuth(newAuth); } return true; diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java index 05e1836eb..fd11d9ea8 100644 --- a/src/main/java/fr/xephi/authme/datasource/MySQL.java +++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java @@ -6,10 +6,18 @@ import fr.xephi.authme.AuthMe; import fr.xephi.authme.ConsoleLogger; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.security.HashAlgorithm; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.security.crypts.XFBCRYPT; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; -import java.sql.*; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; import java.util.ArrayList; import java.util.List; @@ -244,6 +252,25 @@ public class MySQL implements DataSource { return false; } + @Override + public HashedPassword getPassword(String user) { + try (Connection con = getConnection()) { + String sql = "SELECT " + columnPassword + "," + columnSalt + " FROM " + tableName + + " WHERE " + columnName + "=?;"; + PreparedStatement pst = con.prepareStatement(sql); + pst.setString(1, user.toLowerCase()); + ResultSet rs = pst.executeQuery(); + if (rs.next()) { + return new HashedPassword(rs.getString(columnPassword), + !columnSalt.isEmpty() ? rs.getString(columnSalt) : null); + } + } catch (SQLException ex) { + ConsoleLogger.showError(ex.getMessage()); + ConsoleLogger.writeStackTrace(ex); + } + return null; + } + @Override public synchronized PlayerAuth getAuth(String user) { PlayerAuth pAuth; @@ -257,6 +284,7 @@ public class MySQL implements DataSource { } String salt = !columnSalt.isEmpty() ? rs.getString(columnSalt) : null; int group = !columnGroup.isEmpty() ? rs.getInt(columnGroup) : -1; + int id = rs.getInt(columnID); pAuth = PlayerAuth.builder() .name(rs.getString(columnName)) .realName(rs.getString(columnRealName)) @@ -272,6 +300,16 @@ public class MySQL implements DataSource { .build(); rs.close(); pst.close(); + if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + 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.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + } + } } catch (SQLException ex) { ConsoleLogger.showError(ex.getMessage()); ConsoleLogger.writeStackTrace(ex); @@ -452,6 +490,24 @@ public class MySQL implements DataSource { } rs.close(); pst.close(); + } else if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + 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); + 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.getPassword().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) { @@ -463,6 +519,12 @@ public class MySQL implements DataSource { @Override public synchronized boolean updatePassword(PlayerAuth auth) { + return updatePassword(auth.getNickname(), auth.getPassword()); + } + + @Override + public boolean updatePassword(String user, HashedPassword password) { + user = user.toLowerCase(); try (Connection con = getConnection()) { boolean useSalt = !columnSalt.isEmpty(); PreparedStatement pst; @@ -470,18 +532,46 @@ public class MySQL implements DataSource { 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()); + pst.setString(1, password.getHash()); + pst.setString(2, password.getSalt()); + pst.setString(3, user); } 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.setString(1, password.getHash()); + pst.setString(2, user); } pst.executeUpdate(); pst.close(); + if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + String 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); + // Insert password in the correct table + sql = "UPDATE xf_user_authenticate SET data=? WHERE " + columnID + "=?;"; + PreparedStatement pst2 = con.prepareStatement(sql); + byte[] bytes = password.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()); @@ -549,7 +639,24 @@ public class MySQL implements DataSource { public synchronized boolean removeAuth(String user) { user = user.toLowerCase(); try (Connection con = getConnection()) { - PreparedStatement pst = con.prepareStatement("DELETE FROM " + tableName + " WHERE " + columnName + "=?;"); + String sql; + PreparedStatement pst; + if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + 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 + "=?;"); pst.setString(1, user); pst.executeUpdate(); return true; @@ -679,7 +786,7 @@ public class MySQL implements DataSource { } @Override - public synchronized List getAllAuthsByEmail(String email){ + public synchronized List getAllAuthsByEmail(String email) { List countEmail = new ArrayList<>(); try (Connection con = getConnection()) { String sql = "SELECT " + columnName + " FROM " + tableName + " WHERE " + columnEmail + "=?;"; @@ -835,6 +942,17 @@ public class MySQL implements DataSource { .groupId(group) .build(); + if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + 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.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(bytes))); + } + rs2.close(); + } auths.add(pAuth); } pst.close(); @@ -871,6 +989,17 @@ public class MySQL implements DataSource { .groupId(group) .build(); + if (Settings.getPasswordHash == HashAlgorithm.XFBCRYPT) { + 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.setPassword(new HashedPassword(XFBCRYPT.getHashFromBlob(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 a04bb7f91..876bbfa18 100644 --- a/src/main/java/fr/xephi/authme/datasource/SQLite.java +++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java @@ -156,6 +156,36 @@ public class SQLite implements DataSource { } } + @Override + public HashedPassword getPassword(String user) { + PreparedStatement pst = null; + ResultSet rs = null; + try { + pst = con.prepareStatement("SELECT " + columnPassword + "," + columnSalt + + " FROM " + tableName + " WHERE " + columnName + "=?"); + pst.setString(1, user); + rs = pst.executeQuery(); + if (rs.next()) { + return new HashedPassword(rs.getString(columnPassword), + !columnSalt.isEmpty() ? rs.getString(columnSalt) : null); + } + } catch (SQLException ex) { + ConsoleLogger.showError(ex.getMessage()); + ConsoleLogger.writeStackTrace(ex); + } finally { + close(rs); + close(pst); + } + return null; + } + + /** + * Method getAuth. + * + * @param user String + * + * @return PlayerAuth * @see fr.xephi.authme.datasource.DataSource#getAuth(String) + */ @Override public synchronized PlayerAuth getAuth(String user) { PreparedStatement pst = null; @@ -219,9 +249,14 @@ public class SQLite implements DataSource { @Override public synchronized boolean updatePassword(PlayerAuth auth) { + return updatePassword(auth.getNickname(), auth.getPassword()); + } + + @Override + public boolean updatePassword(String user, HashedPassword password) { + user = user.toLowerCase(); PreparedStatement pst = null; try { - HashedPassword password = auth.getPassword(); boolean useSalt = !columnSalt.isEmpty(); String sql = "UPDATE " + tableName + " SET " + columnPassword + " = ?" + (useSalt ? ", " + columnSalt + " = ?" : "") @@ -230,9 +265,9 @@ public class SQLite implements DataSource { pst.setString(1, password.getHash()); if (useSalt) { pst.setString(2, password.getSalt()); - pst.setString(3, auth.getNickname()); + pst.setString(3, user); } else { - pst.setString(2, auth.getNickname()); + pst.setString(2, user); } pst.executeUpdate(); } catch (SQLException ex) { diff --git a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java index 469a02585..389ad485f 100644 --- a/src/main/java/fr/xephi/authme/security/HashAlgorithm.java +++ b/src/main/java/fr/xephi/authme/security/HashAlgorithm.java @@ -35,6 +35,7 @@ public enum HashAlgorithm { WHIRLPOOL(fr.xephi.authme.security.crypts.WHIRLPOOL.class), WORDPRESS(fr.xephi.authme.security.crypts.WORDPRESS.class), XAUTH(fr.xephi.authme.security.crypts.XAUTH.class), + XFBCRYPT(fr.xephi.authme.security.crypts.XFBCRYPT.class), CUSTOM(null); private final Class clazz; diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java index 56ed1e4cd..552b64dd2 100644 --- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java +++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java @@ -1,10 +1,9 @@ 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.HashedPassword; import fr.xephi.authme.security.crypts.EncryptionMethod; +import fr.xephi.authme.security.crypts.HashedPassword; import org.bukkit.plugin.PluginManager; /** @@ -36,12 +35,8 @@ public class PasswordSecurity { } 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; + HashedPassword auth = dataSource.getPassword(playerName); + return auth != null && comparePassword(password, auth, playerName); } public boolean comparePassword(String password, HashedPassword hashedPassword, String playerName) { @@ -63,9 +58,10 @@ public class PasswordSecurity { * 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 password The clear-text password to check * @param hashedPassword The encrypted password to test the clear-text password against - * @param playerName The name of the player + * @param playerName The name of the player + * * @return True if the */ private boolean compareWithAllEncryptionMethods(String password, HashedPassword hashedPassword, @@ -87,8 +83,9 @@ public class PasswordSecurity { * {@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 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) { @@ -102,6 +99,7 @@ public class PasswordSecurity { * 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) { @@ -116,13 +114,9 @@ public class PasswordSecurity { } private void hashPasswordForNewAlgorithm(String password, String playerName) { - PlayerAuth auth = dataSource.getAuth(playerName); - if (auth != null) { - HashedPassword hashedPassword = initializeEncryptionMethod(algorithm, playerName) - .computeHash(password, playerName); - auth.setPassword(hashedPassword); - dataSource.updatePassword(auth); - } + HashedPassword hashedPassword = initializeEncryptionMethod(algorithm, playerName) + .computeHash(password, playerName); + dataSource.updatePassword(playerName, hashedPassword); } } 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 850a6f017..a8ea653bb 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java +++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java @@ -13,17 +13,18 @@ // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. package fr.xephi.authme.security.crypts; -import java.io.UnsupportedEncodingException; -import java.security.SecureRandom; - import fr.xephi.authme.ConsoleLogger; 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.security.crypts.description.Usage; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.StringUtils; +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; + /** *

* BCrypt implements OpenBSD-style Blowfish password hashing using the scheme @@ -64,6 +65,7 @@ import fr.xephi.authme.util.StringUtils; * is twice as much work. The default log_rounds is 10, and the valid range is 4 * to 31. *

+ * * @author Damien Miller * @version 0.2 */ @@ -103,7 +105,9 @@ public class BCRYPT implements EncryptionMethod { * @param d the byte array to encode * @param len the number of bytes to encode * - * @return base64-encoded string * @throws IllegalArgumentException if the length is invalid * @throws IllegalArgumentException + * @return base64-encoded string + * + * @throws IllegalArgumentException if the length is invalid */ private static String encode_base64(byte d[], int len) throws IllegalArgumentException { @@ -161,6 +165,7 @@ public class BCRYPT implements EncryptionMethod { * @param maxolen the maximum number of bytes to decode * * @return an array containing the decoded bytes + * * @throws IllegalArgumentException if maxolen is invalid */ private static byte[] decode_base64(String s, int maxolen) diff --git a/src/main/java/fr/xephi/authme/security/crypts/HashedPassword.java b/src/main/java/fr/xephi/authme/security/crypts/HashedPassword.java index ab1a7eebc..f2e40aa4e 100644 --- a/src/main/java/fr/xephi/authme/security/crypts/HashedPassword.java +++ b/src/main/java/fr/xephi/authme/security/crypts/HashedPassword.java @@ -4,7 +4,7 @@ package fr.xephi.authme.security.crypts; * The result of a hash computation. See {@link #salt} for details. */ public class HashedPassword { - + /** The generated hash. */ private final String hash; /** @@ -36,13 +36,13 @@ public class HashedPassword { public HashedPassword(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/XFBCRYPT.java b/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java new file mode 100644 index 000000000..6666a076c --- /dev/null +++ b/src/main/java/fr/xephi/authme/security/crypts/XFBCRYPT.java @@ -0,0 +1,22 @@ +package fr.xephi.authme.security.crypts; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class XFBCRYPT extends BCRYPT { + private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\""); + + @Override + public String generateSalt() { + return BCRYPT.gensalt(); + } + + public static String getHashFromBlob(byte[] blob) { + String line = new String(blob); + Matcher m = HASH_PATTERN.matcher(line); + if (m.find()) { + return m.group(1); + } + return "*"; // what? + } +} diff --git a/src/main/java/fr/xephi/authme/settings/Settings.java b/src/main/java/fr/xephi/authme/settings/Settings.java index cfb28f75e..4adfe15bd 100644 --- a/src/main/java/fr/xephi/authme/settings/Settings.java +++ b/src/main/java/fr/xephi/authme/settings/Settings.java @@ -515,11 +515,6 @@ public final class Settings { set("Xenoforo.predefinedSalt", null); changes = true; } - if (configFile.getString("settings.security.passwordHash", "SHA256").toUpperCase().equals("XFSHA1") || - configFile.getString("settings.security.passwordHash", "SHA256").toUpperCase().equals("XFSHA256")) { - set("settings.security.passwordHash", "XENFORO"); - changes = true; - } if (!contains("Protection.enableProtection")) { set("Protection.enableProtection", false); changes = true; @@ -528,10 +523,6 @@ public final class Settings { set("settings.restrictions.removeSpeed", true); changes = true; } - if (!contains("DataSource.mySQLMaxConections")) { - set("DataSource.mySQLMaxConections", 25); - changes = true; - } if (!contains("Protection.countries")) { countries = new ArrayList<>(); countries.add("US"); @@ -741,6 +732,7 @@ public final class Settings { } if (changes) { + save(); plugin.getLogger().warning("Merged new Config Options - I'm not an error, please don't report me"); plugin.getLogger().warning("Please check your config.yml file for new configs!"); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index a98d97393..7f151c495 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, SALTED2MD5, JOOMLA, BCRYPT, WBB3, SHA512, + # MYBB, IPB3, PHPFUSION, SMF, XENFORO, 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/security/PasswordSecurityTest.java b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java index 8c1edffd5..d36810ab6 100644 --- a/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java +++ b/src/test/java/fr/xephi/authme/security/PasswordSecurityTest.java @@ -10,6 +10,7 @@ import fr.xephi.authme.security.crypts.PHPBB; import org.bukkit.event.Event; import org.bukkit.plugin.PluginManager; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.invocation.InvocationOnMock; @@ -70,9 +71,7 @@ public class PasswordSecurityTest { String playerLowerCase = playerName.toLowerCase(); String clearTextPass = "myPassTest"; - PlayerAuth auth = mock(PlayerAuth.class); - given(auth.getPassword()).willReturn(password); - given(dataSource.getAuth(playerName)).willReturn(auth); + given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(true); PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.BCRYPT, pluginManager, false); @@ -81,7 +80,7 @@ public class PasswordSecurityTest { // then assertThat(result, equalTo(true)); - verify(dataSource).getAuth(playerName); + verify(dataSource).getPassword(playerName); verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); verify(method).comparePassword(clearTextPass, password, playerLowerCase); } @@ -95,8 +94,7 @@ public class PasswordSecurityTest { String clearTextPass = "passw0Rd1"; PlayerAuth auth = mock(PlayerAuth.class); - given(auth.getPassword()).willReturn(password); - given(dataSource.getAuth(playerName)).willReturn(auth); + given(dataSource.getPassword(playerName)).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.CUSTOM, pluginManager, false); @@ -105,7 +103,7 @@ public class PasswordSecurityTest { // then assertThat(result, equalTo(false)); - verify(dataSource).getAuth(playerName); + verify(dataSource).getPassword(playerName); verify(pluginManager).callEvent(any(PasswordEncryptionEvent.class)); verify(method).comparePassword(clearTextPass, password, playerLowerCase); } @@ -116,7 +114,7 @@ public class PasswordSecurityTest { String playerName = "bobby"; String clearTextPass = "tables"; - given(dataSource.getAuth(playerName)).willReturn(null); + given(dataSource.getPassword(playerName)).willReturn(null); PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, false); // when @@ -124,7 +122,7 @@ public class PasswordSecurityTest { // then assertThat(result, equalTo(false)); - verify(dataSource).getAuth(playerName); + verify(dataSource).getPassword(playerName); verify(pluginManager, never()).callEvent(any(Event.class)); verify(method, never()).comparePassword(anyString(), any(HashedPassword.class), anyString()); } @@ -141,11 +139,7 @@ public class PasswordSecurityTest { // MD5 hash for "Test" HashedPassword newPassword = new HashedPassword("0cbc6611f5540bd0809a388dc95a615b"); - PlayerAuth auth = mock(PlayerAuth.class); - doCallRealMethod().when(auth).getPassword(); - doCallRealMethod().when(auth).setPassword(any(HashedPassword.class)); - auth.setPassword(password); - given(dataSource.getAuth(argThat(equalToIgnoringCase(playerName)))).willReturn(auth); + given(dataSource.getPassword(argThat(equalToIgnoringCase(playerName)))).willReturn(password); given(method.comparePassword(clearTextPass, password, playerLowerCase)).willReturn(false); given(method.computeHash(clearTextPass, playerLowerCase)).willReturn(newPassword); PasswordSecurity security = new PasswordSecurity(dataSource, HashAlgorithm.MD5, pluginManager, true); @@ -158,14 +152,10 @@ public class PasswordSecurityTest { // Note ljacqu 20151230: We need to check the player name in a case-insensitive way because the methods within // PasswordSecurity may convert the name into all lower-case. This is desired because EncryptionMethod methods // should only be invoked with all lower-case names. Data source is case-insensitive itself, so this is fine. - verify(dataSource, times(2)).getAuth(argThat(equalToIgnoringCase(playerName))); + verify(dataSource).getPassword(argThat(equalToIgnoringCase(playerName))); verify(pluginManager, times(2)).callEvent(any(PasswordEncryptionEvent.class)); verify(method).comparePassword(clearTextPass, password, playerLowerCase); - verify(auth).setPassword(newPassword); - - ArgumentCaptor captor = ArgumentCaptor.forClass(PlayerAuth.class); - verify(dataSource).updatePassword(captor.capture()); - assertThat(captor.getValue().getPassword(), equalTo(newPassword)); + verify(dataSource).updatePassword(playerLowerCase, newPassword); } @Test 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 65ea3b68c..106689ae7 100644 --- a/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java +++ b/src/test/java/fr/xephi/authme/security/crypts/BcryptTest.java @@ -2,15 +2,15 @@ package fr.xephi.authme.security.crypts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.util.WrapperMock; -import org.junit.Before; +import org.junit.BeforeClass; /** * Test for {@link BCRYPT}. */ public class BcryptTest extends AbstractEncryptionMethodTest { - @Before - public void setUpSettings() { + @BeforeClass + public static void setUpSettings() { WrapperMock.createInstance(); Settings.bCryptLog2Rounds = 8; } diff --git a/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java new file mode 100644 index 000000000..3b7294a04 --- /dev/null +++ b/src/test/java/fr/xephi/authme/security/crypts/XFBCRYPTTest.java @@ -0,0 +1,23 @@ +package fr.xephi.authme.security.crypts; + +import fr.xephi.authme.util.WrapperMock; +import org.junit.BeforeClass; + +/** + * Test for {@link XFBCRYPT}. + */ +public class XFBCRYPTTest extends AbstractEncryptionMethodTest { + + @BeforeClass + public static void setUpWrapper() { + WrapperMock.createInstance(); + } + + public XFBCRYPTTest() { + super(new XFBCRYPT(), + "$2a$10$UtuON/ZG.x8EWG/zQbryB.BHfQVrfxk3H7qykzP.UJQ8YiLjZyfqq", // password + "$2a$10$Q.ocUo.YtHTdI4nu3pcpKun6BILcmWHm541ANULucmuU/ps1QKY4K", // PassWord1 + "$2a$10$yHjm02.K4HP5iFU1F..yLeTeo7PWZVbKAr/QGex5jU4.J3mdq/uuO", // &^%te$t?Pw@_ + "$2a$10$joIayhGStExKWxNbiqMMPOYFSpQ76HVNjpOB7.QwTmG5q.TiJJ.0e"); // âË_3(íù* + } +}