From bd5d341e67a6f8ef222d0c25d17da8c05954fd20 Mon Sep 17 00:00:00 2001
From: DNx5
Date: Thu, 31 Dec 2015 11:05:18 +0700
Subject: [PATCH] Xenforo support. - Added getPassword method in DataSource and
all implementations.
---
.../authme/datasource/CacheDataSource.java | 11 +++
.../xephi/authme/datasource/DataSource.java | 10 +++
.../fr/xephi/authme/datasource/FlatFile.java | 10 +++
.../fr/xephi/authme/datasource/MySQL.java | 43 +++++++++---
.../fr/xephi/authme/datasource/SQLite.java | 30 +++++++-
.../authme/security/PasswordSecurity.java | 8 +--
.../xephi/authme/security/crypts/BCRYPT.java | 40 ++++++-----
.../security/crypts/HashedPassword.java | 8 +--
.../fr/xephi/authme/security/crypts/XF.java | 70 +++----------------
9 files changed, 130 insertions(+), 100 deletions(-)
diff --git a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
index 2351c6cb..2887b9ee 100644
--- a/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
+++ b/src/main/java/fr/xephi/authme/datasource/CacheDataSource.java
@@ -9,6 +9,7 @@ import com.google.common.cache.RemovalListeners;
import com.google.common.cache.RemovalNotification;
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;
@@ -65,6 +66,16 @@ 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.
*
diff --git a/src/main/java/fr/xephi/authme/datasource/DataSource.java b/src/main/java/fr/xephi/authme/datasource/DataSource.java
index 10f74b92..9e5f051a 100644
--- a/src/main/java/fr/xephi/authme/datasource/DataSource.java
+++ b/src/main/java/fr/xephi/authme/datasource/DataSource.java
@@ -1,6 +1,7 @@
package fr.xephi.authme.datasource;
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.
*
diff --git a/src/main/java/fr/xephi/authme/datasource/FlatFile.java b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
index 55646ddc..da18625b 100644
--- a/src/main/java/fr/xephi/authme/datasource/FlatFile.java
+++ b/src/main/java/fr/xephi/authme/datasource/FlatFile.java
@@ -14,6 +14,7 @@ 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;
/**
@@ -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.
*
diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index e0c91955..e3ce9b5f 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.XF;
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;
@@ -280,8 +307,7 @@ public class MySQL implements DataSource {
if (rs.next()) {
Blob blob = rs.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- // TODO #137: Need to find out how the salt is loaded and need to pass it along to setHash()
- // pAuth.setPassword(new String(bytes));
+ pAuth.setPassword(new HashedPassword(XF.getHashFromBlob(bytes)));
}
}
} catch (SQLException ex) {
@@ -470,11 +496,9 @@ public class MySQL implements DataSource {
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");
- // TODO #137: Need to verify that the salt info is also being passed on...
byte[] bytes = auth.getPassword().getHash().getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
@@ -524,7 +548,6 @@ public class MySQL implements DataSource {
// Insert password in the correct table
sql = "UPDATE xf_user_authenticate SET data=? WHERE " + columnID + "=?;";
PreparedStatement pst2 = con.prepareStatement(sql);
- // TODO #137: What about the salt?
byte[] bytes = auth.getPassword().getHash().getBytes();
Blob blob = con.createBlob();
blob.setBytes(1, bytes);
@@ -757,7 +780,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 + "=?;";
@@ -920,8 +943,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- // TODO #137: Need to pass the hash and the salt here
- // pAuth.setPassword(new String(bytes));
+ pAuth.setPassword(new HashedPassword(XF.getHashFromBlob(bytes)));
}
rs2.close();
}
@@ -968,8 +990,7 @@ public class MySQL implements DataSource {
if (rs2.next()) {
Blob blob = rs2.getBlob("data");
byte[] bytes = blob.getBytes(1, (int) blob.length());
- // TODO #137: Need to pass the hash and the salt here
- // pAuth.setHash(new String(bytes));
+ pAuth.setPassword(new HashedPassword(XF.getHashFromBlob(bytes)));
}
rs2.close();
}
diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java
index 474c5035..1eef222f 100644
--- a/src/main/java/fr/xephi/authme/datasource/SQLite.java
+++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java
@@ -6,7 +6,12 @@ import fr.xephi.authme.security.crypts.HashedPassword;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.util.StringUtils;
-import java.sql.*;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
@@ -167,6 +172,29 @@ 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.
*
diff --git a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
index 56ed1e4c..75a1fcdc 100644
--- a/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
+++ b/src/main/java/fr/xephi/authme/security/PasswordSecurity.java
@@ -36,12 +36,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) {
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 b7602e7f..eb8c717a 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/BCRYPT.java
@@ -15,9 +15,9 @@ package fr.xephi.authme.security.crypts;
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;
@@ -25,43 +25,45 @@ import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
/**
+ *
* BCrypt implements OpenBSD-style Blowfish password hashing using the scheme
* described in "A Future-Adaptable Password Scheme" by Niels Provos and David
* Mazieres.
- *
+ *
* This password hashing system tries to thwart off-line password cracking using
* a computationally-intensive hashing algorithm, based on Bruce Schneier's
* Blowfish cipher. The work factor of the algorithm is parameterised, so it can
* be increased as computers get faster.
- *
+ *
* Usage is really simple. To hash a password for the first time, call the
* hashpw method with a random salt, like this:
- *
+ *
*
- * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
+ * String pw_hash = BCrypt.hashpw(plain_password, BCrypt.gensalt());
*
- *
+ *
* To check whether a plaintext password matches one that has been hashed
* previously, use the checkpw method:
- *
+ *
*
- * if (BCrypt.checkpw(candidate_password, stored_hash))
- * System.out.println("It matches");
- * else
- * System.out.println("It does not match");
+ * if (BCrypt.checkpw(candidate_password, stored_hash))
+ * System.out.println("It matches");
+ * else
+ * System.out.println("It does not match");
*
- *
+ *
* The gensalt() method takes an optional parameter (log_rounds) that determines
* the computational complexity of the hashing:
- *
+ *
*
- * String strong_salt = BCrypt.gensalt(10)
- * String stronger_salt = BCrypt.gensalt(12)
+ * String strong_salt = BCrypt.gensalt(10)
+ * String stronger_salt = BCrypt.gensalt(12)
*
- *
+ *
* The amount of work increases exponentially (2**log_rounds), so each increment
* 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
@@ -102,8 +104,11 @@ 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 {
int off = 0;
@@ -160,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 ab1a7eeb..f2e40aa4 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/XF.java b/src/main/java/fr/xephi/authme/security/crypts/XF.java
index 2a74d2fa..cd3bcf16 100644
--- a/src/main/java/fr/xephi/authme/security/crypts/XF.java
+++ b/src/main/java/fr/xephi/authme/security/crypts/XF.java
@@ -1,74 +1,22 @@
package fr.xephi.authme.security.crypts;
-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) {
- return getSha256(getSha256(password) + regmatch("\"salt\";.:..:\"(.*)\";.:.:\"hashFunc\"", salt));
- }
-
- @Override
- public HashedPassword computeHash(String password, String name) {
- String salt = generateSalt();
- return new HashedPassword(computeHash(password, salt, null), salt);
- }
-
- @Override
- public boolean comparePassword(String password, HashedPassword hashedPassword, String name) {
- // TODO #137: Write the comparePassword method. See commit 121d323 for what was here previously; it was
- // utter non-sense
- return false;
- }
-
- // TODO #137: If this method corresponds to HashUtils.sha256(), use it instead of this
- private String getSha256(String password) {
- MessageDigest md = null;
- try {
- md = MessageDigest.getInstance("SHA-256");
- } catch (NoSuchAlgorithmException e) {
- throw new RuntimeException(e);
- }
- 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();
- }
+public class XF extends BCRYPT {
+ private static final Pattern HASH_PATTERN = Pattern.compile("\"hash\";s.*\"(.*)?\"");
@Override
public String generateSalt() {
- // TODO #137: Find out what kind of salt format XF uses to generate new passwords
- return "";
+ return BCRYPT.gensalt();
}
- @Override
- public boolean hasSeparateSalt() {
- return true;
- }
-
- 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));
+ 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 allMatches.get(0);
+ return "*"; // what?
}
}