diff --git a/main/commons/src/main/java/org/cryptomator/common/Environment.java b/main/commons/src/main/java/org/cryptomator/common/Environment.java index 52e23ff07..8dfb56ef2 100644 --- a/main/commons/src/main/java/org/cryptomator/common/Environment.java +++ b/main/commons/src/main/java/org/cryptomator/common/Environment.java @@ -25,6 +25,7 @@ public class Environment { private static final Path RELATIVE_HOME_DIR = Paths.get("~"); private static final Path ABSOLUTE_HOME_DIR = Paths.get(USER_HOME); private static final char PATH_LIST_SEP = ':'; + private static final int DEFAULT_MIN_PW_LENGTH = 8; @Inject public Environment() { @@ -37,6 +38,7 @@ public class Environment { LOG.debug("cryptomator.keychainPath: {}", System.getProperty("cryptomator.keychainPath")); LOG.debug("cryptomator.logDir: {}", System.getProperty("cryptomator.logDir")); LOG.debug("cryptomator.mountPointsDir: {}", System.getProperty("cryptomator.mountPointsDir")); + LOG.debug("cryptomator.minPwLength: {}", System.getProperty("cryptomator.minPwLength")); } public boolean useCustomLogbackConfig() { @@ -63,6 +65,19 @@ public class Environment { return getPath("cryptomator.mountPointsDir").map(this::replaceHomeDir); } + public int getMinPwLength() { + return getInt("cryptomator.minPwLength", DEFAULT_MIN_PW_LENGTH); + } + + private int getInt(String propertyName, int defaultValue) { + String value = System.getProperty(propertyName); + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { // includes "null" values + return defaultValue; + } + } + private Optional getPath(String propertyName) { String value = System.getProperty(propertyName); return Optional.ofNullable(value).map(Paths::get); diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java index f03da3d7a..b8be3a0fd 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java @@ -9,6 +9,7 @@ package org.cryptomator.ui.common; import com.nulabinc.zxcvbn.Zxcvbn; +import org.cryptomator.common.Environment; import org.cryptomator.ui.fxapp.FxApplicationScoped; import javax.inject.Inject; @@ -20,30 +21,33 @@ public class PasswordStrengthUtil { private static final int PW_TRUNC_LEN = 100; // truncate very long passwords, since zxcvbn memory and runtime depends vastly on the length private static final String RESSOURCE_PREFIX = "passwordStrength.messageLabel."; + private static final List SANITIZED_INPUTS = List.of("cryptomator"); - private final Zxcvbn zxcvbn; - private final List sanitizedInputs; private final ResourceBundle resourceBundle; + private final int minPwLength; + private final Zxcvbn zxcvbn; @Inject - public PasswordStrengthUtil(ResourceBundle resourceBundle) { + public PasswordStrengthUtil(ResourceBundle resourceBundle, Environment environment) { this.resourceBundle = resourceBundle; + this.minPwLength = environment.getMinPwLength(); this.zxcvbn = new Zxcvbn(); - this.sanitizedInputs = List.of("cryptomator"); } public int computeRate(CharSequence password) { - if (password == null || password.length() == 0) { + if (password == null || password.length() < minPwLength) { return -1; } else { int numCharsToRate = Math.min(PW_TRUNC_LEN, password.length()); - return zxcvbn.measure(password.subSequence(0, numCharsToRate), sanitizedInputs).getScore(); + return zxcvbn.measure(password.subSequence(0, numCharsToRate), SANITIZED_INPUTS).getScore(); } } public String getStrengthDescription(Number score) { - if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) { - return resourceBundle.getString("passwordStrength.messageLabel." + score.intValue()); + if (score.intValue() == -1) { + return String.format(resourceBundle.getString(RESSOURCE_PREFIX + "tooShort"), minPwLength); + } else if (resourceBundle.containsKey(RESSOURCE_PREFIX + score.intValue())) { + return resourceBundle.getString(RESSOURCE_PREFIX + score.intValue()); } else { return ""; } diff --git a/main/ui/src/main/resources/i18n/strings.properties b/main/ui/src/main/resources/i18n/strings.properties index 3a67243f3..a401f6fdf 100644 --- a/main/ui/src/main/resources/i18n/strings.properties +++ b/main/ui/src/main/resources/i18n/strings.properties @@ -193,6 +193,7 @@ newPassword.promptText=Enter a new password newPassword.reenterPassword=Confirm the new password newPassword.passwordsMatch=Passwords match! newPassword.passwordsDoNotMatch=Passwords do not match +passwordStrength.messageLabel.tooShort=Use at least %d characters passwordStrength.messageLabel.0=Very weak passwordStrength.messageLabel.1=Weak passwordStrength.messageLabel.2=Fair diff --git a/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java b/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java index 357defb17..fb2a61b18 100644 --- a/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java +++ b/main/ui/src/test/java/org/cryptomator/ui/common/PasswordStrengthUtilTest.java @@ -1,6 +1,7 @@ package org.cryptomator.ui.common; import com.google.common.base.Strings; +import org.cryptomator.common.Environment; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -13,7 +14,7 @@ public class PasswordStrengthUtilTest { @Test public void testLongPasswords() { - PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class)); + PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class), Mockito.mock(Environment.class)); String longPw = Strings.repeat("x", 10_000); Assertions.assertTimeout(Duration.ofSeconds(5), () -> { util.computeRate(longPw); @@ -21,9 +22,9 @@ public class PasswordStrengthUtilTest { } @Test - @Disabled("waiting on upstream fix") + @Disabled("waiting on upstream fix") // https://github.com/nulab/zxcvbn4j/issues/54 public void testIssue979() { - PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class)); + PasswordStrengthUtil util = new PasswordStrengthUtil(Mockito.mock(ResourceBundle.class), Mockito.mock(Environment.class)); int result1 = util.computeRate("backed derrick buckling mountains glove client procedures desire destination sword hidden ram"); int result2 = util.computeRate("backed derrick buckling mountains glove client procedures desire destination sword hidden ram escalation"); Assertions.assertEquals(4, result1);