mirror of
https://github.com/beemdevelopment/Aegis
synced 2024-09-06 16:03:30 +00:00
Merge pull request #926 from alexbakker/backup-password
Add an option to set a separate password for backups and exports
This commit is contained in:
commit
0cfd78ace1
|
@ -36,7 +36,6 @@ android {
|
|||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
}
|
||||
|
||||
|
||||
testOptions {
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.beemdevelopment.aegis;
|
|||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.espresso.UiController;
|
||||
import androidx.test.espresso.ViewAction;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
@ -16,6 +17,7 @@ import com.beemdevelopment.aegis.vault.VaultRepository;
|
|||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vectors.VaultEntries;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Before;
|
||||
|
@ -25,6 +27,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
@ -34,6 +37,7 @@ import dagger.hilt.android.testing.HiltAndroidRule;
|
|||
|
||||
public abstract class AegisTest {
|
||||
public static final String VAULT_PASSWORD = "test";
|
||||
public static final String VAULT_BACKUP_PASSWORD = "something";
|
||||
|
||||
@Rule
|
||||
public HiltAndroidRule hiltRule = new HiltAndroidRule(this);
|
||||
|
@ -53,8 +57,21 @@ public abstract class AegisTest {
|
|||
return (AegisApplicationBase) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
|
||||
}
|
||||
|
||||
protected VaultRepository initVault() {
|
||||
protected VaultRepository initEncryptedVault() {
|
||||
VaultFileCredentials creds = generateCredentials();
|
||||
return initVault(creds, VaultEntries.get());
|
||||
}
|
||||
|
||||
protected VaultRepository initEmptyEncryptedVault() {
|
||||
VaultFileCredentials creds = generateCredentials();
|
||||
return initVault(creds, null);
|
||||
}
|
||||
|
||||
protected VaultRepository initPlainVault() {
|
||||
return initVault(null, VaultEntries.get());
|
||||
}
|
||||
|
||||
private VaultRepository initVault(@Nullable VaultFileCredentials creds, @Nullable List<VaultEntry> entries) {
|
||||
VaultRepository vault;
|
||||
try {
|
||||
vault = _vaultManager.init(creds);
|
||||
|
@ -62,6 +79,18 @@ public abstract class AegisTest {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (entries != null) {
|
||||
for (VaultEntry entry : entries) {
|
||||
_vaultManager.getVault().addEntry(entry);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_vaultManager.save();
|
||||
} catch (VaultRepositoryException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
_prefs.setIntroDone(true);
|
||||
return vault;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,302 @@
|
|||
package com.beemdevelopment.aegis;
|
||||
|
||||
import static androidx.test.espresso.Espresso.onView;
|
||||
import static androidx.test.espresso.action.ViewActions.click;
|
||||
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
|
||||
import static androidx.test.espresso.action.ViewActions.pressBack;
|
||||
import static androidx.test.espresso.action.ViewActions.typeText;
|
||||
import static androidx.test.espresso.intent.Intents.intending;
|
||||
import static androidx.test.espresso.intent.matcher.IntentMatchers.isInternal;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withId;
|
||||
import static androidx.test.espresso.matcher.ViewMatchers.withText;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Instrumentation;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.espresso.contrib.RecyclerViewActions;
|
||||
import androidx.test.espresso.intent.Intents;
|
||||
import androidx.test.espresso.matcher.RootMatchers;
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.encoding.Hex;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
||||
import com.beemdevelopment.aegis.importers.GoogleAuthUriImporter;
|
||||
import com.beemdevelopment.aegis.ui.PreferencesActivity;
|
||||
import com.beemdevelopment.aegis.util.IOUtils;
|
||||
import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileException;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotIntegrityException;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.vectors.VaultEntries;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import dagger.hilt.android.testing.HiltAndroidTest;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@HiltAndroidTest
|
||||
@SmallTest
|
||||
public class BackupExportTest extends AegisTest {
|
||||
@Rule
|
||||
public final ActivityScenarioRule<PreferencesActivity> activityRule = new ActivityScenarioRule<>(PreferencesActivity.class);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Intents.init();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
Intents.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlainVaultExportPlainJson() {
|
||||
initPlainVault();
|
||||
|
||||
openExportDialog();
|
||||
onView(withId(R.id.checkbox_export_encrypt)).perform(click());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
onView(withId(R.id.checkbox_accept)).perform(click());
|
||||
File file = doExport();
|
||||
|
||||
readVault(file, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlainVaultExportPlainTxt() {
|
||||
initPlainVault();
|
||||
|
||||
openExportDialog();
|
||||
onView(withId(R.id.checkbox_export_encrypt)).perform(click());
|
||||
onView(withId(R.id.dropdown_export_format)).perform(click());
|
||||
onView(withText(R.string.export_format_google_auth_uri)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
onView(withId(R.id.checkbox_accept)).perform(click());
|
||||
File file = doExport();
|
||||
|
||||
readTxtExport(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlainVaultExportEncryptedJson() {
|
||||
initPlainVault();
|
||||
|
||||
openExportDialog();
|
||||
File file = doExport();
|
||||
|
||||
onView(withId(R.id.text_password)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
||||
onView(withId(R.id.text_password_confirm)).perform(typeText(VAULT_PASSWORD), closeSoftKeyboard());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
|
||||
readVault(file, VAULT_PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptedVaultExportPlainJson() {
|
||||
initEncryptedVault();
|
||||
|
||||
openExportDialog();
|
||||
onView(withId(R.id.checkbox_export_encrypt)).perform(click());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
onView(withId(R.id.checkbox_accept)).perform(click());
|
||||
File file = doExport();
|
||||
|
||||
readVault(file, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptedVaultExportPlainTxt() {
|
||||
initEncryptedVault();
|
||||
|
||||
openExportDialog();
|
||||
onView(withId(R.id.checkbox_export_encrypt)).perform(click());
|
||||
onView(withId(R.id.dropdown_export_format)).perform(click());
|
||||
onView(withText(R.string.export_format_google_auth_uri)).inRoot(RootMatchers.isPlatformPopup()).perform(click());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
onView(withId(R.id.checkbox_accept)).perform(click());
|
||||
File file = doExport();
|
||||
|
||||
readTxtExport(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptedVaultExportEncryptedJson() {
|
||||
initEncryptedVault();
|
||||
|
||||
openExportDialog();
|
||||
File file = doExport();
|
||||
|
||||
readVault(file, VAULT_PASSWORD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeparateExportPassword() {
|
||||
initEncryptedVault();
|
||||
setSeparateBackupExportPassword();
|
||||
|
||||
openExportDialog();
|
||||
File file = doExport();
|
||||
|
||||
readVault(file, VAULT_BACKUP_PASSWORD);
|
||||
}
|
||||
|
||||
private void setSeparateBackupExportPassword() {
|
||||
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||
assertEquals(creds.getSlots().findRegularPasswordSlots().size(), 1);
|
||||
assertEquals(creds.getSlots().findBackupPasswordSlots().size(), 0);
|
||||
|
||||
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_section_security_title)), click()));
|
||||
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_backup_password_title)), click()));
|
||||
onView(withId(R.id.text_password)).perform(typeText(VAULT_BACKUP_PASSWORD), closeSoftKeyboard());
|
||||
onView(withId(R.id.text_password_confirm)).perform(typeText(VAULT_BACKUP_PASSWORD), closeSoftKeyboard());
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
onView(isRoot()).perform(pressBack());
|
||||
|
||||
creds = _vaultManager.getVault().getCredentials();
|
||||
assertEquals(creds.getSlots().findRegularPasswordSlots().size(), 1);
|
||||
assertEquals(creds.getSlots().findBackupPasswordSlots().size(), 1);
|
||||
for (PasswordSlot slot : creds.getSlots().findBackupPasswordSlots()) {
|
||||
assertThrows(SlotIntegrityException.class, () -> decryptPasswordSlot(slot, VAULT_PASSWORD));
|
||||
MasterKey masterKey;
|
||||
try {
|
||||
masterKey = decryptPasswordSlot(slot, VAULT_BACKUP_PASSWORD);
|
||||
} catch (SlotIntegrityException e) {
|
||||
throw new RuntimeException("Unable to decrypt password slot", e);
|
||||
}
|
||||
|
||||
assertArrayEquals(creds.getKey().getBytes(), masterKey.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
private File doExport() {
|
||||
File file = getExportFileUri();
|
||||
Intent resultData = new Intent();
|
||||
resultData.setData(Uri.fromFile(file));
|
||||
|
||||
Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, resultData);
|
||||
intending(not(isInternal())).respondWith(result);
|
||||
|
||||
onView(withId(android.R.id.button1)).perform(click());
|
||||
return file;
|
||||
}
|
||||
|
||||
private void openExportDialog() {
|
||||
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_section_import_export_title)), click()));
|
||||
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_export_title)), click()));
|
||||
}
|
||||
|
||||
private MasterKey decryptPasswordSlot(PasswordSlot slot, String password) throws SlotIntegrityException {
|
||||
SecretKey derivedKey = slot.deriveKey(password.toCharArray());
|
||||
try {
|
||||
Cipher cipher = slot.createDecryptCipher(derivedKey);
|
||||
return slot.getKey(cipher);
|
||||
} catch (SlotException e) {
|
||||
throw new RuntimeException("Unable to decrypt password slot", e);
|
||||
}
|
||||
}
|
||||
|
||||
private File getExportFileUri() {
|
||||
String dirName = Hex.encode(CryptoUtils.generateRandomBytes(8));
|
||||
File dir = new File(getInstrumentation().getTargetContext().getExternalCacheDir(), String.format("export-%s", dirName));
|
||||
if (!dir.mkdirs()) {
|
||||
throw new RuntimeException(String.format("Unable to create export directory: %s", dir));
|
||||
}
|
||||
|
||||
VaultBackupManager.FileInfo fileInfo = new VaultBackupManager.FileInfo(VaultRepository.FILENAME_PREFIX_EXPORT);
|
||||
return new File(dir, fileInfo.toString());
|
||||
}
|
||||
|
||||
private VaultRepository readVault(File file, @Nullable String password) {
|
||||
VaultRepository repo;
|
||||
try (InputStream inStream = new FileInputStream(file)) {
|
||||
byte[] bytes = IOUtils.readAll(inStream);
|
||||
VaultFile vaultFile = VaultFile.fromBytes(bytes);
|
||||
|
||||
VaultFileCredentials creds = null;
|
||||
if (password != null) {
|
||||
SlotList slots = vaultFile.getHeader().getSlots();
|
||||
for (PasswordSlot slot : slots.findAll(PasswordSlot.class)) {
|
||||
SecretKey derivedKey = slot.deriveKey(password.toCharArray());
|
||||
Cipher cipher = slot.createDecryptCipher(derivedKey);
|
||||
MasterKey masterKey = slot.getKey(cipher);
|
||||
creds = new VaultFileCredentials(masterKey, slots);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
repo = VaultRepository.fromFile(getInstrumentation().getContext(), vaultFile, creds);
|
||||
} catch (SlotException | SlotIntegrityException | VaultRepositoryException | VaultFileException | IOException e) {
|
||||
throw new RuntimeException("Unable to read back vault file", e);
|
||||
}
|
||||
|
||||
checkReadEntries(repo.getEntries());
|
||||
return repo;
|
||||
}
|
||||
|
||||
private void readTxtExport(File file) {
|
||||
GoogleAuthUriImporter importer = new GoogleAuthUriImporter(getInstrumentation().getContext());
|
||||
|
||||
Collection<VaultEntry> entries;
|
||||
try (InputStream inStream = new FileInputStream(file)) {
|
||||
DatabaseImporter.State state = importer.read(inStream);
|
||||
DatabaseImporter.Result result = state.convert();
|
||||
entries = result.getEntries().getValues();
|
||||
} catch (DatabaseImporterException | IOException e) {
|
||||
throw new RuntimeException("Unable to read txt export file", e);
|
||||
}
|
||||
|
||||
checkReadEntries(entries);
|
||||
}
|
||||
|
||||
private void checkReadEntries(Collection<VaultEntry> entries) {
|
||||
List<VaultEntry> vectors = VaultEntries.get();
|
||||
assertEquals(vectors.size(), entries.size());
|
||||
|
||||
int i = 0;
|
||||
for (VaultEntry entry : entries) {
|
||||
VaultEntry vector = vectors.get(i);
|
||||
String message = String.format("Entries are not equivalent: (%s) (%s)", vector.toJson().toString(), entry.toJson().toString());
|
||||
assertTrue(message, vector.equivalates(entry));
|
||||
assertEquals(message, vector.getInfo().getOtp(), entry.getInfo().getOtp());
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ import dagger.hilt.android.testing.HiltAndroidTest;
|
|||
public class DeepLinkTest extends AegisTest {
|
||||
@Before
|
||||
public void before() {
|
||||
initVault();
|
||||
initEmptyEncryptedVault();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -26,7 +26,7 @@ import dagger.hilt.android.testing.HiltAndroidTest;
|
|||
public class PanicTriggerTest extends AegisTest {
|
||||
@Before
|
||||
public void before() {
|
||||
initVault();
|
||||
initEncryptedVault();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -24,7 +24,7 @@ import dagger.hilt.android.testing.HiltAndroidTest;
|
|||
public class VaultRepositoryTest extends AegisTest {
|
||||
@Before
|
||||
public void before() {
|
||||
initVault();
|
||||
initEncryptedVault();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../../../../../../test/java/com/beemdevelopment/aegis/vectors/VaultEntries.java
|
|
@ -9,12 +9,16 @@ import android.os.ParcelFileDescriptor;
|
|||
import android.util.Log;
|
||||
|
||||
import com.beemdevelopment.aegis.util.IOUtils;
|
||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class AegisBackupAgent extends BackupAgent {
|
||||
private static final String TAG = AegisBackupAgent.class.getSimpleName();
|
||||
|
@ -46,14 +50,17 @@ public class AegisBackupAgent extends BackupAgent {
|
|||
}
|
||||
|
||||
// first copy the vault to the files/backup directory
|
||||
createBackupDir();
|
||||
File vaultBackupFile = getVaultBackupFile();
|
||||
try {
|
||||
VaultRepository.copyFileTo(this, vaultBackupFile);
|
||||
} catch (IOException e) {
|
||||
try (OutputStream outputStream = new FileOutputStream(vaultBackupFile)) {
|
||||
createBackupDir();
|
||||
|
||||
VaultFile vaultFile = VaultRepository.readVaultFile(this);
|
||||
byte[] bytes = vaultFile.exportable().toBytes();
|
||||
outputStream.write(bytes);
|
||||
} catch (VaultRepositoryException | IOException e) {
|
||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||
deleteBackupDir();
|
||||
throw e;
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
// then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
||||
|
|
|
@ -114,7 +114,7 @@ public class Dialogs {
|
|||
.create());
|
||||
}
|
||||
|
||||
public static void showSetPasswordDialog(ComponentActivity activity, Dialogs.SlotListener listener) {
|
||||
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);
|
||||
|
@ -460,8 +460,8 @@ public class Dialogs {
|
|||
void onTextInputResult(char[] text);
|
||||
}
|
||||
|
||||
public interface SlotListener {
|
||||
void onSlotResult(Slot slot, Cipher cipher);
|
||||
public interface PasswordSlotListener {
|
||||
void onSlotResult(PasswordSlot slot, Cipher cipher);
|
||||
void onException(Exception e);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ import com.beemdevelopment.aegis.vault.VaultBackupManager;
|
|||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||
import com.beemdevelopment.aegis.vault.slots.Slot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.SlotException;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -267,9 +267,9 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
|
|||
if (_vaultManager.getVault().isEncryptionEnabled()) {
|
||||
cb.exportVault(stream -> _vaultManager.getVault().export(stream));
|
||||
} else {
|
||||
Dialogs.showSetPasswordDialog(requireActivity(), new Dialogs.SlotListener() {
|
||||
Dialogs.showSetPasswordDialog(requireActivity(), new Dialogs.PasswordSlotListener() {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||
VaultFileCredentials creds = new VaultFileCredentials();
|
||||
|
||||
try {
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
|||
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
||||
import com.beemdevelopment.aegis.ui.tasks.PasswordSlotDecryptTask;
|
||||
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||
import com.beemdevelopment.aegis.vault.VaultRepositoryException;
|
||||
import com.beemdevelopment.aegis.vault.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
|
@ -43,6 +44,8 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
private Preference _setPasswordPreference;
|
||||
private Preference _passwordReminderPreference;
|
||||
private SwitchPreferenceCompat _pinKeyboardPreference;
|
||||
private SwitchPreference _backupPasswordPreference;
|
||||
private Preference _backupPasswordChangePreference;
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
|
@ -161,7 +164,7 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
|
||||
Dialogs.showPasswordInputDialog(requireContext(), R.string.set_password_confirm, R.string.pin_keyboard_description, password -> {
|
||||
if (isDigitsOnly(new String(password))) {
|
||||
List<PasswordSlot> slots = _vaultManager.getVault().getCredentials().getSlots().findAll(PasswordSlot.class);
|
||||
List<PasswordSlot> slots = _vaultManager.getVault().getCredentials().getSlots().findRegularPasswordSlots();
|
||||
PasswordSlotDecryptTask.Params params = new PasswordSlotDecryptTask.Params(slots, password);
|
||||
PasswordSlotDecryptTask task = new PasswordSlotDecryptTask(requireContext(), new PasswordConfirmationListener());
|
||||
task.execute(getLifecycle(), params);
|
||||
|
@ -232,33 +235,72 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
Dialogs.showSecureDialog(builder.create());
|
||||
return false;
|
||||
});
|
||||
|
||||
_backupPasswordPreference = requirePreference("pref_backup_password");
|
||||
_backupPasswordPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (!isBackupPasswordSet()) {
|
||||
Dialogs.showSetPasswordDialog(requireActivity(), new SetBackupPasswordListener());
|
||||
} else {
|
||||
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
||||
for (Slot slot : slots.findBackupPasswordSlots()) {
|
||||
slots.remove(slot);
|
||||
}
|
||||
|
||||
saveAndBackupVault();
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
_backupPasswordChangePreference = requirePreference("pref_backup_password_change");
|
||||
_backupPasswordChangePreference.setOnPreferenceClickListener(preference -> {
|
||||
Dialogs.showSetPasswordDialog(requireActivity(), new SetBackupPasswordListener());
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateEncryptionPreferences() {
|
||||
boolean encrypted = _vaultManager.getVault().isEncryptionEnabled();
|
||||
boolean backupPasswordSet = isBackupPasswordSet();
|
||||
_encryptionPreference.setChecked(encrypted, true);
|
||||
_setPasswordPreference.setVisible(encrypted);
|
||||
_biometricsPreference.setVisible(encrypted);
|
||||
_autoLockPreference.setVisible(encrypted);
|
||||
_pinKeyboardPreference.setVisible(encrypted);
|
||||
_backupPasswordPreference.getParent().setVisible(encrypted);
|
||||
_backupPasswordPreference.setChecked(backupPasswordSet, true);
|
||||
_backupPasswordChangePreference.setVisible(backupPasswordSet);
|
||||
|
||||
if (encrypted) {
|
||||
SlotList slots = _vaultManager.getVault().getCredentials().getSlots();
|
||||
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
||||
boolean multiBackupPassword = slots.findBackupPasswordSlots().size() > 1;
|
||||
boolean multiPassword = slots.findRegularPasswordSlots().size() > 1;
|
||||
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
||||
boolean canUseBio = BiometricsHelper.isAvailable(requireContext());
|
||||
_setPasswordPreference.setEnabled(!multiPassword);
|
||||
_biometricsPreference.setEnabled(canUseBio && !multiBio);
|
||||
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
|
||||
_passwordReminderPreference.setVisible(slots.has(BiometricSlot.class));
|
||||
_backupPasswordChangePreference.setEnabled(!multiBackupPassword);
|
||||
} else {
|
||||
_setPasswordPreference.setEnabled(false);
|
||||
_biometricsPreference.setEnabled(false);
|
||||
_biometricsPreference.setChecked(false, true);
|
||||
_passwordReminderPreference.setVisible(false);
|
||||
_backupPasswordChangePreference.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBackupPasswordSet() {
|
||||
VaultRepository vault = _vaultManager.getVault();
|
||||
if (!vault.isEncryptionEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return vault.getCredentials().getSlots().findBackupPasswordSlots().size() > 0;
|
||||
}
|
||||
|
||||
private String getPasswordReminderSummary() {
|
||||
PassReminderFreq freq = _prefs.getPasswordReminderFrequency();
|
||||
if (freq == PassReminderFreq.NEVER) {
|
||||
|
@ -291,9 +333,9 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
return getString(R.string.pref_auto_lock_summary, builder.toString());
|
||||
}
|
||||
|
||||
private class SetPasswordListener implements Dialogs.SlotListener {
|
||||
private class SetPasswordListener implements Dialogs.PasswordSlotListener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
|
@ -331,6 +373,43 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private class SetBackupPasswordListener implements Dialogs.PasswordSlotListener {
|
||||
@Override
|
||||
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||
slot.setIsBackup(true);
|
||||
|
||||
VaultFileCredentials creds = _vaultManager.getVault().getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
try {
|
||||
// encrypt the master key for this slot
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
|
||||
// remove the old backup password slot
|
||||
for (Slot oldSlot : slots.findBackupPasswordSlots()) {
|
||||
slots.remove(oldSlot);
|
||||
}
|
||||
|
||||
// add the new backup password slot
|
||||
slots.add(slot);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_vaultManager.getVault().setCredentials(creds);
|
||||
saveAndBackupVault();
|
||||
updateEncryptionPreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
e.printStackTrace();
|
||||
updateEncryptionPreferences();
|
||||
Dialogs.showErrorDialog(requireContext(), R.string.encryption_set_password_error, e);
|
||||
}
|
||||
}
|
||||
|
||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
@Override
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
|
@ -357,9 +436,9 @@ public class SecurityPreferencesFragment extends PreferencesFragment {
|
|||
}
|
||||
}
|
||||
|
||||
private class EnableEncryptionListener implements Dialogs.SlotListener {
|
||||
private class EnableEncryptionListener implements Dialogs.PasswordSlotListener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
public void onSlotResult(PasswordSlot slot, Cipher cipher) {
|
||||
VaultFileCredentials creds = new VaultFileCredentials();
|
||||
|
||||
try {
|
||||
|
|
|
@ -117,6 +117,21 @@ public class VaultFile {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this VaultFile that's suitable for exporting.
|
||||
* In case there's a backup password slot, any regular password slots are stripped.
|
||||
*/
|
||||
public VaultFile exportable() {
|
||||
if (!isEncrypted()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return new VaultFile(getContent(), new VaultFile.Header(
|
||||
getHeader().getSlots().exportable(),
|
||||
getHeader().getParams()
|
||||
));
|
||||
}
|
||||
|
||||
public static class Header {
|
||||
private SlotList _slots;
|
||||
private CryptParameters _params;
|
||||
|
|
|
@ -37,4 +37,12 @@ public class VaultFileCredentials implements Serializable {
|
|||
public SlotList getSlots() {
|
||||
return _slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of these VaultFileCredentials that is suitable for exporting.
|
||||
* In case there's a backup password slot, any regular password slots are stripped.
|
||||
*/
|
||||
public VaultFileCredentials exportable() {
|
||||
return new VaultFileCredentials(_key, _slots.exportable());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -198,7 +200,10 @@ public class VaultManager {
|
|||
}
|
||||
|
||||
File tempFile = File.createTempFile(VaultBackupManager.FILENAME_PREFIX, ".json", dir);
|
||||
VaultRepository.copyFileTo(_context, tempFile);
|
||||
try (OutputStream outStream = new FileOutputStream(tempFile)) {
|
||||
_repo.export(outStream);
|
||||
}
|
||||
|
||||
_backups.scheduleBackup(tempFile, _prefs.getBackupsLocation(), _prefs.getBackupsVersionCount());
|
||||
} catch (IOException e) {
|
||||
throw new VaultRepositoryException(e);
|
||||
|
|
|
@ -107,13 +107,6 @@ public class VaultRepository {
|
|||
return new VaultRepository(context, vault, creds);
|
||||
}
|
||||
|
||||
public static void copyFileTo(Context context, File destFile) throws IOException {
|
||||
try (InputStream inStream = VaultRepository.getAtomicFile(context).openRead();
|
||||
OutputStream outStream = new FileOutputStream(destFile)) {
|
||||
IOUtils.copy(inStream, outStream);
|
||||
}
|
||||
}
|
||||
|
||||
void save() throws VaultRepositoryException {
|
||||
try {
|
||||
JSONObject obj = _vault.toJson();
|
||||
|
@ -137,7 +130,7 @@ public class VaultRepository {
|
|||
}
|
||||
|
||||
/**
|
||||
* Exports the vault bt serializing it and writing it to the given OutputStream. If encryption
|
||||
* Exports the vault by serializing it and writing it to the given OutputStream. If encryption
|
||||
* is enabled, the vault will be encrypted automatically.
|
||||
*/
|
||||
public void export(OutputStream stream) throws VaultRepositoryException {
|
||||
|
@ -149,6 +142,10 @@ public class VaultRepository {
|
|||
* not null, it will be used to encrypt the vault first.
|
||||
*/
|
||||
public void export(OutputStream stream, VaultFileCredentials creds) throws VaultRepositoryException {
|
||||
if (creds != null) {
|
||||
creds = creds.exportable();
|
||||
}
|
||||
|
||||
try {
|
||||
VaultFile vaultFile = new VaultFile();
|
||||
if (creds != null) {
|
||||
|
|
|
@ -16,16 +16,19 @@ import javax.crypto.SecretKey;
|
|||
|
||||
public class PasswordSlot extends RawSlot {
|
||||
private boolean _repaired;
|
||||
private boolean _isBackup;
|
||||
|
||||
private SCryptParameters _params;
|
||||
|
||||
public PasswordSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected PasswordSlot(UUID uuid, byte[] key, CryptParameters keyParams, SCryptParameters scryptParams, boolean repaired) {
|
||||
protected PasswordSlot(UUID uuid, byte[] key, CryptParameters keyParams, SCryptParameters scryptParams, boolean repaired, boolean isBackup) {
|
||||
super(uuid, key, keyParams);
|
||||
_params = scryptParams;
|
||||
_repaired = repaired;
|
||||
_isBackup = isBackup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -37,6 +40,7 @@ public class PasswordSlot extends RawSlot {
|
|||
obj.put("p", _params.getP());
|
||||
obj.put("salt", Hex.encode(_params.getSalt()));
|
||||
obj.put("repaired", _repaired);
|
||||
obj.put("is_backup", _isBackup);
|
||||
return obj;
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -70,8 +74,15 @@ public class PasswordSlot extends RawSlot {
|
|||
return _repaired;
|
||||
}
|
||||
|
||||
public SCryptParameters getSCryptParameters() {
|
||||
return _params;
|
||||
/**
|
||||
* Reports whether this slot is a backup password slot.
|
||||
*/
|
||||
public boolean isBackup() {
|
||||
return _isBackup;
|
||||
}
|
||||
|
||||
public void setIsBackup(boolean isBackup) {
|
||||
_isBackup = isBackup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -140,7 +140,8 @@ public abstract class Slot extends UUIDMap.Value {
|
|||
Hex.decode(obj.getString("salt"))
|
||||
);
|
||||
boolean repaired = obj.optBoolean("repaired", false);
|
||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);
|
||||
boolean isBackup = obj.optBoolean("is_backup", false);
|
||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired, isBackup);
|
||||
break;
|
||||
case Slot.TYPE_BIOMETRIC:
|
||||
slot = new BiometricSlot(uuid, key, keyParams);
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.json.JSONObject;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SlotList extends UUIDMap<Slot> {
|
||||
public JSONArray toJson() {
|
||||
|
@ -54,7 +55,48 @@ public class SlotList extends UUIDMap<Slot> {
|
|||
return list;
|
||||
}
|
||||
|
||||
public List<PasswordSlot> findBackupPasswordSlots() {
|
||||
return findAll(PasswordSlot.class)
|
||||
.stream()
|
||||
.filter(PasswordSlot::isBackup)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<PasswordSlot> findRegularPasswordSlots() {
|
||||
return findAll(PasswordSlot.class)
|
||||
.stream()
|
||||
.filter(s -> !s.isBackup())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public <T extends Slot> boolean has(Class<T> type) {
|
||||
return find(type) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of this SlotList that is suitable for exporting.
|
||||
* In case there's a backup password slot, any regular password slots are stripped.
|
||||
*/
|
||||
public SlotList exportable() {
|
||||
boolean hasBackupSlots = false;
|
||||
for (Slot slot : this) {
|
||||
if (slot instanceof PasswordSlot && ((PasswordSlot) slot).isBackup()) {
|
||||
hasBackupSlots = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasBackupSlots) {
|
||||
return this;
|
||||
}
|
||||
|
||||
SlotList slots = new SlotList();
|
||||
for (Slot slot : this) {
|
||||
if (!(slot instanceof PasswordSlot) || ((PasswordSlot) slot).isBackup()) {
|
||||
slots.add(slot);
|
||||
}
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,11 @@
|
|||
<string name="pref_auto_lock_type_back_button">The back button is pressed</string>
|
||||
<string name="pref_auto_lock_type_minimize">The app is minimized</string>
|
||||
<string name="pref_auto_lock_type_device_lock">The device is locked</string>
|
||||
<string name="pref_backup_password_category">Backup & Export</string>
|
||||
<string name="pref_backup_password_title">Separate password for backup & export</string>
|
||||
<string name="pref_backup_password_summary">If enabled, the password that is used to unlock the app can\'t be used to decrypt backups and exports anymore.</string>
|
||||
<string name="pref_backup_password_change_title">Change password for backup & export</string>
|
||||
<string name="pref_backup_password_change_summary">Set a new password that\'ll be used to encrypt the vault during backup and export.</string>
|
||||
<string name="pref_encryption_title">Encryption</string>
|
||||
<string name="pref_encryption_summary">Encrypt the vault and unlock it with a password or biometrics</string>
|
||||
<string name="pref_biometrics_title">Biometric unlock</string>
|
||||
|
|
|
@ -33,6 +33,23 @@
|
|||
android:dependency="pref_biometrics"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_backup_password_category"
|
||||
app:iconSpaceReserved="false">
|
||||
<com.beemdevelopment.aegis.ui.preferences.SwitchPreference
|
||||
android:key="pref_backup_password"
|
||||
android:title="@string/pref_backup_password_title"
|
||||
android:summary="@string/pref_backup_password_summary"
|
||||
android:persistent="false"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
||||
<Preference
|
||||
android:key="pref_backup_password_change"
|
||||
android:title="@string/pref_backup_password_change_title"
|
||||
android:summary="@string/pref_backup_password_change_summary"
|
||||
android:dependency="pref_encryption"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_section_behavior_title"
|
||||
app:iconSpaceReserved="false">
|
||||
|
|
|
@ -11,8 +11,8 @@ import android.os.Build;
|
|||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.beemdevelopment.aegis.vectors.VaultEntries;
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
|
@ -48,16 +48,8 @@ public class DatabaseImporterTest {
|
|||
*/
|
||||
|
||||
@Before
|
||||
public void initVectors() throws EncodingException, OtpInfoException {
|
||||
_vectors = Lists.newArrayList(
|
||||
new VaultEntry(new TotpInfo(Base32.decode("4SJHB4GSD43FZBAI7C2HLRJGPQ")), "Mason", "Deno"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("5OM4WOOGPLQEF6UGN3CPEOOLWU"), "SHA256", 7, 20), "James", "SPDX"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("7ELGJSGXNCCTV3O6LKJWYFV2RA"), "SHA512", 8, 50), "Elijah", "Airbnb"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("YOOMIXWS5GN6RTBPUFFWKTW5M4"), "SHA1", 6, 1), "James", "Issuu"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("KUVJJOM753IHTNDSZVCNKL7GII"), "SHA256", 7, 50), "Benjamin", "Air Canada"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("5VAML3X35THCEBVRLV24CGBKOY"), "SHA512", 8, 10300), "Mason", "WWE"),
|
||||
new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing")
|
||||
);
|
||||
public void initVectors() {
|
||||
_vectors = VaultEntries.get();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package com.beemdevelopment.aegis.vault.slots;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.crypto.SCryptParameters;
|
||||
|
@ -16,9 +20,6 @@ import javax.crypto.NoSuchPaddingException;
|
|||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class SlotTest {
|
||||
private MasterKey _masterKey;
|
||||
|
||||
|
@ -93,4 +94,24 @@ public class SlotTest {
|
|||
garbledCiphertext[0] = (byte) ~garbledCiphertext[0];
|
||||
assertThrows(SlotIntegrityException.class, () -> slot.getKey(decryptCipher));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordSlotExclusion() {
|
||||
SlotList slots = new SlotList();
|
||||
PasswordSlot passSlot = new PasswordSlot();
|
||||
PasswordSlot passSlot2 = new PasswordSlot();
|
||||
slots.add(passSlot);
|
||||
slots.add(passSlot2);
|
||||
|
||||
assertArrayEquals(slots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||
|
||||
SlotList backupSlots = new SlotList();
|
||||
PasswordSlot backupSlot = new PasswordSlot();
|
||||
backupSlot.setIsBackup(true);
|
||||
slots.add(backupSlot);
|
||||
backupSlots.add(backupSlot);
|
||||
|
||||
assertArrayEquals(backupSlots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||
assertNotEquals(slots.getValues().toArray(), slots.exportable().getValues().toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.beemdevelopment.aegis.vectors;
|
||||
|
||||
import com.beemdevelopment.aegis.encoding.Base32;
|
||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||
import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VaultEntries {
|
||||
private VaultEntries() {
|
||||
|
||||
}
|
||||
|
||||
public static List<VaultEntry> get() {
|
||||
try {
|
||||
return Lists.newArrayList(
|
||||
new VaultEntry(new TotpInfo(Base32.decode("4SJHB4GSD43FZBAI7C2HLRJGPQ")), "Mason", "Deno"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("5OM4WOOGPLQEF6UGN3CPEOOLWU"), "SHA256", 7, 20), "James", "SPDX"),
|
||||
new VaultEntry(new TotpInfo(Base32.decode("7ELGJSGXNCCTV3O6LKJWYFV2RA"), "SHA512", 8, 50), "Elijah", "Airbnb"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("YOOMIXWS5GN6RTBPUFFWKTW5M4"), "SHA1", 6, 1), "James", "Issuu"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("KUVJJOM753IHTNDSZVCNKL7GII"), "SHA256", 7, 50), "Benjamin", "Air Canada"),
|
||||
new VaultEntry(new HotpInfo(Base32.decode("5VAML3X35THCEBVRLV24CGBKOY"), "SHA512", 8, 10300), "Mason", "WWE"),
|
||||
new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing")
|
||||
);
|
||||
} catch (OtpInfoException | EncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue