Disable some fields if Steam OTP type is selected

Also, move some magic default OTP parameters to constants
This commit is contained in:
Alexander Bakker 2021-01-30 16:41:25 +01:00
parent bc6cb35dc0
commit 2c8a64f943
15 changed files with 55 additions and 29 deletions

View file

@ -178,10 +178,6 @@ public class OverallTest extends AegisTest {
onView(withId(R.id.dropdown_type)).perform(click()); onView(withId(R.id.dropdown_type)).perform(click());
onView(withText(otpType)).inRoot(RootMatchers.isPlatformPopup()).perform(click()); onView(withText(otpType)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
if (entry.getInfo() instanceof SteamInfo) {
onView(withId(R.id.text_digits)).perform(clearText(), typeText("5"), closeSoftKeyboard());
}
} }
String secret = Base32.encode(entry.getInfo().getSecret()); String secret = Base32.encode(entry.getInfo().getSecret());

View file

@ -239,7 +239,7 @@ public class AndOtpImporter extends DatabaseImporter {
info = new TotpInfo(secret, algo, digits, obj.getInt("period")); info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
break; break;
case "steam": case "steam":
info = new SteamInfo(secret, algo, digits, obj.optInt("period", 30)); info = new SteamInfo(secret, algo, digits, obj.optInt("period", TotpInfo.DEFAULT_PERIOD));
break; break;
default: default:
throw new DatabaseImporterException("unsupported otp type: " + type); throw new DatabaseImporterException("unsupported otp type: " + type);

View file

@ -256,7 +256,7 @@ public class AuthyImporter extends DatabaseImporter {
} }
int digits = entry.getInt("digits"); int digits = entry.getInt("digits");
OtpInfo info = new TotpInfo(secret, "SHA1", digits, isAuthy ? 10 : 30); OtpInfo info = new TotpInfo(secret, OtpInfo.DEFAULT_ALGORITHM, digits, isAuthy ? 10 : TotpInfo.DEFAULT_PERIOD);
return new VaultEntry(info, authyEntryInfo.Name, authyEntryInfo.Issuer); return new VaultEntry(info, authyEntryInfo.Name, authyEntryInfo.Issuer);
} catch (OtpInfoException | JSONException | EncodingException e) { } catch (OtpInfoException | JSONException | EncodingException e) {
throw new DatabaseImporterEntryException(e, entry.toString()); throw new DatabaseImporterEntryException(e, entry.toString());

View file

@ -91,7 +91,7 @@ public class MicrosoftAuthImporter extends DatabaseImporter {
throw new DatabaseImporterEntryException(String.format("Unsupported OTP type: %d", entry.getType()), entry.toString()); throw new DatabaseImporterEntryException(String.format("Unsupported OTP type: %d", entry.getType()), entry.toString());
} }
OtpInfo info = new TotpInfo(secret, "SHA1", digits, 30); OtpInfo info = new TotpInfo(secret, OtpInfo.DEFAULT_ALGORITHM, digits, TotpInfo.DEFAULT_PERIOD);
return new VaultEntry(info, entry.getUserName(), entry.getIssuer()); return new VaultEntry(info, entry.getUserName(), entry.getIssuer());
} catch (EncodingException | OtpInfoException e) { } catch (EncodingException | OtpInfoException e) {
throw new DatabaseImporterEntryException(e, entry.toString()); throw new DatabaseImporterEntryException(e, entry.toString());

View file

@ -221,7 +221,7 @@ public class TotpAuthenticatorImporter extends DatabaseImporter {
throw new DatabaseImporterEntryException(String.format("Unsupported secret encoding: base %d", base), obj.toString()); throw new DatabaseImporterEntryException(String.format("Unsupported secret encoding: base %d", base), obj.toString());
} }
TotpInfo info = new TotpInfo(secret, "SHA1", 6, 30); TotpInfo info = new TotpInfo(secret);
String name = obj.optString("name"); String name = obj.optString("name");
String issuer = obj.optString("issuer"); String issuer = obj.optString("issuer");

View file

