Stop analyzing password strength if it becomes longer than 64 chars

This should help reduce the chance that zxcvbn4j explodes on a password
input.

I also took the opportunity to deduplicate related code a bit.
This commit is contained in:
Alexander Bakker 2024-03-27 14:43:28 +01:00
parent 06437132b5
commit 559e68e0d2
4 changed files with 65 additions and 43 deletions

View file

@ -1,14 +1,60 @@
package com.beemdevelopment.aegis.helpers;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.Strings;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
public class PasswordStrengthHelper {
// Material design color palette
private static String[] COLORS = {"#FF5252", "#FF5252", "#FFC107", "#8BC34A", "#4CAF50"};
// Limit the password length to prevent zxcvbn4j from exploding
private static final int MAX_PASSWORD_LENGTH = 64;
public static String getString(int score, Context context) {
// Material design color palette
private final static String[] COLORS = {"#FF5252", "#FF5252", "#FFC107", "#8BC34A", "#4CAF50"};
private final Zxcvbn _zxcvbn = new Zxcvbn();
private final EditText _textPassword;
private final ProgressBar _barPasswordStrength;
private final TextView _textPasswordStrength;
private final TextInputLayout _textPasswordWrapper;
public PasswordStrengthHelper(
EditText textPassword,
ProgressBar barPasswordStrength,
TextView textPasswordStrength,
TextInputLayout textPasswordWrapper
) {
_textPassword = textPassword;
_barPasswordStrength = barPasswordStrength;
_textPasswordStrength = textPasswordStrength;
_textPasswordWrapper = textPasswordWrapper;
}
public void measure(Context context) {
if (_textPassword.getText().length() > MAX_PASSWORD_LENGTH) {
_barPasswordStrength.setProgress(0);
_textPasswordStrength.setText(R.string.password_strength_unknown);
} else {
Strength strength = _zxcvbn.measure(_textPassword.getText());
_barPasswordStrength.setProgress(strength.getScore());
_barPasswordStrength.setProgressTintList(ColorStateList.valueOf(Color.parseColor(getColor(strength.getScore()))));
_textPasswordStrength.setText((_textPassword.getText().length() != 0) ? getString(strength.getScore(), context) : "");
String warning = strength.getFeedback().getWarning();
_textPasswordWrapper.setError(warning);
_textPasswordWrapper.setErrorEnabled(!Strings.isNullOrEmpty(warning));
strength.wipe();
}
}
private static String getString(int score, Context context) {
if (score < 0 || score > 4) {
throw new IllegalArgumentException("Not a valid zxcvbn score");
}
@ -17,7 +63,7 @@ public class PasswordStrengthHelper {
return strings[score];
}
public static String getColor(int score) {
private static String getColor(int score) {
if (score < 0 || score > 4) {
throw new IllegalArgumentException("Not a valid zxcvbn score");
}

View file

@ -5,8 +5,6 @@ import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.text.InputType;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
@ -43,8 +41,6 @@ import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import java.util.ArrayList;
import java.util.List;
@ -120,7 +116,6 @@ public class Dialogs {
}
public static void showSetPasswordDialog(ComponentActivity activity, PasswordSlotListener listener) {
Zxcvbn zxcvbn = new Zxcvbn();
View view = activity.getLayoutInflater().inflate(R.layout.dialog_password, null);
EditText textPassword = view.findViewById(R.id.text_password);
EditText textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
@ -128,6 +123,8 @@ public class Dialogs {
TextView textPasswordStrength = view.findViewById(R.id.text_password_strength);
TextInputLayout textPasswordWrapper = view.findViewById(R.id.text_password_wrapper);
CheckBox switchToggleVisibility = view.findViewById(R.id.check_toggle_visibility);
PasswordStrengthHelper passStrength = new PasswordStrengthHelper(
textPassword, barPasswordStrength, textPasswordStrength, textPasswordWrapper);
switchToggleVisibility.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
@ -183,13 +180,7 @@ public class Dialogs {
TextWatcher watcher = new SimpleTextWatcher(text -> {
boolean equal = EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm);
buttonOK.get().setEnabled(equal);
Strength strength = zxcvbn.measure(textPassword.getText());
barPasswordStrength.setProgress(strength.getScore());
barPasswordStrength.setProgressTintList(ColorStateList.valueOf(Color.parseColor(PasswordStrengthHelper.getColor(strength.getScore()))));
textPasswordStrength.setText((textPassword.getText().length() != 0) ? PasswordStrengthHelper.getString(strength.getScore(), activity) : "");
textPasswordWrapper.setError(strength.getFeedback().getWarning());
strength.wipe();
passStrength.measure(activity);
});
textPassword.addTextChangedListener(watcher);
textPasswordConfirm.addTextChangedListener(watcher);

View file

@ -1,10 +1,12 @@
package com.beemdevelopment.aegis.ui.slides;
import android.content.res.ColorStateList;
import android.graphics.Color;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.PasswordTransformationMethod;
import android.view.LayoutInflater;
import android.view.View;
@ -23,6 +25,7 @@ import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.PasswordStrengthHelper;
import com.beemdevelopment.aegis.helpers.SimpleTextWatcher;
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
import com.beemdevelopment.aegis.ui.intro.SlideFragment;
import com.beemdevelopment.aegis.ui.tasks.KeyDerivationTask;
@ -32,17 +35,10 @@ import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
import com.beemdevelopment.aegis.vault.slots.Slot;
import com.beemdevelopment.aegis.vault.slots.SlotException;
import com.google.android.material.textfield.TextInputLayout;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_BIOMETRIC;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_INVALID;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_NONE;
import static com.beemdevelopment.aegis.ui.slides.SecurityPickerSlide.CRYPT_TYPE_PASS;
public class SecuritySetupSlide extends SlideFragment {
private EditText _textPassword;
private EditText _textPasswordConfirm;
@ -76,27 +72,15 @@ public class SecuritySetupSlide extends SlideFragment {
}
});
_textPassword.addTextChangedListener(new TextWatcher() {
private Zxcvbn _zxcvbn = new Zxcvbn();
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Strength strength = _zxcvbn.measure(_textPassword.getText());
_barPasswordStrength.setProgress(strength.getScore());
_barPasswordStrength.setProgressTintList(ColorStateList.valueOf(Color.parseColor(PasswordStrengthHelper.getColor(strength.getScore()))));
_textPasswordStrength.setText((_textPassword.getText().length() != 0) ? PasswordStrengthHelper.getString(strength.getScore(), requireContext()) : "");
_textPasswordWrapper.setError(strength.getFeedback().getWarning());
strength.wipe();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
_textPassword.addTextChangedListener(new SimpleTextWatcher(new SimpleTextWatcher.Listener() {
private final PasswordStrengthHelper passStrength = new PasswordStrengthHelper(
_textPassword, _barPasswordStrength, _textPasswordStrength, _textPasswordWrapper);
@Override
public void afterTextChanged(Editable s) {
passStrength.measure(requireContext());
}
});
}));
return view;
}

View file

@ -447,6 +447,7 @@
<string name="password_strength_fair">Fair</string>
<string name="password_strength_good">Good</string>
<string name="password_strength_strong">Strong</string>
<string name="password_strength_unknown">Password too long for strength analysis</string>
<string name="pref_pin_keyboard_title">Use PIN keyboard on lockscreen</string>
<string name="pref_pin_keyboard_summary">Enable this if you want to enable the PIN keyboard on the lockscreen. This only works for numeric passwords</string>