From: Lucas Werkmeister Date: Sat, 2 Jun 2018 19:53:15 +0000 (+0200) Subject: add: optionally check pwned passwords X-Git-Url: https://code.wpia.club/?p=gigi.git;a=commitdiff_plain;h=135e6c4a1a81bb3ea5db81d5d3a619f0c30130ab add: optionally check pwned passwords A new configuration option is added, specifying the path to a file of known password hashes which Gigi will refuse to accept for user accounts. If the option is not specified, Gigi attempts to use the Pwned Passwords database (see the pwned-passwords-bin package) but continues startup if the database cannot be opened. This is intended to be useful for developers: production users should always configure the path to the file explicitly, so that Gigi will refuse to start if the file cannot be accessed for whatever reason. The PasswordHashChecker, if used, is chained behind the usual PasswordStrengthChecker using a DelegatingPasswordChecker. Change-Id: I9e54bd45fa35d7ea81d44677f50635d6ab8514e0 --- diff --git a/config/gigi.properties.template b/config/gigi.properties.template index 5e939e2d..85f2afde 100644 --- a/config/gigi.properties.template +++ b/config/gigi.properties.template @@ -13,6 +13,7 @@ sql.user= sql.password= highFinancialValue=/path/to/alexa/list +#knownPasswordHashes = /usr/share/pwned-passwords/pwned-passwords.bin time.testValidMonths=12 time.reverificationDays=90 diff --git a/debian/control b/debian/control index 54c5b165..fc5499d1 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Homepage: https://wpia.club Package: wpia-gigi Architecture: all Depends: java7-runtime-headless, wpia-gigi-setuid, libpostgresql-jdbc-java, libdnsjava-java, ${shlibs:Depends}, ${misc:Depends} +Recommends: pwned-passwords-bin Conflicts: wpia-gigi-testing Description: WPIA Web-DB software. This program is used to manage accounts and certificates. diff --git a/debian/gigi.properties.5 b/debian/gigi.properties.5 index fc54d8b8..c0cc96df 100644 --- a/debian/gigi.properties.5 +++ b/debian/gigi.properties.5 @@ -124,6 +124,16 @@ Defaults to \fI25\fR. A path to a plain text file of Internet domain names, one per line, which Gigi should refuse to issue certificates to. .TP +.B knownPasswordHashes +A path to a file of SHA-1 hashes of known passwords. +The file should contain the hashes in binary format, without any separators, and should be sorted. +Gigi will refuse user passwords with hashes that are found in this file. +If this option is specified, Gigi will refuse startup if the file cannot be opened, +otherwise it will attempt to use the file +.I /usr/share/pwned-passwords/pwned-passwords.bin +(provided by the \fBpwned-passwords-bin\fR package) +but continue startup if the file cannot be opened. +.TP .B time.testValidMonths The maximum time, in months, for which a passed agent quiz is considered recent. Defaults to \fI12\fR. diff --git a/src/club/wpia/gigi/Gigi.java b/src/club/wpia/gigi/Gigi.java index 44e2ddd9..660c3d30 100644 --- a/src/club/wpia/gigi/Gigi.java +++ b/src/club/wpia/gigi/Gigi.java @@ -4,7 +4,12 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.channels.FileChannel; +import java.nio.file.FileSystems; +import java.nio.file.Path; import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Collections; @@ -78,7 +83,9 @@ import club.wpia.gigi.pages.statistics.StatisticsRoles; import club.wpia.gigi.pages.wot.Points; import club.wpia.gigi.pages.wot.RequestTTPPage; import club.wpia.gigi.pages.wot.VerifyPage; +import club.wpia.gigi.passwords.DelegatingPasswordChecker; import club.wpia.gigi.passwords.PasswordChecker; +import club.wpia.gigi.passwords.PasswordHashChecker; import club.wpia.gigi.passwords.PasswordStrengthChecker; import club.wpia.gigi.ping.PingerDaemon; import club.wpia.gigi.util.AuthorizationContext; @@ -277,7 +284,44 @@ public final class Gigi extends HttpServlet { this.truststore = truststore; pinger = new PingerDaemon(truststore); pinger.start(); - Gigi.passwordChecker = new PasswordStrengthChecker(); + Gigi.passwordChecker = getPasswordChecker(conf); + } + } + + private PasswordChecker getPasswordChecker(Properties conf) { + final String knownPasswordHashesPath; + final boolean knownPasswordHashesRequired; + String knownPasswordHashesConfig = conf.getProperty("knownPasswordHashes"); + if (knownPasswordHashesConfig != null) { + knownPasswordHashesPath = knownPasswordHashesConfig; + knownPasswordHashesRequired = true; + } else { + knownPasswordHashesPath = "/usr/share/pwned-passwords/pwned-passwords.bin"; + knownPasswordHashesRequired = false; + } + + final MessageDigest sha1; + try { + sha1 = MessageDigest.getInstance("SHA-1"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + try { + final FileChannel knownPasswordHashesFile = FileChannel.open( + FileSystems.getDefault().getPath(knownPasswordHashesPath)); + return new DelegatingPasswordChecker(new PasswordChecker[] { + new PasswordStrengthChecker(), + new PasswordHashChecker(knownPasswordHashesFile, sha1) + }); + } catch (IOException e) { + if (knownPasswordHashesRequired) { + throw new RuntimeException("Error while opening password hash database, refusing startup", e); + } else { + System.err.println("Error while opening password hash database, passwords will be checked only by strength"); + e.printStackTrace(); + return new PasswordStrengthChecker(); + } } }