@ -221,7 +221,7 @@ public class GoogleAuthInfo implements Serializable {
case DIGIT_COUNT_UNSPECIFIED: case DIGIT_COUNT_UNSPECIFIED:
// intentional fallthrough // intentional fallthrough
case DIGIT_COUNT_SIX: case DIGIT_COUNT_SIX:
digits = 6; digits = TotpInfo.DEFAULT_DIGITS;
break; break;
case DIGIT_COUNT_EIGHT: case DIGIT_COUNT_EIGHT:
digits = 8; digits = 8;
@ -252,7 +252,7 @@ public class GoogleAuthInfo implements Serializable {
case OTP_TYPE_UNSPECIFIED: case OTP_TYPE_UNSPECIFIED:
// intentional fallthrough // intentional fallthrough
case OTP_TYPE_TOTP: case OTP_TYPE_TOTP:
otp = new TotpInfo(secret, algo, digits, 30); otp = new TotpInfo(secret, algo, digits, TotpInfo.DEFAULT_PERIOD);
break; break;
case OTP_TYPE_HOTP: case OTP_TYPE_HOTP:
otp = new HotpInfo(secret, algo, digits, params.getCounter()); otp = new HotpInfo(secret, algo, digits, params.getCounter());

View file

@ -11,6 +11,7 @@ import java.security.NoSuchAlgorithmException;
public class HotpInfo extends OtpInfo { public class HotpInfo extends OtpInfo {
public static final String ID = "hotp"; public static final String ID = "hotp";
public static final int DEFAULT_COUNTER = 0;
private long _counter; private long _counter;
@ -20,7 +21,7 @@ public class HotpInfo extends OtpInfo {
} }
public HotpInfo(byte[] secret) throws OtpInfoException { public HotpInfo(byte[] secret) throws OtpInfoException {
this(secret, 0); this(secret, DEFAULT_COUNTER);
} }
public HotpInfo(byte[] secret, String algorithm, int digits, long counter) throws OtpInfoException { public HotpInfo(byte[] secret, String algorithm, int digits, long counter) throws OtpInfoException {

View file

@ -10,12 +10,15 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
public abstract class OtpInfo implements Serializable { public abstract class OtpInfo implements Serializable {
public static final int DEFAULT_DIGITS = 6;
public static final String DEFAULT_ALGORITHM = "SHA1";
private byte[] _secret; private byte[] _secret;
private String _algorithm; private String _algorithm;
private int _digits; private int _digits;
public OtpInfo(byte[] secret) throws OtpInfoException { public OtpInfo(byte[] secret) throws OtpInfoException {
this(secret, "SHA1", 6); this(secret, DEFAULT_ALGORITHM, DEFAULT_DIGITS);
} }
public OtpInfo(byte[] secret, String algorithm, int digits) throws OtpInfoException { public OtpInfo(byte[] secret, String algorithm, int digits) throws OtpInfoException {
@ -29,7 +32,7 @@ public abstract class OtpInfo implements Serializable {
public abstract String getTypeId(); public abstract String getTypeId();
public String getType() { public String getType() {
return getType().toUpperCase(); return getTypeId().toUpperCase();
} }
public JSONObject toJson() { public JSONObject toJson() {

View file

@ -8,9 +8,10 @@ import java.security.NoSuchAlgorithmException;
public class SteamInfo extends TotpInfo { public class SteamInfo extends TotpInfo {
public static final String ID = "steam"; public static final String ID = "steam";
public static final int DIGITS = 5;
public SteamInfo(byte[] secret) throws OtpInfoException { public SteamInfo(byte[] secret) throws OtpInfoException {
super(secret, "SHA1", 5, 30); super(secret, OtpInfo.DEFAULT_ALGORITHM, DIGITS, TotpInfo.DEFAULT_PERIOD);
} }
public SteamInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException { public SteamInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException {

View file

@ -11,12 +11,13 @@ import java.security.NoSuchAlgorithmException;
public class TotpInfo extends OtpInfo { public class TotpInfo extends OtpInfo {
public static final String ID = "totp"; public static final String ID = "totp";
public static final int DEFAULT_PERIOD = 30;
private int _period; private int _period;
public TotpInfo(byte[] secret) throws OtpInfoException { public TotpInfo(byte[] secret) throws OtpInfoException {
super(secret); super(secret);
setPeriod(30); setPeriod(DEFAULT_PERIOD);
} }
public TotpInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException { public TotpInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException {

View file

@ -81,10 +81,12 @@ public class EditEntryActivity extends AegisActivity {
private TextInputEditText _textPeriodCounter; private TextInputEditText _textPeriodCounter;
private TextInputLayout _textPeriodCounterLayout; private TextInputLayout _textPeriodCounterLayout;
private TextInputEditText _textDigits; private TextInputEditText _textDigits;
private TextInputLayout _textDigitsLayout;
private TextInputEditText _textSecret; private TextInputEditText _textSecret;
private AutoCompleteTextView _dropdownType; private AutoCompleteTextView _dropdownType;
private AutoCompleteTextView _dropdownAlgo; private AutoCompleteTextView _dropdownAlgo;
private TextInputLayout _dropdownAlgoLayout;
private AutoCompleteTextView _dropdownGroup; private AutoCompleteTextView _dropdownGroup;
private List<String> _dropdownGroupList = new ArrayList<>(); private List<String> _dropdownGroupList = new ArrayList<>();
@ -129,9 +131,11 @@ public class EditEntryActivity extends AegisActivity {
_textPeriodCounter = findViewById(R.id.text_period_counter); _textPeriodCounter = findViewById(R.id.text_period_counter);
_textPeriodCounterLayout = findViewById(R.id.text_period_counter_layout); _textPeriodCounterLayout = findViewById(R.id.text_period_counter_layout);
_textDigits = findViewById(R.id.text_digits); _textDigits = findViewById(R.id.text_digits);
_textDigitsLayout = findViewById(R.id.text_digits_layout);
_textSecret = findViewById(R.id.text_secret); _textSecret = findViewById(R.id.text_secret);
_dropdownType = findViewById(R.id.dropdown_type); _dropdownType = findViewById(R.id.dropdown_type);
DropdownHelper.fillDropdown(this, _dropdownType, R.array.otp_types_array); DropdownHelper.fillDropdown(this, _dropdownType, R.array.otp_types_array);
_dropdownAlgoLayout = findViewById(R.id.dropdown_algo_layout);
_dropdownAlgo = findViewById(R.id.dropdown_algo); _dropdownAlgo = findViewById(R.id.dropdown_algo);
DropdownHelper.fillDropdown(this, _dropdownAlgo, R.array.otp_algo_array); DropdownHelper.fillDropdown(this, _dropdownAlgo, R.array.otp_algo_array);
_dropdownGroup = findViewById(R.id.dropdown_group); _dropdownGroup = findViewById(R.id.dropdown_group);
@ -194,8 +198,9 @@ public class EditEntryActivity extends AegisActivity {
_textSecret.setText(secretString); _textSecret.setText(secretString);
} }
_dropdownType.setText(_origEntry.getInfo().getTypeId().toUpperCase(), false); _dropdownType.setText(_origEntry.getInfo().getType(), false);
_dropdownAlgo.setText(_origEntry.getInfo().getAlgorithm(false), false); _dropdownAlgo.setText(_origEntry.getInfo().getAlgorithm(false), false);
updateAdvancedFieldStatus(_origEntry.getInfo().getTypeId());
String group = _origEntry.getGroup(); String group = _origEntry.getGroup();
setGroup(group); setGroup(group);
@ -208,18 +213,27 @@ public class EditEntryActivity extends AegisActivity {
_dropdownType.setOnItemClickListener((parent, view, position, id) -> { _dropdownType.setOnItemClickListener((parent, view, position, id) -> {
String type = _dropdownType.getText().toString().toLowerCase(); String type = _dropdownType.getText().toString().toLowerCase();
switch (type) { switch (type) {
case TotpInfo.ID:
case SteamInfo.ID: case SteamInfo.ID:
_dropdownAlgo.setText(OtpInfo.DEFAULT_ALGORITHM, false);
_textPeriodCounterLayout.setHint(R.string.period_hint); _textPeriodCounterLayout.setHint(R.string.period_hint);
_textPeriodCounter.setText(String.format("%d", 30)); _textPeriodCounter.setText(String.valueOf(TotpInfo.DEFAULT_PERIOD));
_textDigits.setText(String.valueOf(SteamInfo.DIGITS));
break;
case TotpInfo.ID:
_textPeriodCounterLayout.setHint(R.string.period_hint);
_textPeriodCounter.setText(String.valueOf(TotpInfo.DEFAULT_PERIOD));
_textDigits.setText(String.valueOf(OtpInfo.DEFAULT_DIGITS));
break; break;
case HotpInfo.ID: case HotpInfo.ID:
_textPeriodCounterLayout.setHint(R.string.counter); _textPeriodCounterLayout.setHint(R.string.counter);
_textPeriodCounter.setText(String.format("%d", 0)); _textPeriodCounter.setText(String.valueOf(HotpInfo.DEFAULT_COUNTER));
_textDigits.setText(String.valueOf(OtpInfo.DEFAULT_DIGITS));
break; break;
default: default:
throw new RuntimeException(String.format("Unsupported OTP type: %s", type)); throw new RuntimeException(String.format("Unsupported OTP type: %s", type));
} }
updateAdvancedFieldStatus(type);
}); });
_iconView.setOnClickListener(v -> { _iconView.setOnClickListener(v -> {
@ -248,6 +262,13 @@ public class EditEntryActivity extends AegisActivity {
}); });
} }
private void updateAdvancedFieldStatus(String otpType) {
boolean isSteam = otpType.equals(SteamInfo.ID);
_textDigitsLayout.setEnabled(!isSteam);
_textPeriodCounterLayout.setEnabled(!isSteam);
_dropdownAlgoLayout.setEnabled(!isSteam);
}
private void setGroup(String groupName) { private void setGroup(String groupName) {
int pos = 0; int pos = 0;
if (groupName != null) { if (groupName != null) {

View file

@ -14,7 +14,7 @@ import androidx.annotation.RequiresApi;
import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.otp.TotpInfo;
public class TotpProgressBar extends ProgressBar { public class TotpProgressBar extends ProgressBar {
private int _period = 30; private int _period = TotpInfo.DEFAULT_PERIOD;
private Handler _handler; private Handler _handler;
private float _animDurationScale; private float _animDurationScale;

View file

@ -237,6 +237,7 @@
android:inputType="none"/> android:inputType="none"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/dropdown_algo_layout"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
@ -271,6 +272,7 @@
android:inputType="text"/> android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_digits_layout"
android:hint="@string/digits" android:hint="@string/digits"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"

View file

@ -8,6 +8,7 @@ import androidx.test.core.app.ApplicationProvider;
import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.EncodingException; import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.HotpInfo; import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.otp.TotpInfo;
@ -178,11 +179,11 @@ public class DatabaseImporterTest {
for (VaultEntry entry : entries) { for (VaultEntry entry : entries) {
// Google Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here // Google Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setDigits(6); entryVector.getInfo().setDigits(OtpInfo.DEFAULT_DIGITS);
if (entryVector.getInfo() instanceof TotpInfo) { if (entryVector.getInfo() instanceof TotpInfo) {
((TotpInfo) entryVector.getInfo()).setPeriod(30); ((TotpInfo) entryVector.getInfo()).setPeriod(TotpInfo.DEFAULT_PERIOD);
} }
entryVector.getInfo().setAlgorithm("SHA1"); entryVector.getInfo().setAlgorithm(OtpInfo.DEFAULT_ALGORITHM);
checkImportedEntry(entryVector, entry); checkImportedEntry(entryVector, entry);
} }
} }
@ -264,7 +265,7 @@ public class DatabaseImporterTest {
for (VaultEntry entry : entries) { for (VaultEntry entry : entries) {
// Authy doesn't support different hash algorithms or periods, so fix those up here // Authy doesn't support different hash algorithms or periods, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setAlgorithm("SHA1"); entryVector.getInfo().setAlgorithm(OtpInfo.DEFAULT_ALGORITHM);
((TotpInfo) entry.getInfo()).setPeriod(((TotpInfo) entryVector.getInfo()).getPeriod()); ((TotpInfo) entry.getInfo()).setPeriod(((TotpInfo) entryVector.getInfo()).getPeriod());
checkImportedEntry(entryVector, entry); checkImportedEntry(entryVector, entry);
} }
@ -274,9 +275,9 @@ public class DatabaseImporterTest {
for (VaultEntry entry : entries) { for (VaultEntry entry : entries) {
// TOTP Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here // TOTP Authenticator doesn't support different hash algorithms, periods or digits, so fix those up here
VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret());
entryVector.getInfo().setDigits(6); entryVector.getInfo().setDigits(OtpInfo.DEFAULT_DIGITS);
((TotpInfo) entryVector.getInfo()).setPeriod(30); ((TotpInfo) entryVector.getInfo()).setPeriod(TotpInfo.DEFAULT_PERIOD);
entryVector.getInfo().setAlgorithm("SHA1"); entryVector.getInfo().setAlgorithm(OtpInfo.DEFAULT_ALGORITHM);
entryVector.setName(entryVector.getName().toLowerCase()); entryVector.setName(entryVector.getName().toLowerCase());
checkImportedEntry(entryVector, entry); checkImportedEntry(entryVector, entry);
} }

View file

@ -11,7 +11,7 @@ public class OtpTest {
@Test @Test
public void testHotpInfoOtp() throws OtpInfoException { public void testHotpInfoOtp() throws OtpInfoException {
for (int i = 0; i < HOTPTest.VECTORS.length; i++) { for (int i = 0; i < HOTPTest.VECTORS.length; i++) {
HotpInfo info = new HotpInfo(HOTPTest.SECRET, "SHA1", 6, i); HotpInfo info = new HotpInfo(HOTPTest.SECRET, OtpInfo.DEFAULT_ALGORITHM, OtpInfo.DEFAULT_DIGITS, i);
assertEquals(HOTPTest.VECTORS[i], info.getOtp()); assertEquals(HOTPTest.VECTORS[i], info.getOtp());
} }
} }
@ -20,7 +20,7 @@ public class OtpTest {
public void testTotpInfoOtp() throws OtpInfoException { public void testTotpInfoOtp() throws OtpInfoException {
for (TOTPTest.Vector vector : TOTPTest.VECTORS) { for (TOTPTest.Vector vector : TOTPTest.VECTORS) {
byte[] seed = TOTPTest.getSeed(vector.Algo); byte[] seed = TOTPTest.getSeed(vector.Algo);
TotpInfo info = new TotpInfo(seed, vector.Algo, 8, 30); TotpInfo info = new TotpInfo(seed, vector.Algo, 8, TotpInfo.DEFAULT_PERIOD);
assertEquals(vector.OTP, info.getOtp(vector.Time)); assertEquals(vector.OTP, info.getOtp(vector.Time));
} }
} }