66import fr .xephi .authme .ConsoleLogger ;
77import fr .xephi .authme .datasource .DataSource ;
88import fr .xephi .authme .initialization .Reloadable ;
9- import fr .xephi .authme .output .ConsoleLoggerFactory ;
109import fr .xephi .authme .message .MessageKey ;
10+ import fr .xephi .authme .output .ConsoleLoggerFactory ;
1111import fr .xephi .authme .permission .PermissionsManager ;
1212import fr .xephi .authme .permission .PlayerStatePermission ;
13+ import fr .xephi .authme .security .HashUtils ;
1314import fr .xephi .authme .settings .Settings ;
1415import fr .xephi .authme .settings .properties .EmailSettings ;
1516import fr .xephi .authme .settings .properties .ProtectionSettings ;
2324
2425import javax .annotation .PostConstruct ;
2526import javax .inject .Inject ;
27+ import java .io .DataInputStream ;
28+ import java .net .HttpURLConnection ;
29+ import java .net .URL ;
30+ import java .util .Arrays ;
2631import java .util .Collection ;
2732import java .util .List ;
2833import java .util .Locale ;
3540 * Validation service.
3641 */
3742public class ValidationService implements Reloadable {
38-
43+
3944 private final ConsoleLogger logger = ConsoleLoggerFactory .get (ValidationService .class );
4045
4146 @ Inject
@@ -80,7 +85,16 @@ public ValidationResult validatePassword(String password, String username) {
8085 return new ValidationResult (MessageKey .INVALID_PASSWORD_LENGTH );
8186 } else if (settings .getProperty (SecuritySettings .UNSAFE_PASSWORDS ).contains (passLow )) {
8287 return new ValidationResult (MessageKey .PASSWORD_UNSAFE_ERROR );
88+ } else if (settings .getProperty (SecuritySettings .HAVE_I_BEEN_PWNED_CHECK )) {
89+ HaveIBeenPwnedResults results = validatePasswordHaveIBeenPwned (password );
90+
91+ if (results != null
92+ && results .isPwned ()
93+ && results .getPwnCount () > settings .getProperty (SecuritySettings .HAVE_I_BEEN_PWNED_LIMIT )) {
94+ return new ValidationResult (MessageKey .PASSWORD_PWNED_ERROR , String .valueOf (results .getPwnCount ()));
95+ }
8396 }
97+
8498 return new ValidationResult ();
8599 }
86100
@@ -103,7 +117,7 @@ public boolean validateEmail(String email) {
103117 * Queries the database whether the email is still free for registration, i.e. whether the given
104118 * command sender may use the email to register a new account (as defined by settings and permissions).
105119 *
106- * @param email the email to verify
120+ * @param email the email to verify
107121 * @param sender the command sender
108122 * @return true if the email may be used, false otherwise (registration threshold has been exceeded)
109123 */
@@ -178,7 +192,7 @@ public boolean fulfillsNameRestrictions(Player player) {
178192 * Whitelist has precedence over blacklist: if a whitelist is set, the value is rejected if not present
179193 * in the whitelist.
180194 *
181- * @param value the value to verify
195+ * @param value the value to verify
182196 * @param whitelist the whitelist property
183197 * @param blacklist the blacklist property
184198 * @return true if the value is admitted by the lists, false otherwise
@@ -222,6 +236,53 @@ private Multimap<String, String> loadNameRestrictions(Set<String> configuredRest
222236 return restrictions ;
223237 }
224238
239+ /**
240+ * Check haveibeenpwned.com for the given password.
241+ *
242+ * @param password password to check for
243+ * @return Results of the check
244+ */
245+ public HaveIBeenPwnedResults validatePasswordHaveIBeenPwned (String password ) {
246+ String hash = HashUtils .sha1 (password );
247+
248+ String hashPrefix = hash .substring (0 , 5 );
249+
250+ try {
251+ String url = String .format ("https://api.pwnedpasswords.com/range/%s" , hashPrefix );
252+ HttpURLConnection connection = (HttpURLConnection ) new URL (url ).openConnection ();
253+ connection .setRequestMethod ("GET" );
254+ connection .setRequestProperty ("User-Agent" , "AuthMeReloaded" );
255+ connection .setConnectTimeout (5000 );
256+ connection .setReadTimeout (5000 );
257+ connection .setDoOutput (true );
258+ connection .setDoInput (true );
259+ StringBuilder outStr = new StringBuilder ();
260+
261+ try (DataInputStream input = new DataInputStream (connection .getInputStream ())) {
262+ for (int c = input .read (); c != -1 ; c = input .read ())
263+ outStr .append ((char ) c );
264+ }
265+
266+ String [] hashes = outStr .toString ().split ("\n " );
267+ System .out .println (Arrays .toString (hashes ));
268+ for (String hashSuffix : hashes ) {
269+ String [] hashSuffixParts = hashSuffix .trim ().split (":" );
270+ System .out .println (Arrays .toString (hashSuffixParts ));
271+ if (hashSuffixParts [0 ].equalsIgnoreCase (hash .substring (5 ))) {
272+ System .out .println ("Found match" );
273+ System .out .println (hashSuffixParts [1 ]);
274+ return new HaveIBeenPwnedResults (true , Integer .parseInt (hashSuffixParts [1 ]));
275+ }
276+ }
277+
278+ return new HaveIBeenPwnedResults (false , 0 );
279+ } catch (Exception e ) {
280+ e .printStackTrace ();
281+ }
282+
283+ return null ;
284+ }
285+
225286 public static final class ValidationResult {
226287 private final MessageKey messageKey ;
227288 private final String [] args ;
@@ -238,7 +299,7 @@ public ValidationResult() {
238299 * Constructor for a failed validation.
239300 *
240301 * @param messageKey message key of the validation error
241- * @param args arguments for the message key
302+ * @param args arguments for the message key
242303 */
243304 public ValidationResult (MessageKey messageKey , String ... args ) {
244305 this .messageKey = messageKey ;
@@ -262,4 +323,22 @@ public String[] getArgs() {
262323 return args ;
263324 }
264325 }
326+
327+ public static final class HaveIBeenPwnedResults {
328+ private final boolean isPwned ;
329+ private final int pwnCount ;
330+
331+ public HaveIBeenPwnedResults (boolean isPwned , int pwnCount ) {
332+ this .isPwned = isPwned ;
333+ this .pwnCount = pwnCount ;
334+ }
335+
336+ public boolean isPwned () {
337+ return isPwned ;
338+ }
339+
340+ public int getPwnCount () {
341+ return pwnCount ;
342+ }
343+ }
265344}
0 commit comments