Compare commits

..
60 Commits
Author SHA1 Message Date
Alexander Bakker f4678e2813 Release v0.3.3 2019-04-17 13:40:57 +02:00
Michael Schättgen b958145761 Fix bug where parsing otp codes was impossible
Y I K E S
2019-04-17 13:37:53 +02:00
Alexander Bakker feea9a2e67 Fix crash when trying to auto lock a locked vault 2019-04-17 08:45:42 +02:00
Alexander Bakker ebb9d0be3f Make app importer path lookup more dynamic
Also fixes a crash in SteamAppImporter that occurred with empty dirs
2019-04-17 01:32:20 +02:00
Alexander Bakker f5cbec21f4 Release v0.3.2 2019-04-17 00:00:41 +02:00
Alexander Bakker e55d6111cf Fix bold in full_description.txt for F-Droid 2019-04-17 00:00:10 +02:00
Alexander Bakker cb01fb21d3 Remove extra translations to fix lint errors in release mode 2019-04-16 23:48:50 +02:00
Alexander Bakker c5be99e58d Release v0.3.1 2019-04-16 23:38:05 +02:00
Alexander Bakker 92458d0d3b Move setGlobalDurationScale to AegisActivity
Also, rename the method and make the error string translatable
2019-04-16 23:24:39 +02:00
Michael SchättgenandGitHub 5d835b05ee Merge pull request #56 from ByteHamster/krop-color
Darkened Krop color
2019-04-16 22:26:55 +02:00
Michael Schättgen 2a47fa4de5 Add method to set global duration scale
Fixes #35
2019-04-16 22:12:49 +02:00
Alexander BakkerandGitHub 553cb16bc4 Merge pull request #57 from ByteHamster/arrays
Strings.xml cleanup
2019-04-14 18:03:16 +02:00
ByteHamster 47f8c9e648 Strings cleanup
- Removed unused strings
- Moved arrays to their own file, so translators do not have to deal with arrays
2019-04-14 17:18:14 +02:00
Alexander BakkerandGitHub 791138ccab Merge pull request #55 from ByteHamster/focus-password
Show keyboard automatically
2019-04-14 15:57:00 +02:00
Alexander BakkerandGitHub 6cbef27cfb Merge pull request #58 from ByteHamster/master
Added F-droid icon to readme
2019-04-14 15:56:21 +02:00
H. LehmannandGitHub e5f7dce9b8 Added F-droid icon 2019-04-14 14:24:53 +02:00
ByteHamster a415ef573e Show keyboard automatically 2019-04-14 14:15:34 +02:00
ByteHamster ffa2577cc2 Darkened Krop color 2019-04-14 00:47:21 +02:00
Alexander Bakker 2a0daae155 Fix crash that occurred when scheme of the URI in a QR code is missing 2019-04-10 11:42:35 +02:00
Michael Schättgen b41e328d22 Add ability to translate themes and viewmodes
Closes #50
2019-04-09 17:53:10 +02:00
Michael SchättgenandGitHub 1ac42d85d6 Merge pull request #53 from alexbakker/feature-autolock
Add an option to automatically lock the app
2019-04-09 17:34:47 +02:00
Michael SchättgenandGitHub 685a791aba Merge pull request #49 from zaggo0/master
Add Dutch translations
2019-04-09 17:34:35 +02:00
Michael Schättgen 89a1663e28 Fix bug where entries didn't get sorted properly 2019-04-09 17:15:25 +02:00
Alexander Bakker 0563ac917a Fix some rare crashes when restoring the app after termination
Also fixes an issue where the app shortcuts would not work for unencrypted vaults
2019-04-08 23:13:11 +02:00
Patrick van der Linden 719ef3bc71 Add Dutch translations
+ Fix preference groups not being translated
2019-04-08 15:00:24 +02:00
Alexander Bakker 18fd88a441 Add an option to automatically lock the app
This adds an option to automatically lock the app when:
* The back button is pressed
* The device is locked

It's the first step towards implementing #7
2019-04-07 18:36:13 +02:00
Alexander Bakker 6d93b78f9a Fix a crash on KitKat when clearing the KeyStore
Apparently KitKat doesn't like KeyPermanentlyInvalidatedException
2019-04-04 18:49:57 +02:00
Alexander Bakker 4365a693f2 Get Cipher back from FingerprintManager,CryptoObject after auth 2019-04-04 18:18:34 +02:00
Alexander Bakker 14fa493ac9 Remove keys from the KeyStore when disabling encryption or fingerprint 2019-04-04 15:27:24 +02:00
Alexander Bakker 36e3dd559c Normalize formatting and 'optimize' imports 2019-04-04 14:07:36 +02:00
Alexander Bakker b681d57b4e Update testdata file to use 'test' as the password 2019-04-03 15:03:56 +02:00
Alexander Bakker c5fa40166f Add some info about verifying APK's obtained from GitHub to the README 2019-04-02 21:37:49 +02:00
Michael Schättgen b7cf234db6 Add smaller view mode
Closes #39
2019-04-02 15:47:07 +02:00
Alexander Bakker f27dc54ec4 Release v0.3 2019-04-01 23:50:07 +02:00
Alexander Bakker 4c1dec613c Fallback to a 30 second period if an andOTP doesn't provide one 2019-04-01 23:48:44 +02:00
Alexander Bakker a251685002 Add a dialog that displays a list of errors that occurred during import 2019-04-01 23:36:31 +02:00
Alexander Bakker 118221e3df Add support for Steam tokens to the andOTP importer
Close #44
2019-04-01 22:37:56 +02:00
Alexander Bakker 1dd5f893da Add support for Steam OTP 2019-04-01 22:37:52 +02:00
Michael Schättgen a46640f43d Fix recreate when setting view mode 2019-04-01 21:47:41 +02:00
Michael Schättgen a293078603 Add way to set view mode 2019-04-01 01:44:38 +02:00
Michael Schättgen f3ed79dc71 Add ability to change view mode 2019-04-01 01:21:12 +02:00
Michael Schättgen ee8fd2e9f2 Hide divider in compact mode when progressbar is visible 2019-04-01 01:04:26 +02:00
Michael Schättgen 8b9ec5f976 Start working on compact mode 2019-04-01 00:58:45 +02:00
Michael Schättgen d283eae7a0 Add russian strings for authentication methods
#43
2019-04-01 00:18:44 +02:00
Michael SchättgenandGitHub a854db760c Merge pull request #41 from ferhadnecef/stringlate-ru-1048
Updated ru (Russian) translation
2019-03-31 23:57:36 +02:00
Michael Schättgen caf2ec882a Merge branch 'feature-sorting' 2019-03-31 23:30:52 +02:00
Michael Schättgen bbf967e2b7 Add a way to save current sorting method 2019-03-31 22:34:25 +02:00
Michael Schättgen b9d011b48e Disable drag and drop when list is sorted 2019-03-31 21:28:37 +02:00
Michael Schättgen ecdadf5335 Add SortCategory fixes and support for custom sorting 2019-03-31 21:23:14 +02:00
Michael Schättgen 10b80442d0 Comparators will now ignore casing 2019-03-31 21:22:20 +02:00
Alexander Bakker a2879a4558 Fix the screenshot links in the README 2019-03-31 20:39:41 +02:00
Michael Schättgen 62025d1053 Add way to check for reversed sort categories 2019-03-31 20:14:11 +02:00
Michael Schättgen 09fe550a28 Add AccountNameComparator 2019-03-31 20:12:34 +02:00
Michael Schättgen 07aac1e6a3 Add support for reversed sorting methods 2019-03-31 20:12:25 +02:00
Michael Schättgen b8939b504e Start working on entry sorting 2019-03-31 19:57:45 +02:00
Alexander Bakker 4f29d0c2e4 Fix metadata files for F-Droid (for real this time) 2019-03-31 18:21:10 +02:00
Alexander Bakker 592c6683c3 Continue importing entries even if one can't be parsed 2019-03-30 18:26:16 +01:00
ferhad.necef f8edac72ea Updated ru (Russian) translation 2019-03-29 16:00:49 +03:00
Alexander Bakker fc0e1150f6 Replace hardcoded encoding names with constants 2019-03-28 20:01:00 +01:00
Michael SchättgenandGitHub e1fd913817 Update README.md 2019-03-28 13:15:18 +00:00
90 changed files with 1945 additions and 519 deletions
+57 -12
View File
@@ -8,7 +8,7 @@ Aegis is a free, secure and open source 2FA app for Android.
- Secure
- Encryption (AES-256)
- Password (scrypt)
- Fingerprint (Android Keystore)
- Fingerprint (Android Keystore)
- Screen capture prevention
- Tap to reveal ability
- Multiple ways to add new entries
@@ -20,9 +20,12 @@ Aegis is a free, secure and open source 2FA app for Android.
- Aegis
- Import from apps (requires root):
- Google Authenticator
- Steam
- Supported algorithms:
- HOTP ([RFC 4226](https://tools.ietf.org/html/rfc4226))
- TOTP ([RFC 6238](https://tools.ietf.org/html/rfc6238))
- Steam ([RFC 6238](https://tools.ietf.org/html/rfc6238) with custom
encoding)
- Compatible with Google Authenticator
- Organization
- Custom or default icons
@@ -37,36 +40,78 @@ Aegis is a free, secure and open source 2FA app for Android.
## Screenshots
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true)
[<img width=200 alt="Settings Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true)
[<img width=200 alt="Edit Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true)
[<img width=200 alt="Main Activity"
src="metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main.png?raw=true)
[<img width=200 alt="Settings Activity"
src="metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_settings.png?raw=true)
[<img width=200 alt="Edit Activity"
src="metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true">](/metadata/en-US/images/phoneScreenshots/screenshot_edit.png?raw=true)
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true)
[<img width=200 alt="Main Activity" src="metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true)
[<img width=200 alt="Main Activity"
src="metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_group.png?raw=true)
[<img width=200 alt="Main Activity"
src="metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true">](metadata/en-US/images/phoneScreenshots/screenshot_main_dark.png?raw=true)
## Downloads
Aegis is available in the Google Play Store.
Aegis is available on the Google Play Store and on F-Droid.
[<img height=80 alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" />](http://play.google.com/store/apps/details?id=com.beemdevelopment.aegis)
[<img height=80 alt="Get it on Google Play"
src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png"
/>](http://play.google.com/store/apps/details?id=com.beemdevelopment.aegis)
[<img height="80" alt="Get it on F-Droid"
src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
/>](https://f-droid.org/app/com.beemdevelopment.aegis)
### Verification
There are plans to make it available on F-Droid as well.
APK releases on Google Play and GitHub are signed using the same key. They can
be verified using
[apksigner](https://developer.android.com/studio/command-line/apksigner.html#options-verify):
```
apksigner verify --print-certs --verbose aegis.apk
```
The output should look like:
```
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
```
The certificate fingerprints should correspond to the ones listed below:
```
Owner: CN=Beem Development
Issuer: CN=Beem Development
Serial number: 172380c
Valid from: Sat Feb 09 14:05:49 CET 2019 until: Wed Feb 03 14:05:49 CET 2044
Certificate fingerprints:
MD5: AA:EE:86:DB:C7:B8:88:9F:1F:C9:D0:7A:EC:37:36:32
SHA1: 59:FB:63:B7:1F:CE:95:74:6C:EB:1E:1A:CB:2C:2E:45:E5:FF:13:50
SHA256: C6:DB:80:A8:E1:4E:52:30:C1:DE:84:15:EF:82:0D:13:DC:90:1D:8F:E3:3C:F3:AC:B5:7B:68:62:D8:58:A8:23
```
## Libraries
- [TextDrawable](https://github.com/amulyakhare/TextDrawable) by Amulya Khare
- [FloatingActionButton](https://github.com/Clans/FloatingActionButton) by Dmytro Tarianyk
- [FloatingActionButton](https://github.com/Clans/FloatingActionButton) by
Dmytro Tarianyk
- [AppIntro](https://github.com/AppIntro/AppIntro) by Paolo Rotolo
- [Krop](https://github.com/avito-tech/krop) by Avito Technology
- [SpongyCastle](https://github.com/rtyley/spongycastle) by Roberto Tyley
- [Swirl](https://github.com/mattprecious/swirl) by Matthew Precious
- [CircleImageView](https://github.com/hdodenhof/CircleImageView) by Henning Dodenhof
- [CircleImageView](https://github.com/hdodenhof/CircleImageView) by Henning
Dodenhof
- [barcodescanner](https://github.com/dm77/barcodescanner) by Dushyanth
- [libsu](https://github.com/topjohnwu/libsu) by John Wu
## License
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details.
This project is licensed under the GNU General Public License v3.0. See the
[LICENSE](LICENSE) file for details.
## Attribution
+3 -3
View File
@@ -7,8 +7,8 @@ android {
applicationId "com.beemdevelopment.aegis"
minSdkVersion 19
targetSdkVersion 28
versionCode 5
versionName "0.2.2"
versionCode 9
versionName "0.3.3"
}
buildTypes {
debug {
@@ -41,7 +41,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'com.github.apl-devs:appintro:v4.2.2'
implementation 'com.github.avito-tech:krop:3e65e12'
implementation 'com.github.avito-tech:krop:0.43'
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'com.mattprecious.swirl:swirl:1.0.0'
implementation 'de.hdodenhof:circleimageview:2.2.0'
+4 -7
View File
@@ -15,11 +15,11 @@
android:label="Aegis"
android:supportsRtl="true"
tools:replace="android:theme"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/AppTheme.NoActionBar"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.beemdevelopment.aegis.ui.MainActivity"
android:label="${title}"
android:alwaysRetainTaskState="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
@@ -40,12 +40,9 @@
</activity>
<activity
android:name="com.beemdevelopment.aegis.ui.IntroActivity"
android:theme="@style/Theme.Intro"
android:launchMode="singleTop">
android:theme="@style/Theme.Intro">
</activity>
<activity
android:name="com.beemdevelopment.aegis.ui.AuthActivity"
android:launchMode="singleTop">
<activity android:name="com.beemdevelopment.aegis.ui.AuthActivity">
</activity>
<activity android:name="com.beemdevelopment.aegis.ui.PreferencesActivity">
</activity>
@@ -1,7 +1,10 @@
package com.beemdevelopment.aegis;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
@@ -10,10 +13,10 @@ import android.os.Build;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.ui.MainActivity;
import androidx.annotation.RequiresApi;
import java.util.Collections;
import androidx.annotation.RequiresApi;
public class AegisApplication extends Application {
private DatabaseManager _manager;
private Preferences _prefs;
@@ -24,6 +27,10 @@ public class AegisApplication extends Application {
_manager = new DatabaseManager(this);
_prefs = new Preferences(this);
// listen for SCREEN_OFF events
ScreenOffReceiver receiver = new ScreenOffReceiver();
registerReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
initAppShortcuts();
}
@@ -57,4 +64,20 @@ public class AegisApplication extends Application {
shortcutManager.setDynamicShortcuts(Collections.singletonList(shortcut));
}
public boolean isAutoLockEnabled() {
return _prefs.isAutoLockEnabled() && _manager.isLoaded() && _manager.isEncryptionEnabled() && !_manager.isLocked();
}
private class ScreenOffReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (isAutoLockEnabled()) {
_manager.lock();
Intent newIntent = new Intent(getApplicationContext(), MainActivity.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(newIntent);
}
}
}
}
@@ -11,10 +11,6 @@ public class Preferences {
_prefs = PreferenceManager.getDefaultSharedPreferences(context);
}
public boolean isDarkModeEnabled() {
return _prefs.getBoolean("pref_dark_mode", false);
}
public boolean isTapToRevealEnabled() {
return _prefs.getBoolean("pref_tap_to_reveal", false);
}
@@ -31,6 +27,10 @@ public class Preferences {
return _prefs.getBoolean("pref_intro", false);
}
public boolean isAutoLockEnabled() {
return _prefs.getBoolean("pref_auto_lock", true);
}
public void setIntroDone(boolean done) {
_prefs.edit().putBoolean("pref_intro", done).apply();
}
@@ -39,6 +39,14 @@ public class Preferences {
_prefs.edit().putInt("pref_tap_to_reveal_time", number).apply();
}
public void setCurrentSortCategory(SortCategory category) {
_prefs.edit().putInt("pref_current_sort_category", category.ordinal()).apply();
}
public int getCurrentSortCategory() {
return _prefs.getInt("pref_current_sort_category", 0);
}
public int getTapToRevealTime() {
return _prefs.getInt("pref_tap_to_reveal_time", 30);
}
@@ -49,9 +57,15 @@ public class Preferences {
public void setCurrentTheme(Theme theme) {
_prefs.edit().putInt("pref_current_theme", theme.ordinal()).apply();
}
public int getCurrentViewMode() {
return _prefs.getInt("pref_current_view_mode", 0);
}
public void setCurrentViewMode(ViewMode viewMode) {
_prefs.edit().putInt("pref_current_view_mode", viewMode.ordinal()).apply();
}
public int getTimeout() {
return _prefs.getInt("pref_timeout", -1);
@@ -0,0 +1,72 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.helpers.comparators.AccountNameComparator;
import com.beemdevelopment.aegis.helpers.comparators.IssuerNameComparator;
import java.util.Comparator;
public enum SortCategory {
CUSTOM,
ACCOUNT,
ACCOUNTREVERSED,
ISSUER,
ISSUERREVERSED;
public static SortCategory fromInteger(int x) {
switch (x) {
case 0:
return CUSTOM;
case 1:
return ACCOUNT;
case 2:
return ACCOUNTREVERSED;
case 3:
return ISSUER;
case 4:
return ISSUERREVERSED;
}
return null;
}
public static Comparator getComparator(SortCategory sortCategory) {
switch (sortCategory) {
case ACCOUNT:
case ACCOUNTREVERSED:
return new AccountNameComparator();
case ISSUER:
case ISSUERREVERSED:
return new IssuerNameComparator();
case CUSTOM:
return new IssuerNameComparator();
}
return null;
}
public static boolean isReversed(SortCategory sortCategory) {
switch (sortCategory) {
case ACCOUNTREVERSED:
case ISSUERREVERSED:
return true;
default:
return false;
}
}
public static int getMenuItem(SortCategory sortCategory) {
switch (sortCategory) {
case CUSTOM:
return R.id.menu_sort_custom;
case ACCOUNT:
return R.id.menu_sort_alphabetically_name;
case ACCOUNTREVERSED:
return R.id.menu_sort_alphabetically_name_reverse;
case ISSUER:
return R.id.menu_sort_alphabetically;
case ISSUERREVERSED:
return R.id.menu_sort_alphabetically_reverse;
default:
return R.id.menu_sort_custom;
}
}
}
@@ -6,7 +6,7 @@ public enum Theme {
AMOLED;
public static Theme fromInteger(int x) {
switch(x) {
switch (x) {
case 0:
return LIGHT;
case 1:
@@ -17,23 +17,31 @@ public enum Theme {
return null;
}
public static String getThemeName(int x) {
switch(x) {
public static int getThemeNameResource(int x) {
switch (x) {
case 0:
return "Light theme";
return R.string.light_theme_title;
case 1:
return "Dark theme";
return R.string.dark_theme_title;
case 2:
return "Amoled theme";
return R.string.amoled_theme_title;
}
return null;
return R.string.light_theme_title;
}
public static String[] getThemeNames() {
return new String[] {
return new String[]{
"Light theme",
"Dark theme",
"Amoled theme"
};
}
public static int[] getThemeNameResources() {
return new int[] {
R.string.light_theme_title,
R.string.dark_theme_title,
R.string.amoled_theme_title
};
}
}
@@ -0,0 +1,48 @@
package com.beemdevelopment.aegis;
public enum ViewMode {
NORMAL,
COMPACT,
SMALL;
public static ViewMode fromInteger(int x) {
switch (x) {
case 0:
return NORMAL;
case 1:
return COMPACT;
case 2:
return SMALL;
}
return null;
}
public static int getViewModeNameResource(int x) {
switch (x) {
case 0:
return R.string.normal_viewmode_title;
case 1:
return R.string.compact_mode_title;
case 2:
return R.string.small_mode_title;
}
return R.string.normal_viewmode_title;
}
public static String[] getViewModeNames() {
return new String[]{
"Normal",
"Compact",
"Small"
};
}
public static int[] getViewModeNameResources() {
return new int[] {
R.string.normal_viewmode_title,
R.string.compact_mode_title,
R.string.small_mode_title
};
}
}
@@ -2,11 +2,13 @@ package com.beemdevelopment.aegis.crypto;
import android.os.Build;
import org.spongycastle.crypto.generators.SCrypt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -24,8 +26,6 @@ import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.spongycastle.crypto.generators.SCrypt;
public class CryptoUtils {
public static final String CRYPTO_AEAD = "AES/GCM/NoPadding";
public static final byte CRYPTO_AEAD_KEY_SIZE = 32;
@@ -122,7 +122,7 @@ public class CryptoUtils {
private static byte[] toBytes(char[] chars) {
CharBuffer charBuf = CharBuffer.wrap(chars);
ByteBuffer byteBuf = Charset.forName("UTF-8").encode(charBuf);
ByteBuffer byteBuf = StandardCharsets.UTF_8.encode(charBuf);
return byteBuf.array();
}
}
@@ -14,12 +14,15 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Collections;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import androidx.annotation.RequiresApi;
public class KeyStoreHandle {
private final KeyStore _keyStore;
private static final String STORE_NAME = "AndroidKeyStore";
@@ -76,22 +79,33 @@ public class KeyStoreHandle {
throw new KeyStoreHandleException(e);
}
// try to initialize a dummy cipher
// and see if KeyPermanentlyInvalidatedException is thrown
if (isSupported()) {
try {
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD);
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (KeyPermanentlyInvalidatedException e) {
return null;
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
if (isSupported() && isKeyPermanentlyInvalidated(key)) {
return null;
}
return key;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean isKeyPermanentlyInvalidated(SecretKey key) {
// try to initialize a dummy cipher
// and see if KeyPermanentlyInvalidatedException is thrown
try {
Cipher cipher = Cipher.getInstance(CryptoUtils.CRYPTO_AEAD);
cipher.init(Cipher.ENCRYPT_MODE, key);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
// apparently KitKat doesn't like KeyPermanentlyInvalidatedException, even when guarded with a version check
// it will throw a java.lang.VerifyError when its listed in a 'catch' statement
// so instead, check for it here
if (e instanceof KeyPermanentlyInvalidatedException) {
return true;
}
throw new RuntimeException(e);
}
return false;
}
public void deleteKey(String id) throws KeyStoreHandleException {
try {
_keyStore.deleteEntry(id);
@@ -100,6 +114,16 @@ public class KeyStoreHandle {
}
}
public void clear() throws KeyStoreHandleException {
try {
for (String alias : Collections.list(_keyStore.aliases())) {
deleteKey(alias);
}
} catch (KeyStoreException e) {
throw new KeyStoreHandleException(e);
}
}
public static boolean isSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@@ -2,8 +2,8 @@ package com.beemdevelopment.aegis.crypto.otp;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -12,7 +12,7 @@ public class HOTP {
private HOTP() {
}
public static String generateOTP(byte[] secret, String algo, int digits, long counter)
public static OTP generateOTP(byte[] secret, String algo, int digits, long counter)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKeySpec key = new SecretKeySpec(secret, "RAW");
@@ -30,18 +30,11 @@ public class HOTP {
// truncate hash to get the HTOP value
// http://tools.ietf.org/html/rfc4226#section-5.4
int offset = hash[hash.length - 1] & 0xf;
long bin = ((hash[offset] & 0x7f) << 24)
int otp = ((hash[offset] & 0x7f) << 24)
| ((hash[offset + 1] & 0xff) << 16)
| ((hash[offset + 2] & 0xff) << 8)
| (hash[offset + 3] & 0xff);
long otp = bin % (long) Math.pow(10, digits);
// prepend zeroes if needed
StringBuilder res = new StringBuilder(Long.toString(otp));
while (res.length() < digits) {
res.insert(0, "0");
}
return res.toString();
return new OTP(otp, digits);
}
}
@@ -0,0 +1,50 @@
package com.beemdevelopment.aegis.crypto.otp;
import androidx.annotation.NonNull;
public class OTP {
private static final String STEAM_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
private int _code;
private int _digits;
public OTP(int code, int digits) {
_code = code;
_digits = digits;
}
public int getCode() {
return _code;
}
public int getDigits() {
return _digits;
}
@NonNull
@Override
public String toString() {
int code = _code % (int) Math.pow(10, _digits);
// prepend zeroes if needed
StringBuilder res = new StringBuilder(Long.toString(code));
while (res.length() < _digits) {
res.insert(0, "0");
}
return res.toString();
}
public String toSteamString() {
int code = _code;
StringBuilder res = new StringBuilder();
for (int i = 0; i < _digits; i++) {
char c = STEAM_ALPHABET.charAt(code % STEAM_ALPHABET.length());
res.append(c);
code /= STEAM_ALPHABET.length();
}
return res.toString();
}
}
@@ -8,13 +8,13 @@ public class TOTP {
private TOTP() {
}
public static String generateOTP(byte[] secret, String algo, int digits, long period, long seconds)
public static OTP generateOTP(byte[] secret, String algo, int digits, long period, long seconds)
throws InvalidKeyException, NoSuchAlgorithmException {
long counter = (long) Math.floor((double) seconds / period);
return HOTP.generateOTP(secret, algo, digits, counter);
}
public static String generateOTP(byte[] secret, String algo, int digits, long period)
public static OTP generateOTP(byte[] secret, String algo, int digits, long period)
throws InvalidKeyException, NoSuchAlgorithmException {
return generateOTP(secret, algo, digits, period, System.currentTimeMillis() / 1000);
}
@@ -1,7 +1,5 @@
package com.beemdevelopment.aegis.db;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
@@ -9,6 +7,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.UUID;
import androidx.annotation.NonNull;
public class DatabaseEntryList implements Iterable<DatabaseEntry>, Serializable {
private List<DatabaseEntry> _entries = new ArrayList<>();
@@ -12,7 +12,7 @@ import com.beemdevelopment.aegis.encoding.HexException;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class DatabaseFile {
public static final byte VERSION = 1;
@@ -54,8 +54,8 @@ public class DatabaseFile {
try {
String string = obj.toString(4);
return string.getBytes("UTF-8");
} catch (JSONException | UnsupportedEncodingException e) {
return string.getBytes(StandardCharsets.UTF_8);
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@@ -79,9 +79,9 @@ public class DatabaseFile {
public static DatabaseFile fromBytes(byte[] data) throws DatabaseFileException {
try {
JSONObject obj = new JSONObject(new String(data, "UTF-8"));
JSONObject obj = new JSONObject(new String(data, StandardCharsets.UTF_8));
return DatabaseFile.fromJson(obj);
} catch (UnsupportedEncodingException | JSONException e) {
} catch (JSONException e) {
throw new DatabaseFileException(e);
}
}
@@ -94,8 +94,8 @@ public class DatabaseFile {
try {
byte[] bytes = Base64.decode((String) _content);
CryptResult result = creds.decrypt(bytes, _header.getParams());
return new JSONObject(new String(result.getData(), "UTF-8"));
} catch (MasterKeyException | JSONException | UnsupportedEncodingException | Base64Exception e) {
return new JSONObject(new String(result.getData(), StandardCharsets.UTF_8));
} catch (MasterKeyException | JSONException | Base64Exception e) {
throw new DatabaseFileException(e);
}
}
@@ -108,12 +108,12 @@ public class DatabaseFile {
public void setContent(JSONObject obj, DatabaseFileCredentials creds) throws DatabaseFileException {
try {
String string = obj.toString(4);
byte[] dbBytes = string.getBytes("UTF-8");
byte[] dbBytes = string.getBytes(StandardCharsets.UTF_8);
CryptResult result = creds.encrypt(dbBytes);
_content = Base64.encode(result.getData());
_header = new Header(creds.getSlots(), result.getParams());
} catch (MasterKeyException | UnsupportedEncodingException | JSONException e) {
} catch (MasterKeyException | JSONException e) {
throw new DatabaseFileException(e);
}
}
@@ -4,11 +4,10 @@ import com.beemdevelopment.aegis.crypto.CryptParameters;
import com.beemdevelopment.aegis.crypto.CryptResult;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.crypto.MasterKeyException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import java.io.Serializable;
import com.beemdevelopment.aegis.db.slots.SlotList;
public class DatabaseFileCredentials implements Serializable {
private MasterKey _key;
private SlotList _slots;
@@ -3,6 +3,9 @@ package com.beemdevelopment.aegis.db;
import android.content.Context;
import android.os.Environment;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
import org.json.JSONObject;
import java.io.DataInputStream;
@@ -15,9 +18,6 @@ import java.util.List;
import java.util.TreeSet;
import java.util.UUID;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
public class DatabaseManager {
private static final String FILENAME = "aegis.json";
private static final String FILENAME_EXPORT = "aegis_export.json";
@@ -1,6 +1,6 @@
package com.beemdevelopment.aegis.encoding;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
public class Base64 {
private static final int _flags = android.util.Base64.NO_WRAP;
@@ -19,11 +19,6 @@ public class Base64 {
public static String encode(byte[] data) {
byte[] encoded = android.util.Base64.encode(data, _flags);
try {
return new String(encoded, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return new String(encoded, StandardCharsets.UTF_8);
}
}
@@ -22,12 +22,12 @@ package com.beemdevelopment.aegis.helpers;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.CancellationSignal;
import androidx.annotation.RequiresApi;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.mattprecious.swirl.SwirlView;
import com.beemdevelopment.aegis.R;
import androidx.annotation.RequiresApi;
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
@@ -3,12 +3,13 @@ package com.beemdevelopment.aegis.helpers;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class PermissionHelper {
private PermissionHelper() {
@@ -1,7 +1,7 @@
package com.beemdevelopment.aegis.helpers;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
@@ -1,12 +1,13 @@
package com.beemdevelopment.aegis.helpers;
import android.content.Context;
import androidx.annotation.ArrayRes;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import java.util.List;
import androidx.annotation.ArrayRes;
public class SpinnerHelper {
private SpinnerHelper() {
@@ -0,0 +1,12 @@
package com.beemdevelopment.aegis.helpers.comparators;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.Comparator;
public class AccountNameComparator implements Comparator<DatabaseEntry> {
@Override
public int compare(DatabaseEntry a, DatabaseEntry b) {
return a.getName().compareToIgnoreCase(b.getName());
}
}
@@ -0,0 +1,12 @@
package com.beemdevelopment.aegis.helpers.comparators;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.Comparator;
public class IssuerNameComparator implements Comparator<DatabaseEntry> {
@Override
public int compare(DatabaseEntry a, DatabaseEntry b) {
return a.getIssuer().compareToIgnoreCase(b.getIssuer());
}
}
@@ -2,18 +2,18 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import org.json.JSONObject;
import java.util.List;
import com.beemdevelopment.aegis.db.Database;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseException;
import com.beemdevelopment.aegis.db.DatabaseFile;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseFileException;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.util.ByteInputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class AegisFileImporter extends DatabaseFileImporter {
private DatabaseFileCredentials _creds;
private DatabaseFile _file;
@@ -33,7 +33,9 @@ public class AegisFileImporter extends DatabaseFileImporter {
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
public DatabaseImporterResult convert() throws DatabaseImporterException {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
JSONObject obj;
if (_file.isEncrypted() && _creds != null) {
@@ -42,11 +44,29 @@ public class AegisFileImporter extends DatabaseFileImporter {
obj = _file.getContent();
}
Database db = Database.fromJson(obj);
return db.getEntries();
} catch (DatabaseException | DatabaseFileException e) {
JSONArray array = obj.getJSONArray("entries");
for (int i = 0; i < array.length(); i++) {
JSONObject entryObj = array.getJSONObject(i);
try {
DatabaseEntry entry = convertEntry(entryObj);
result.addEntry(entry);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
}
} catch (JSONException | DatabaseFileException e) {
throw new DatabaseImporterException(e);
}
return result;
}
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
try {
return DatabaseEntry.fromJson(obj);
} catch (JSONException | OtpInfoException | Base64Exception e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}
@Override
@@ -2,23 +2,22 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.ByteInputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
public class AndOtpFileImporter extends DatabaseFileImporter {
private JSONArray _obj;
@@ -29,55 +28,68 @@ public class AndOtpFileImporter extends DatabaseFileImporter {
@Override
public void parse() throws DatabaseImporterException {
try {
_obj = new JSONArray(new String(_stream.getBytes(), "UTF-8"));
_obj = new JSONArray(new String(_stream.getBytes(), StandardCharsets.UTF_8));
} catch (JSONException e) {
throw new DatabaseImporterException(e);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
List<DatabaseEntry> entries = new ArrayList<>();
public DatabaseImporterResult convert() throws DatabaseImporterException {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
for (int i = 0; i < _obj.length(); i++) {
for (int i = 0; i < _obj.length(); i++) {
try {
JSONObject obj = _obj.getJSONObject(i);
String type = obj.getString("type").toLowerCase();
String algo = obj.getString("algorithm");
int digits = obj.getInt("digits");
byte[] secret = Base32.decode(obj.getString("secret").toCharArray());
OtpInfo info;
if (type.equals("totp")) {
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
} else if (type.equals("hotp")) {
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
} else {
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String issuer = "";
String name = "";
String[] parts = obj.getString("label").split(" - ");
if (parts.length > 1) {
issuer = parts[0];
name = parts[1];
} else {
name = parts[0];
}
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
entries.add(entry);
DatabaseEntry entry = convertEntry(obj);
result.addEntry(entry);
} catch (JSONException e) {
throw new DatabaseImporterException(e);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
} catch (Base32Exception | OtpInfoException | JSONException e) {
throw new DatabaseImporterException(e);
}
return entries;
return result;
}
private static DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
try {
String type = obj.getString("type").toLowerCase();
String algo = obj.getString("algorithm");
int digits = obj.getInt("digits");
byte[] secret = Base32.decode(obj.getString("secret").toCharArray());
OtpInfo info;
switch (type) {
case "hotp":
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
break;
case "totp":
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
break;
case "steam":
info = new SteamInfo(secret, algo, digits, obj.optInt("period", 30));
break;
default:
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String name;
String issuer = "";
String[] parts = obj.getString("label").split(" - ");
if (parts.length > 1) {
issuer = parts[0];
name = parts[1];
} else {
name = parts[0];
}
return new DatabaseEntry(info, name, issuer);
} catch (DatabaseImporterException | Base32Exception | OtpInfoException | JSONException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}
@Override
@@ -1,13 +1,13 @@
package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.content.pm.PackageManager;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.topjohnwu.superuser.io.SuFile;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class DatabaseAppImporter implements DatabaseImporter {
@@ -16,18 +16,31 @@ public abstract class DatabaseAppImporter implements DatabaseImporter {
// note: keep this list sorted alphabetically
LinkedHashMap<String, Class<? extends DatabaseAppImporter>> importers = new LinkedHashMap<>();
importers.put("Google Authenticator", GoogleAuthAppImporter.class);
importers.put("Steam", SteamAppImporter.class);
_importers = Collections.unmodifiableMap(importers);
}
private SuFile _path;
private Context _context;
protected DatabaseAppImporter(Context context) {
protected DatabaseAppImporter(Context context, String pkgName, String subPath) throws DatabaseImporterException {
_context = context;
try {
PackageManager man = context.getPackageManager();
_path = new SuFile(man.getApplicationInfo(pkgName, 0).dataDir, subPath);
} catch (PackageManager.NameNotFoundException e) {
throw new DatabaseImporterException(e);
}
}
protected SuFile getPath() {
return _path;
}
public abstract void parse() throws DatabaseImporterException;
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract DatabaseImporterResult convert() throws DatabaseImporterException;
public abstract boolean isEncrypted();
@@ -2,15 +2,13 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import com.beemdevelopment.aegis.util.ByteInputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.util.ByteInputStream;
public abstract class DatabaseFileImporter implements DatabaseImporter {
private static Map<String, Class<? extends DatabaseFileImporter>> _importers;
static {
@@ -32,7 +30,7 @@ public abstract class DatabaseFileImporter implements DatabaseImporter {
public abstract void parse() throws DatabaseImporterException;
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
public abstract DatabaseImporterResult convert() throws DatabaseImporterException;
public abstract boolean isEncrypted();
@@ -2,13 +2,9 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.List;
public interface DatabaseImporter {
void parse() throws DatabaseImporterException;
List<DatabaseEntry> convert() throws DatabaseImporterException;
DatabaseImporterResult convert() throws DatabaseImporterException;
boolean isEncrypted();
Context getContext();
}
@@ -0,0 +1,14 @@
package com.beemdevelopment.aegis.importers;
public class DatabaseImporterEntryException extends Exception {
private String _text;
public DatabaseImporterEntryException(Throwable cause, String text) {
super(cause);
_text = text;
}
public String getText() {
return _text;
}
}
@@ -0,0 +1,27 @@
package com.beemdevelopment.aegis.importers;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import java.util.ArrayList;
import java.util.List;
public class DatabaseImporterResult {
private List<DatabaseEntry> _entries = new ArrayList<>();
private List<DatabaseImporterEntryException> _errors = new ArrayList<>();
public void addEntry(DatabaseEntry entry) {
_entries.add(entry);
}
public void addError(DatabaseImporterEntryException error) {
_errors.add(error);
}
public List<DatabaseEntry> getEntries() {
return _entries;
}
public List<DatabaseImporterEntryException> getErrors() {
return _errors;
}
}
@@ -3,6 +3,13 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.util.Xml;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.ByteInputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -13,13 +20,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.ByteInputStream;
public class FreeOtpFileImporter extends DatabaseFileImporter {
private List<XmlEntry> _xmlEntries;
@@ -46,43 +46,48 @@ public class FreeOtpFileImporter extends DatabaseFileImporter {
}
@Override
public List<DatabaseEntry> convert() throws DatabaseImporterException {
List<DatabaseEntry> entries = new ArrayList<>();
public DatabaseImporterResult convert() {
DatabaseImporterResult result = new DatabaseImporterResult();
try {
for (XmlEntry xmlEntry : _xmlEntries) {
if (xmlEntry.Name.equals("tokenOrder")) {
// TODO: order
JSONArray array = new JSONArray(xmlEntry.Value);
} else {
JSONObject obj = new JSONObject(xmlEntry.Value);
String type = obj.getString("type").toLowerCase();
String algo = obj.getString("algo");
int digits = obj.getInt("digits");
byte[] secret = toBytes(obj.getJSONArray("secret"));
OtpInfo info;
if (type.equals("totp")) {
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
} else if (type.equals("hotp")) {
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
} else {
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String issuer = obj.getString("issuerExt");
String name = obj.optString("label");
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
entries.add(entry);
for (XmlEntry xmlEntry : _xmlEntries) {
// TODO: order
if (!xmlEntry.Name.equals("tokenOrder")) {
try {
DatabaseEntry entry = convertEntry(xmlEntry);
result.addEntry(entry);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
}
} catch (OtpInfoException | JSONException e) {
throw new DatabaseImporterException(e);
}
return entries;
return result;
}
private static DatabaseEntry convertEntry(XmlEntry xmlEntry) throws DatabaseImporterEntryException {
try {
JSONObject obj = new JSONObject(xmlEntry.Value);
String type = obj.getString("type").toLowerCase();
String algo = obj.getString("algo");
int digits = obj.getInt("digits");
byte[] secret = toBytes(obj.getJSONArray("secret"));
OtpInfo info;
if (type.equals("totp")) {
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
} else if (type.equals("hotp")) {
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
} else {
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String issuer = obj.getString("issuerExt");
String name = obj.optString("label");
return new DatabaseEntry(info, name, issuer);
} catch (DatabaseImporterException | OtpInfoException | JSONException e) {
throw new DatabaseImporterEntryException(e, xmlEntry.Value);
}
}
@Override
@@ -1,6 +1,5 @@
package com.beemdevelopment.aegis.importers;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -14,7 +13,6 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.topjohnwu.superuser.ShellUtils;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import java.io.File;
@@ -29,13 +27,13 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
private static final int TYPE_TOTP = 0;
private static final int TYPE_HOTP = 1;
@SuppressLint("SdCardPath")
private static final String _filename = "/data/data/com.google.android.apps.authenticator2/databases/databases";
private static final String _subPath = "databases/databases";
private static final String _pkgName = "com.google.android.apps.authenticator2";
private List<DatabaseEntry> _entries = new ArrayList<>();
private List<Entry> _entries = new ArrayList<>();
public GoogleAuthAppImporter(Context context) {
super(context);
public GoogleAuthAppImporter(Context context) throws DatabaseImporterException {
super(context, _pkgName, _subPath);
}
@Override
@@ -45,7 +43,7 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
try {
// create a temporary copy of the database so that SQLiteDatabase can open it
file = File.createTempFile("google-import-", "", getContext().getCacheDir());
try (SuFileInputStream in = new SuFileInputStream(new SuFile(_filename))) {
try (SuFileInputStream in = new SuFileInputStream(getPath())) {
try (FileOutputStream out = new FileOutputStream(file)) {
ShellUtils.pump(in, out);
}
@@ -61,34 +59,11 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
}
do {
int type = getInt(cursor, "type");
byte[] secret = Base32.decode(getString(cursor, "secret").toCharArray());
OtpInfo info;
switch (type) {
case TYPE_TOTP:
info = new TotpInfo(secret);
break;
case TYPE_HOTP:
info = new HotpInfo(secret, getInt(cursor, "counter"));
break;
default:
throw new DatabaseImporterException("unsupported otp type: " + type);
}
String name = getString(cursor, "email", "");
String issuer = getString(cursor, "issuer", "");
String[] parts = name.split(":");
if (parts.length == 2) {
name = parts[1];
}
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
Entry entry = new Entry(cursor);
_entries.add(entry);
} while(cursor.moveToNext());
}
} catch (SQLiteException | OtpInfoException | Base32Exception e) {
} catch (SQLiteException e) {
throw new DatabaseImporterException(e);
} finally {
// always delete the temporary file
@@ -97,8 +72,47 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
}
@Override
public List<DatabaseEntry> convert() {
return _entries;
public DatabaseImporterResult convert() {
DatabaseImporterResult result = new DatabaseImporterResult();
for (Entry sqlEntry : _entries) {
try {
DatabaseEntry entry = convertEntry(sqlEntry);
result.addEntry(entry);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
}
return result;
}
private static DatabaseEntry convertEntry(Entry entry) throws DatabaseImporterEntryException {
try {
byte[] secret = Base32.decode(entry.getSecret().toCharArray());
OtpInfo info;
switch (entry.getType()) {
case TYPE_TOTP:
info = new TotpInfo(secret);
break;
case TYPE_HOTP:
info = new HotpInfo(secret, entry.getCounter());
break;
default:
throw new DatabaseImporterException("unsupported otp type: " + entry.getType());
}
String name = entry.getEmail();
String[] parts = name.split(":");
if (parts.length == 2) {
name = parts[1];
}
return new DatabaseEntry(info, name, entry.getIssuer());
} catch (Base32Exception | OtpInfoException | DatabaseImporterException e) {
throw new DatabaseImporterEntryException(e, entry.toString());
}
}
@Override
@@ -121,4 +135,44 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
private static int getInt(Cursor cursor, String columnName) {
return cursor.getInt(cursor.getColumnIndex(columnName));
}
private static long getLong(Cursor cursor, String columnName) {
return cursor.getLong(cursor.getColumnIndex(columnName));
}
private static class Entry {
private int _type;
private String _secret;
private String _email;
private String _issuer;
private long _counter;
public Entry(Cursor cursor) {
_type = getInt(cursor, "type");
_secret = getString(cursor, "secret");
_email = getString(cursor, "email", "");
_issuer = getString(cursor, "issuer", "");
_counter = getLong(cursor, "counter");
}
public int getType() {
return _type;
}
public String getSecret() {
return _secret;
}
public String getEmail() {
return _email;
}
public String getIssuer() {
return _issuer;
}
public long getCounter() {
return _counter;
}
}
}
@@ -0,0 +1,83 @@
package com.beemdevelopment.aegis.importers;
import android.content.Context;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.util.ByteInputStream;
import com.topjohnwu.superuser.io.SuFile;
import com.topjohnwu.superuser.io.SuFileInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class SteamAppImporter extends DatabaseAppImporter {
private static final String _subDir = "files";
private static final String _pkgName = "com.valvesoftware.android.steam.community";
private List<JSONObject> _objs = new ArrayList<>();
public SteamAppImporter(Context context) throws DatabaseImporterException {
super(context, _pkgName, _subDir);
}
@Override
public void parse() throws DatabaseImporterException {
SuFile[] files = getPath().listFiles((d, name) -> name.startsWith("Steamguard-"));
if (files == null || files.length == 0) {
throw new DatabaseImporterException(String.format("Empty directory: %s", getPath().getAbsolutePath()));
}
for (SuFile file : files) {
try (SuFileInputStream in = new SuFileInputStream(file)) {
try (ByteInputStream stream = ByteInputStream.create(in)) {
JSONObject obj = new JSONObject(new String(stream.getBytes(), StandardCharsets.UTF_8));
_objs.add(obj);
}
} catch (IOException | JSONException e) {
throw new DatabaseImporterException(e);
}
}
}
@Override
public DatabaseImporterResult convert() {
DatabaseImporterResult result = new DatabaseImporterResult();
for (JSONObject obj : _objs) {
try {
DatabaseEntry entry = convertEntry(obj);
result.addEntry(entry);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
}
return result;
}
public DatabaseEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException{
try {
byte[] secret = Base64.decode(obj.getString("shared_secret"));
SteamInfo info = new SteamInfo(secret);
String account = obj.getString("account_name");
return new DatabaseEntry(info, account, "Steam");
} catch (JSONException | Base64Exception | OtpInfoException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}
@Override
public boolean isEncrypted() {
return false;
}
}
@@ -57,7 +57,8 @@ public class GoogleAuthInfo {
}
public static GoogleAuthInfo parseUri(Uri uri) throws GoogleAuthInfoException {
if (!uri.getScheme().equals("otpauth")) {
String scheme = uri.getScheme();
if (scheme == null || !scheme.equals("otpauth")) {
throw new GoogleAuthInfoException("unsupported protocol");
}
@@ -1,6 +1,7 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import org.json.JSONException;
import org.json.JSONObject;
@@ -9,6 +10,8 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class HotpInfo extends OtpInfo {
public static final String ID = "hotp";
private long _counter;
public HotpInfo(byte[] secret, long counter) throws OtpInfoException {
@@ -28,7 +31,8 @@ public class HotpInfo extends OtpInfo {
@Override
public String getOtp() {
try {
return HOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getCounter());
OTP otp = HOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getCounter());
return otp.toString();
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException();
}
@@ -36,7 +40,7 @@ public class HotpInfo extends OtpInfo {
@Override
public String getType() {
return "hotp";
return ID;
}
@Override
@@ -98,13 +98,14 @@ public abstract class OtpInfo implements Serializable {
int digits = obj.getInt("digits");
switch (type) {
case "totp":
int period = obj.getInt("period");
info = new TotpInfo(secret, algo, digits, period);
case TotpInfo.ID:
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
break;
case "hotp":
long counter = obj.getLong("counter");
info = new HotpInfo(secret, algo, digits, counter);
case SteamInfo.ID:
info = new SteamInfo(secret, algo, digits, obj.getInt("period"));
break;
case HotpInfo.ID:
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
break;
default:
throw new OtpInfoException("unsupported otp type: " + type);
@@ -126,7 +127,8 @@ public abstract class OtpInfo implements Serializable {
}
OtpInfo info = (OtpInfo) o;
return Arrays.equals(getSecret(), info.getSecret())
return getType().equals(info.getType())
&& Arrays.equals(getSecret(), info.getSecret())
&& getAlgorithm(false).equals(info.getAlgorithm(false))
&& getDigits() == info.getDigits();
}
@@ -0,0 +1,34 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class SteamInfo extends TotpInfo {
public static final String ID = "steam";
public SteamInfo(byte[] secret) throws OtpInfoException {
super(secret, "SHA1", 5, 30);
}
public SteamInfo(byte[] secret, String algorithm, int digits, int period) throws OtpInfoException {
super(secret, algorithm, digits, period);
}
@Override
public String getOtp() {
try {
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
return otp.toSteamString();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
@Override
public String getType() {
return ID;
}
}
@@ -1,5 +1,6 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import org.json.JSONException;
@@ -9,6 +10,8 @@ import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class TotpInfo extends OtpInfo {
public static final String ID = "totp";
private int _period;
public TotpInfo(byte[] secret) throws OtpInfoException {
@@ -24,7 +27,8 @@ public class TotpInfo extends OtpInfo {
@Override
public String getOtp() {
try {
return TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
OTP otp = TOTP.generateOTP(getSecret(), getAlgorithm(true), getDigits(), getPeriod());
return otp.toString();
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
@@ -32,7 +36,7 @@ public class TotpInfo extends OtpInfo {
@Override
public String getType() {
return "totp";
return ID;
}
@Override
@@ -1,14 +1,20 @@
package com.beemdevelopment.aegis.ui;
import android.animation.ValueAnimator;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.provider.Settings;
import android.view.WindowManager;
import android.widget.Toast;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import androidx.appcompat.app.AppCompatActivity;
public abstract class AegisActivity extends AppCompatActivity {
private AegisApplication _app;
@@ -17,6 +23,15 @@ public abstract class AegisActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
_app = (AegisApplication) getApplication();
// if the app was killed, relaunch MainActivity and close everything else
if (!(this instanceof MainActivity) && !(this instanceof AuthActivity) && _app.getDatabaseManager().isLocked()) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish();
return;
}
// set FLAG_SECURE on the window of every AegisActivity
if (getPreferences().isSecureScreenEnabled()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
@@ -24,6 +39,9 @@ public abstract class AegisActivity extends AppCompatActivity {
// set the theme
setPreferredTheme(Theme.fromInteger(getPreferences().getCurrentTheme()));
// apply a dirty hack to make progress bars work even if animations are disabled
setGlobalAnimationDurationScale();
}
protected AegisApplication getApp() {
@@ -39,14 +57,23 @@ public abstract class AegisActivity extends AppCompatActivity {
case LIGHT:
setTheme(R.style.AppTheme);
break;
case DARK:
setTheme(R.style.AppTheme_Dark);
break;
case AMOLED:
setTheme(R.style.AppTheme_TrueBlack);
break;
}
}
private void setGlobalAnimationDurationScale() {
float durationScale = Settings.Global.getFloat(getContentResolver(), Settings.Global.ANIMATOR_DURATION_SCALE, 0);
if (durationScale != 1) {
try {
ValueAnimator.class.getMethod("setDurationScale", float.class).invoke(null, 1f);
} catch (Throwable t) {
Toast.makeText(this, R.string.progressbar_error, Toast.LENGTH_SHORT).show();
}
}
}
}
@@ -3,46 +3,44 @@ package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import androidx.appcompat.app.AlertDialog;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
import com.beemdevelopment.aegis.ui.tasks.SlotListTask;
import com.mattprecious.swirl.SwirlView;
import java.lang.reflect.UndeclaredThrowableException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.db.slots.SlotException;
import androidx.appcompat.app.AlertDialog;
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotListTask.Callback {
private EditText _textPassword;
private SlotList _slots;
private FingerprintUiHelper _fingerHelper;
private Cipher _fingerCipher;
private FingerprintManager.CryptoObject _fingerCryptoObj;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -87,7 +85,8 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
invalidated = true;
continue;
}
_fingerCipher = slot.createDecryptCipher(key);
Cipher cipher = slot.createDecryptCipher(key);
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
boxFingerprint.setVisibility(View.VISIBLE);
invalidated = false;
@@ -95,7 +94,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
}
}
} catch (KeyStoreHandleException | SlotException e) {
throw new UndeclaredThrowableException(e);
throw new RuntimeException(e);
}
// display a help message if a matching invalidated keystore entry was found
@@ -111,6 +110,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
trySlots(PasswordSlot.class, password);
}
});
if (_fingerHelper == null) {
_textPassword.requestFocus();
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
}
}
private void showError() {
@@ -145,7 +149,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
super.onResume();
if (_fingerHelper != null) {
_fingerHelper.startListening(new FingerprintManager.CryptoObject(_fingerCipher));
_fingerHelper.startListening(_fingerCryptoObj);
}
}
@@ -160,7 +164,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
@Override
public void onAuthenticated() {
trySlots(FingerprintSlot.class, _fingerCipher);
trySlots(FingerprintSlot.class, _fingerCryptoObj.getCipher());
}
@Override
@@ -14,8 +14,14 @@ import android.widget.EditText;
import android.widget.NumberPicker;
import android.widget.TextView;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
@@ -30,14 +36,6 @@ import javax.crypto.SecretKey;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
public class Dialogs {
private Dialogs() {
@@ -130,8 +128,12 @@ public class Dialogs {
boolean equal = EditTextHelper.areEditTextsEqual(textPassword, textPasswordConfirm);
buttonOK.get().setEnabled(equal);
}
public void beforeTextChanged(CharSequence c, int start, int count, int after) { }
public void afterTextChanged(Editable c) { }
public void beforeTextChanged(CharSequence c, int start, int count, int after) {
}
public void afterTextChanged(Editable c) {
}
};
textPassword.addTextChangedListener(watcher);
textPasswordConfirm.addTextChangedListener(watcher);
@@ -176,7 +178,7 @@ public class Dialogs {
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
Cipher cipher;
FingerprintManager.CryptoObject obj;
FingerprintSlot slot;
final AtomicReference<FingerprintUiHelper> helper = new AtomicReference<>();
FingerprintManager manager = FingerprintHelper.getManager(activity);
@@ -184,7 +186,8 @@ public class Dialogs {
try {
slot = new FingerprintSlot();
SecretKey key = new KeyStoreHandle().generateKey(slot.getUUID().toString());
cipher = Slot.createEncryptCipher(key);
Cipher cipher = Slot.createEncryptCipher(key);
obj = new FingerprintManager.CryptoObject(cipher);
} catch (KeyStoreHandleException | SlotException e) {
throw new RuntimeException(e);
}
@@ -201,7 +204,7 @@ public class Dialogs {
helper.set(new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, new FingerprintUiHelper.Callback() {
@Override
public void onAuthenticated() {
listener.onSlotResult(slot, cipher);
listener.onSlotResult(slot, obj.getCipher());
dialog.dismiss();
}
@@ -211,7 +214,7 @@ public class Dialogs {
}
}));
helper.get().startListening(new FingerprintManager.CryptoObject(cipher));
helper.get().startListening(obj);
showSecureDialog(dialog);
}
@@ -10,9 +10,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.ArrayRes;
import androidx.appcompat.app.ActionBar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
@@ -30,7 +27,8 @@ import android.widget.TableRow;
import com.amulyakhare.textdrawable.TextDrawable;
import com.avito.android.krop.KropView;
import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
@@ -39,6 +37,7 @@ import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import java.io.ByteArrayInputStream;
@@ -54,10 +53,10 @@ import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import androidx.annotation.ArrayRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import de.hdodenhof.circleimageview.CircleImageView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
public class EditEntryActivity extends AegisActivity {
private static final int PICK_IMAGE_REQUEST = 0;
@@ -167,7 +166,7 @@ public class EditEntryActivity extends AegisActivity {
}
String type = _origEntry.getInfo().getType();
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type.toUpperCase()), false);
_spinnerType.setSelection(getStringResourceIndex(R.array.otp_types_array, type), false);
String algo = _origEntry.getInfo().getAlgorithm(false);
_spinnerAlgo.setSelection(getStringResourceIndex(R.array.otp_algo_array, algo), false);
@@ -190,11 +189,12 @@ public class EditEntryActivity extends AegisActivity {
String type = _spinnerType.getSelectedItem().toString();
switch (type.toLowerCase()) {
case "totp":
case TotpInfo.ID:
case SteamInfo.ID:
_rowCounter.setVisibility(View.GONE);
_rowPeriod.setVisibility(View.VISIBLE);
break;
case "hotp":
case HotpInfo.ID:
_rowPeriod.setVisibility(View.GONE);
_rowCounter.setVisibility(View.VISIBLE);
break;
@@ -418,6 +418,14 @@ public class EditEntryActivity extends AegisActivity {
super.onActivityResult(requestCode, resultCode, data);
}
private int parsePeriod() throws ParseException {
try {
return Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) {
throw new ParseException("Period is not an integer.");
}
}
private DatabaseEntry parseEntry() throws ParseException {
if (_textSecret.length() == 0) {
throw new ParseException("Secret is a required field.");
@@ -444,16 +452,13 @@ public class EditEntryActivity extends AegisActivity {
OtpInfo info;
try {
switch (type.toLowerCase()) {
case "totp":
int period;
try {
period = Integer.parseInt(_textPeriod.getText().toString());
} catch (NumberFormatException e) {
throw new ParseException("Period is not an integer.");
}
info = new TotpInfo(secret, algo, digits, period);
case TotpInfo.ID:
info = new TotpInfo(secret, algo, digits, parsePeriod());
break;
case "hotp":
case SteamInfo.ID:
info = new SteamInfo(secret, algo, digits, parsePeriod());
break;
case HotpInfo.ID:
long counter;
try {
counter = Long.parseLong(_textCounter.getText().toString());
@@ -547,7 +552,7 @@ public class EditEntryActivity extends AegisActivity {
private int getStringResourceIndex(@ArrayRes int id, String string) {
String[] res = getResources().getStringArray(id);
for (int i = 0; i < res.length; i++) {
if (res[i].equals(string)) {
if (res[i].equalsIgnoreCase(string)) {
return i;
}
}
@@ -4,6 +4,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ui.views.GroupAdapter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.TreeSet;
@@ -12,8 +15,6 @@ import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.ui.views.GroupAdapter;
public class GroupManagerActivity extends AegisActivity implements GroupAdapter.Listener {
private GroupAdapter _adapter;
@@ -2,35 +2,34 @@ package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.WindowManager;
import com.github.paolorotolo.appintro.AppIntro2;
import com.github.paolorotolo.appintro.AppIntroFragment;
import com.github.paolorotolo.appintro.model.SliderPage;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.Database;
import com.beemdevelopment.aegis.db.DatabaseFile;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseFileException;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticatedSlide;
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticationSlide;
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
import com.github.paolorotolo.appintro.AppIntro2;
import com.github.paolorotolo.appintro.AppIntroFragment;
import com.github.paolorotolo.appintro.model.SliderPage;
import org.json.JSONObject;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseFileException;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.db.Database;
import com.beemdevelopment.aegis.db.DatabaseFile;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.slots.SlotException;
import androidx.fragment.app.Fragment;
public class IntroActivity extends AppIntro2 implements DerivationTask.Callback {
public static final int RESULT_OK = 0;
@@ -8,7 +8,6 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
@@ -20,24 +19,24 @@ import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.ui.views.EntryListView;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.TreeSet;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.ui.views.EntryListView;
import com.getbase.floatingactionbutton.FloatingActionsMenu;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
public class MainActivity extends AegisActivity implements EntryListView.Listener {
// activity request codes
@@ -61,6 +60,8 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
private FloatingActionsMenu _fabMenu;
private EntryListView _entryListView;
private boolean _isAnimating;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -77,6 +78,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setShowAccountName(getPreferences().isAccountNameVisible());
_entryListView.setTapToReveal(getPreferences().isTapToRevealEnabled());
_entryListView.setTapToRevealTime(getPreferences().getTapToRevealTime());
_entryListView.setViewMode(ViewMode.fromInteger(getPreferences().getCurrentViewMode()));
// set up the floating action button
_fabMenu = findViewById(R.id.fab);
@@ -112,9 +114,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
super.onNewIntent(intent);
setIntent(intent);
if (!doShortcutActions() || _db.isLocked()) {
unlockDatabase(null);
}
doShortcutActions();
}
@Override
@@ -258,6 +258,19 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.setGroupFilter(group);
}
private void setSortCategory(SortCategory sortCategory) {
if (sortCategory == SortCategory.CUSTOM) {
_entryListView.clearEntries();
loadEntries();
}
_entryListView.setSortCategory(sortCategory);
}
private void updateSortCategoryMenu(SortCategory sortCategory) {
_menu.findItem(SortCategory.getMenuItem(sortCategory)).setChecked(true);
}
private void addEntry(DatabaseEntry entry) {
_db.addEntry(entry);
_entryListView.addEntry(entry);
@@ -267,7 +280,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
if (resultCode == IntroActivity.RESULT_EXCEPTION) {
// TODO: user feedback
Exception e = (Exception) data.getSerializableExtra("exception");
throw new UndeclaredThrowableException(e);
throw new RuntimeException(e);
}
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
@@ -290,15 +303,11 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
startActivityForResult(scannerActivity, CODE_SCAN);
}
private boolean doShortcutActions() {
// return false if an action was blocked by a locked database
// otherwise, always return true
private void doShortcutActions() {
Intent intent = getIntent();
String action = intent.getStringExtra("action");
if (action == null) {
return true;
} else if (_db.isLocked()) {
return false;
if (action == null || _db.isLocked()) {
return;
}
switch (action) {
@@ -308,11 +317,9 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
intent.removeExtra("action");
return true;
}
public void startActivityForResult(Intent intent, int requestCode) {
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
super.startActivityForResult(intent, requestCode);
}
@@ -346,6 +353,16 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
updateLockIcon();
doShortcutActions();
}
@Override
public void onBackPressed() {
if (_app.isAutoLockEnabled()) {
lockDatabase();
}
super.onBackPressed();
}
private BottomSheetDialog createBottomSheet(final DatabaseEntry entry) {
@@ -399,6 +416,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
updateLockIcon();
if (_loaded) {
updateGroupFilterMenu();
updateSortCategoryMenu(SortCategory.fromInteger(getPreferences().getCurrentSortCategory()));
}
return true;
}
@@ -412,6 +430,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
return true;
case R.id.action_lock:
lockDatabase();
startAuthActivity();
return true;
default:
if (item.getGroupId() == R.id.action_filter_group) {
@@ -423,6 +442,33 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
setGroupFilter(group);
}
if (item.getGroupId() == R.id.action_sort_category) {
item.setChecked(true);
SortCategory sortCategory;
switch (item.getItemId()) {
case R.id.menu_sort_alphabetically:
sortCategory = SortCategory.ISSUER;
break;
case R.id.menu_sort_alphabetically_reverse:
sortCategory = SortCategory.ISSUERREVERSED;
break;
case R.id.menu_sort_alphabetically_name:
sortCategory = SortCategory.ACCOUNT;
break;
case R.id.menu_sort_alphabetically_name_reverse:
sortCategory = SortCategory.ACCOUNTREVERSED;
break;
case R.id.menu_sort_custom:
default:
sortCategory = SortCategory.CUSTOM;
break;
}
setSortCategory(sortCategory);
getPreferences().setCurrentSortCategory(sortCategory);
}
return super.onOptionsItemSelected(item);
}
}
@@ -432,7 +478,6 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
_entryListView.clearEntries();
_db.lock();
_loaded = false;
startAuthActivity();
}
}
@@ -460,11 +505,14 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
}
loadEntries();
SortCategory currentSortCategory = SortCategory.fromInteger(getPreferences().getCurrentSortCategory());
setSortCategory(currentSortCategory);
}
private void loadEntries() {
// load all entries
_entryListView.addEntries(_db.getEntries());
List<DatabaseEntry> entries = new ArrayList<DatabaseEntry>(_db.getEntries());
_entryListView.addEntries(entries);
_loaded = true;
}
@@ -510,12 +558,10 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
saveDatabase();
}
private boolean isAnimating;
@Override
public void onScroll(int dx, int dy) {
if (dy > 0 && _fabMenu.getVisibility() == View.VISIBLE && !isAnimating) {
isAnimating = true;
if (dy > 0 && _fabMenu.getVisibility() == View.VISIBLE && !_isAnimating) {
_isAnimating = true;
CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) _fabMenu.getLayoutParams();
int fabBottomMargin = lp.bottomMargin;
_fabMenu.animate()
@@ -524,13 +570,12 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
_isAnimating = false;
_fabMenu.setVisibility(View.INVISIBLE);
super.onAnimationEnd(animation);
}
}).start();
}
else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !isAnimating) {
} else if (dy < 0 && _fabMenu.getVisibility() != View.VISIBLE && !_isAnimating) {
_fabMenu.setVisibility(View.VISIBLE);
_fabMenu.animate()
.translationY(0)
@@ -538,7 +583,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
isAnimating = false;
_isAnimating = false;
super.onAnimationEnd(animation);
}
}).start();
@@ -2,34 +2,49 @@ package com.beemdevelopment.aegis.ui;
import android.Manifest;
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import android.view.Gravity;
import android.view.View;
import android.text.TextUtils;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.Toast;
import com.beemdevelopment.aegis.Preferences;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.helpers.PermissionHelper;
import com.beemdevelopment.aegis.importers.AegisFileImporter;
import com.beemdevelopment.aegis.importers.DatabaseAppImporter;
import com.beemdevelopment.aegis.importers.DatabaseFileImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporter;
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
import com.beemdevelopment.aegis.importers.DatabaseImporterResult;
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
import com.beemdevelopment.aegis.util.ByteInputStream;
import com.google.android.material.snackbar.Snackbar;
import com.takisoft.preferencex.PreferenceFragmentCompat;
import com.topjohnwu.superuser.Shell;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -43,18 +58,8 @@ import java.util.concurrent.atomic.AtomicReference;
import javax.crypto.Cipher;
import com.beemdevelopment.aegis.AegisApplication;
import com.beemdevelopment.aegis.BuildConfig;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.db.DatabaseManager;
import com.beemdevelopment.aegis.db.DatabaseManagerException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.topjohnwu.superuser.Shell;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
public class PreferencesFragment extends PreferenceFragmentCompat {
// activity request codes
@@ -93,11 +98,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
int currentTheme = app.getPreferences().getCurrentTheme();
Preference darkModePreference = findPreference("pref_dark_mode");
darkModePreference.setSummary("Selected: " + Theme.getThemeName(currentTheme));
darkModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(Theme.getThemeNameResource(currentTheme))));
darkModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] themeNames = Theme.getThemeNames();
String[] themeNames = getStringsFromResourceIds(Theme.getThemeNameResources());
int checkedTheme = app.getPreferences().getCurrentTheme();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
@@ -127,6 +132,33 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
});
int currentViewMode = app.getPreferences().getCurrentViewMode();
Preference viewModePreference = findPreference("pref_view_mode");
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(ViewMode.getViewModeNameResource(currentViewMode))));
viewModePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
String[] viewModes = getStringsFromResourceIds(ViewMode.getViewModeNameResources());
int checkedMode = app.getPreferences().getCurrentViewMode();
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(getString(R.string.choose_view_mode))
.setSingleChoiceItems(viewModes, checkedMode, (dialog, which) -> {
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
app.getPreferences().setCurrentViewMode(ViewMode.fromInteger(i));
dialog.dismiss();
viewModePreference.setSummary(String.format("%s: %s", getString(R.string.selected), getString(ViewMode.getViewModeNameResource(i))));
_result.putExtra("needsRecreate", true);
})
.setPositiveButton(android.R.string.ok, null)
.create());
return true;
}
});
Preference importPreference = findPreference("pref_import");
importPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@@ -227,8 +259,18 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
try {
_db.disableEncryption();
} catch (DatabaseManagerException e) {
Toast.makeText(getActivity(), getString(R.string.encrypting_error), Toast.LENGTH_SHORT).show();
Toast.makeText(getActivity(), getString(R.string.disable_encryption_error), Toast.LENGTH_SHORT).show();
return;
}
// clear the KeyStore
try {
KeyStoreHandle handle = new KeyStoreHandle();
handle.clear();
} catch (KeyStoreHandleException e) {
e.printStackTrace();
}
updateEncryptionPreferences();
}
})
@@ -254,6 +296,14 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
slots.remove(slot);
_db.setCredentials(creds);
// remove the KeyStore key
try {
KeyStoreHandle handle = new KeyStoreHandle();
handle.deleteKey(slot.getUUID().toString());
} catch (KeyStoreHandleException e) {
e.printStackTrace();
}
saveDatabase();
updateEncryptionPreferences();
}
@@ -289,6 +339,11 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
return true;
}
});
}
@Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
updateEncryptionPreferences();
}
@@ -465,7 +520,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
private void importDatabase(DatabaseImporter importer) throws DatabaseImporterException {
List<DatabaseEntry> entries = importer.convert();
DatabaseImporterResult result = importer.convert();
List<DatabaseEntry> entries = result.getEntries();
List<DatabaseImporterEntryException> errors = result.getErrors();
for (DatabaseEntry entry : entries) {
// temporary: randomize the UUID of duplicate entries and add them anyway
if (_db.getEntryByUUID(entry.getUUID()) != null) {
@@ -480,7 +538,30 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
_result.putExtra("needsRecreate", true);
Toast.makeText(getActivity(), String.format(Locale.getDefault(), getString(R.string.imported_entries_count), entries.size()), Toast.LENGTH_LONG).show();
Snackbar bar = Snackbar.make(getView(), String.format(Locale.getDefault(), getString(R.string.imported_entries_count), entries.size(), errors.size()), Snackbar.LENGTH_LONG);
if (errors.size() > 0) {
bar.setAction(R.string.details, v -> {
List<String> messages = new ArrayList<>();
for (DatabaseImporterEntryException e : errors) {
messages.add(e.getMessage());
}
String message = TextUtils.join("\n\n", messages);
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
.setTitle(R.string.import_error_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(android.R.string.copy, (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("text/plain", message);
clipboard.setPrimaryClip(clip);
Toast.makeText(getActivity(), getString(R.string.errors_copied), Toast.LENGTH_SHORT).show();
})
.create());
});
}
bar.show();
}
private void onExport() {
@@ -582,6 +663,15 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
}
}
private String[] getStringsFromResourceIds(int[] resourceIds) {
String[] strings = new String[resourceIds.length];
for(int i = 0; i < resourceIds.length; i++) {
strings[i] = getString(resourceIds[i]);
}
return strings;
}
private class SetPasswordListener implements Dialogs.SlotListener {
@Override
public void onSlotResult(Slot slot, Cipher cipher) {
@@ -8,19 +8,19 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.Theme;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.SquareFinderView;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import java.util.Collections;
import me.dm7.barcodescanner.core.IViewFinder;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK;
import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT;
@@ -2,30 +2,29 @@ package com.beemdevelopment.aegis.ui;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.ui.views.SlotAdapter;
import javax.crypto.Cipher;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.db.slots.SlotException;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, Dialogs.SlotListener {
private DatabaseFileCredentials _creds;
@@ -2,10 +2,11 @@ package com.beemdevelopment.aegis.ui.preferences;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import androidx.annotation.RequiresApi;
import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat;
import android.util.AttributeSet;
public class SwitchPreference extends SwitchPreferenceCompat {
private Preference.OnPreferenceChangeListener _listener;
@@ -5,8 +5,6 @@ import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Build;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -14,23 +12,22 @@ import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.github.paolorotolo.appintro.ISlideSelectionListener;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.github.paolorotolo.appintro.ISlideSelectionListener;
import com.google.android.material.snackbar.Snackbar;
import com.mattprecious.swirl.SwirlView;
import java.lang.reflect.UndeclaredThrowableException;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import androidx.fragment.app.Fragment;
public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiHelper.Callback, ISlidePolicy, ISlideSelectionListener {
private int _cryptType;
@@ -44,7 +41,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
private FingerprintUiHelper _fingerHelper;
private KeyStoreHandle _storeHandle;
private FingerprintSlot _fingerSlot;
private Cipher _fingerCipher;
private FingerprintManager.CryptoObject _fingerCryptoObj;
private boolean _fingerAuthenticated;
@Override
@@ -74,7 +71,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
}
public Cipher getFingerCipher() {
return _fingerCipher;
return _fingerCryptoObj.getCipher();
}
public FingerprintSlot getFingerSlot() {
@@ -105,7 +102,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
}
key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
} catch (KeyStoreHandleException e) {
throw new UndeclaredThrowableException(e);
throw new RuntimeException(e);
}
if (_fingerHelper == null) {
@@ -114,11 +111,12 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
}
try {
_fingerCipher = Slot.createEncryptCipher(key);
Cipher cipher = Slot.createEncryptCipher(key);
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
} catch (Exception e) {
throw new UndeclaredThrowableException(e);
throw new RuntimeException(e);
}
_fingerHelper.startListening(new FingerprintManager.CryptoObject(_fingerCipher));
_fingerHelper.startListening(_fingerCryptoObj);
break;
default:
throw new RuntimeException();
@@ -3,8 +3,6 @@ package com.beemdevelopment.aegis.ui.slides;
import android.content.Intent;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -12,10 +10,12 @@ import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.github.paolorotolo.appintro.ISlidePolicy;
import com.google.android.material.snackbar.Snackbar;
import androidx.fragment.app.Fragment;
public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy, RadioGroup.OnCheckedChangeListener {
public static final int CRYPT_TYPE_INVALID = 0;
@@ -2,15 +2,13 @@ package com.beemdevelopment.aegis.ui.tasks;
import android.content.Context;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.SCryptParameters;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import javax.crypto.SecretKey;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
public class DerivationTask extends ProgressDialogTask<DerivationTask.Params, SecretKey> {
private Callback _cb;
@@ -4,9 +4,11 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Process;
import androidx.annotation.CallSuper;
import com.beemdevelopment.aegis.ui.Dialogs;
import androidx.annotation.CallSuper;
public abstract class ProgressDialogTask<Params, Result> extends AsyncTask<Params, Void, Result> {
private ProgressDialog _dialog;
@@ -2,19 +2,17 @@ package com.beemdevelopment.aegis.ui.tasks;
import android.content.Context;
import com.beemdevelopment.aegis.crypto.MasterKey;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.db.slots.SlotList;
import com.beemdevelopment.aegis.db.slots.SlotException;
import com.beemdevelopment.aegis.db.slots.SlotIntegrityException;
import com.beemdevelopment.aegis.db.slots.SlotList;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTask.Params, MasterKey> {
private Callback _cb;
@@ -1,10 +1,13 @@
package com.beemdevelopment.aegis.ui.views;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.ItemTouchHelperAdapter;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
@@ -16,8 +19,7 @@ import java.util.Collections;
import java.util.List;
import java.util.UUID;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import androidx.recyclerview.widget.RecyclerView;
public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements ItemTouchHelperAdapter {
private List<DatabaseEntry> _entries;
@@ -27,6 +29,9 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
private boolean _tapToReveal;
private int _tapToRevealTime;
private String _groupFilter;
private SortCategory _sortCategory;
private ViewMode _viewMode;
// keeps track of the viewholders that are currently bound
private List<EntryHolder> _holders;
@@ -148,9 +153,32 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
_shownEntries.add(entry);
}
}
sortList(_sortCategory);
notifyDataSetChanged();
}
public void setSortCategory(SortCategory sortCategory) {
if (_sortCategory != sortCategory && sortCategory != SortCategory.CUSTOM) {
sortList(sortCategory);
notifyDataSetChanged();
}
_sortCategory = sortCategory;
}
private void sortList(SortCategory sortCategory) {
Collections.sort(_shownEntries, SortCategory.getComparator(sortCategory));
if (SortCategory.isReversed(sortCategory)) {
Collections.reverse(_shownEntries);
}
}
public void setViewMode(ViewMode viewMode) {
_viewMode = viewMode;
}
@Override
public void onItemDismiss(int position) {
@@ -185,6 +213,14 @@ public class EntryAdapter extends RecyclerView.Adapter<EntryHolder> implements I
@Override
public EntryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
if (_viewMode == ViewMode.NORMAL) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry, parent, false);
} else if (_viewMode == ViewMode.COMPACT) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_compact, parent, false);
} else if (_viewMode == ViewMode.SMALL) {
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_entry_small, parent, false);
}
return new EntryHolder(view);
}
@@ -3,21 +3,22 @@ package com.beemdevelopment.aegis.ui.views;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Handler;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.amulyakhare.textdrawable.TextDrawable;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import android.os.Handler;
import androidx.recyclerview.widget.RecyclerView;
public class EntryHolder extends RecyclerView.ViewHolder {
private TextView _profileName;
@@ -27,6 +28,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
private DatabaseEntry _entry;
private ImageView _buttonRefresh;
private View _entryDivider;
private View _currentView;
private boolean _hidden;
@@ -47,6 +50,8 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_profileDrawable = view.findViewById(R.id.ivTextDrawable);
_buttonRefresh = view.findViewById(R.id.buttonRefresh);
_entryDivider = view.findViewById(R.id.entryDivider);
_progressBar = view.findViewById(R.id.progressBar);
int primaryColorId = view.getContext().getResources().getColor(R.color.colorPrimary);
_progressBar.getProgressDrawable().setColorFilter(primaryColorId, PorterDuff.Mode.SRC_IN);
@@ -78,6 +83,10 @@ public class EntryHolder extends RecyclerView.ViewHolder {
_progressBar.setVisibility(showProgress ? View.VISIBLE : View.GONE);
if (showProgress) {
_progressBar.setPeriod(((TotpInfo)entry.getInfo()).getPeriod());
if (_entryDivider != null) {
_entryDivider.setVisibility(View.GONE);
}
}
// only show the button if this entry is of type HotpInfo
@@ -131,10 +140,18 @@ public class EntryHolder extends RecyclerView.ViewHolder {
}
private void updateCode() {
String otp = _entry.getInfo().getOtp();
String text = otp.substring(0, (otp.length() / 2)
+ (otp.length() % 2)) + " "
+ otp.substring(otp.length() / 2);
OtpInfo info = _entry.getInfo();
String text;
if (info instanceof SteamInfo) {
text = info.getOtp();
} else {
String otp = info.getOtp();
text = otp.substring(0, (otp.length() / 2)
+ (otp.length() % 2)) + " "
+ otp.substring(otp.length() / 2);
}
_profileCode.setText(text);
}
@@ -2,26 +2,26 @@ package com.beemdevelopment.aegis.ui.views;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.LayoutAnimationController;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.SortCategory;
import com.beemdevelopment.aegis.ViewMode;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import com.beemdevelopment.aegis.helpers.SimpleItemTouchHelperCallback;
import com.beemdevelopment.aegis.helpers.UiRefresher;
import com.beemdevelopment.aegis.otp.TotpInfo;
import java.util.List;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.DatabaseEntry;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
public class EntryListView extends Fragment implements EntryAdapter.Listener {
private EntryAdapter _adapter;
@@ -34,8 +34,6 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
private UiRefresher _refresher;
private RecyclerView.OnScrollChangeListener _onScrollListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -93,6 +91,17 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
runLayoutAnimation(_rvKeyProfiles);
}
public void setSortCategory(SortCategory sortCategory) {
_touchCallback.setIsLongPressDragEnabled(sortCategory == SortCategory.CUSTOM);
_adapter.setSortCategory(sortCategory);
runLayoutAnimation(_rvKeyProfiles);
}
public void setViewMode(ViewMode viewMode) {
_adapter.setViewMode(viewMode);
}
public void refresh(boolean hard) {
if (_showProgress) {
_progressBar.refresh();
@@ -209,6 +218,5 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener {
void onEntryDrop(DatabaseEntry entry);
void onEntryChange(DatabaseEntry entry);
void onScroll(int dx, int dy);
}
}
@@ -4,10 +4,11 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.beemdevelopment.aegis.R;
import java.util.ArrayList;
import androidx.recyclerview.widget.RecyclerView;
import com.beemdevelopment.aegis.R;
public class GroupAdapter extends RecyclerView.Adapter<GroupHolder> {
private GroupAdapter.Listener _listener;
@@ -1,12 +1,13 @@
package com.beemdevelopment.aegis.ui.views;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import androidx.recyclerview.widget.RecyclerView;
public class GroupHolder extends RecyclerView.ViewHolder {
private TextView _slotName;
private ImageView _buttonDelete;
@@ -3,13 +3,14 @@ package com.beemdevelopment.aegis.ui.views;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.os.Build;
import androidx.annotation.RequiresApi;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;
import android.widget.ProgressBar;
import com.beemdevelopment.aegis.otp.TotpInfo;
import androidx.annotation.RequiresApi;
public class PeriodProgressBar extends ProgressBar {
private int _period;
@@ -1,15 +1,16 @@
package com.beemdevelopment.aegis.ui.views;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.Slot;
import java.util.ArrayList;
import androidx.recyclerview.widget.RecyclerView;
public class SlotAdapter extends RecyclerView.Adapter<SlotHolder> {
private Listener _listener;
private ArrayList<Slot> _slots;
@@ -1,21 +1,20 @@
package com.beemdevelopment.aegis.ui.views;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
import com.beemdevelopment.aegis.db.slots.RawSlot;
import com.beemdevelopment.aegis.db.slots.Slot;
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
import androidx.recyclerview.widget.RecyclerView;
public class SlotHolder extends RecyclerView.ViewHolder {
private TextView _slotUsed;
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z"/>
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 B

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -38,7 +38,7 @@
app:krop_aspectX="1"
app:krop_aspectY="1"
app:krop_offset="70dp"
app:krop_overlayColor="#aaffffff" >
app:krop_overlayColor="#aadddddd" >
<ImageView
android:id="@+id/iv_saveImage"
@@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:background="?attr/cardBackground"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivTextDrawable"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentBottom="false"
android:layout_alignParentTop="false"
android:layout_alignWithParentIfMissing="false"
android:gravity="bottom"
android:id="@+id/relativeLayout"
android:layout_alignParentStart="false"
android:paddingTop="6dp"
android:paddingBottom="8dp"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/profile_issuer"
android:text="@string/issuer"
android:textColor="?attr/secondaryText"
android:includeFontPadding="false"
android:textSize="13sp"
android:layout_below="@+id/profile_code"
android:layout_alignLeft="@+id/profile_code"
android:layout_alignStart="@+id/profile_code"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/profile_account_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profile_code"
android:layout_toRightOf="@id/profile_issuer"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/extra_info_text"
android:textSize="13sp"
tools:text=" - AccountName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:includeFontPadding="false"
android:textSize="26sp"
android:textColor="?attr/codePrimaryText"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
</RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="3dp"
android:id="@+id/progressBar"
android:max="1000"
android:layout_weight="1"/>
</LinearLayout>
<View
android:id="@+id/entryDivider"
android:layout_width="match_parent"
android:layout_height="0.1dp"
android:background="@color/divider" />
</LinearLayout>
@@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal"
android:background="?attr/cardBackground"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingLeft="16dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/ivTextDrawable"
android:layout_width="45dp"
android:layout_height="45dp"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_alignParentBottom="false"
android:layout_alignParentTop="false"
android:layout_alignWithParentIfMissing="false"
android:gravity="bottom"
android:id="@+id/relativeLayout"
android:layout_alignParentStart="false"
android:paddingTop="6dp"
android:paddingBottom="8dp"
android:paddingEnd="8dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/profile_issuer"
android:text="@string/issuer"
android:textColor="?attr/secondaryText"
android:includeFontPadding="false"
android:textSize="13sp"
android:layout_below="@+id/profile_code"
android:layout_alignLeft="@+id/profile_code"
android:layout_alignStart="@+id/profile_code"
android:ellipsize="end"
android:maxLines="1"/>
<TextView
android:id="@+id/profile_account_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profile_code"
android:layout_toRightOf="@id/profile_issuer"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/extra_info_text"
android:textSize="13sp"
tools:text=" - AccountName" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="sans-serif-light"
tools:text="012 345"
android:id="@+id/profile_code"
android:includeFontPadding="false"
android:textSize="26sp"
android:textColor="?attr/codePrimaryText"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_marginTop="0dp"
android:textStyle="normal|bold"/>
</RelativeLayout>
<ImageView
android:id="@+id/buttonRefresh"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:clickable="true"
android:focusable="true"
android:src="@drawable/ic_refresh_black_24dp"
android:tint="?attr/iconColorPrimary"
android:background="?android:attr/selectableItemBackground" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:padding="0dp"
android:layout_margin="0dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.beemdevelopment.aegis.ui.views.PeriodProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="wrap_content"
android:layout_height="3dp"
android:id="@+id/progressBar"
android:max="1000"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>
+29
View File
@@ -21,6 +21,35 @@
android:title="@string/all"
android:checked="true" />
</group>
</menu>
</item>
<item
android:id="@+id/action_sort"
android:icon="@drawable/ic_action_sort"
app:showAsAction="always"
android:title="@string/filter">
<menu>
<group
android:id="@+id/action_sort_category"
android:checkableBehavior="single">
<item
android:id="@+id/menu_sort_custom"
android:title="@string/sort_custom"
android:checked="true" />
<item
android:id="@+id/menu_sort_alphabetically_name"
android:title="@string/sort_alphabetically_name"/>
<item
android:id="@+id/menu_sort_alphabetically_name_reverse"
android:title="@string/sort_alphabetically_name_reverse"/>
<item
android:id="@+id/menu_sort_alphabetically"
android:title="@string/sort_alphabetically"/>
<item
android:id="@+id/menu_sort_alphabetically_reverse"
android:title="@string/sort_alphabetically_reverse"/>
</group>
</menu>
</item>
<item
+153
View File
@@ -0,0 +1,153 @@
<resources>
<string name="app_name">Aegis</string>
<string name="app_name_dev">AegisDev</string>
<string name="action_settings">Instellingen</string>
<string name="action_import">Importeren</string>
<string name="action_delete">Verwijderen</string>
<string name="action_default_icon">Standaard pictogram herstellen</string>
<string name="discard">Ongedaan maken</string>
<string name="save">Opslaan</string>
<string name="issuer">Uitgever</string>
<string name="settings">Voorkeuren</string>
<string name="pref_appearance_group_title">Uiterlijk</string>
<string name="pref_security_group_title">Beveiliging</string>
<string name="pref_tools_group_title">Hulpmiddelen</string>
<string name="pref_select_theme_title">Thema</string>
<string name="pref_view_mode_title">Weergavemodus</string>
<string name="pref_account_name_title">Toon de accountnaam</string>
<string name="pref_account_name_summary">Schakel in om de accountnaam naast de uitgever te tonen</string>
<string name="pref_timeout_title">Time-out</string>
<string name="pref_timeout_summary">Automatisch database vergrendelen na %1$s seconden zonder activiteit</string>
<string name="pref_slots_title">Vergrendelingssleutels</string>
<string name="pref_slots_summary">Beheer de lijst van vergrendelingssleutels die de database kunnen decrypten</string>
<string name="pref_import_file_title">Importeren vanuit een bestand</string>
<string name="pref_import_file_summary">Database importeren vanuit een bestand</string>
<string name="pref_import_app_title">Importeren vanuit een app</string>
<string name="pref_import_app_summary">Database importeren vanuit een app (vereist root-access)</string>
<string name="pref_export_title">Exporteren</string>
<string name="pref_export_summary">Exporteer database</string>
<string name="pref_secure_screen_title">Schermbeveiliging</string>
<string name="pref_secure_screen_summary">Blokkeer screenshots en andere pogingen tot schermopnamen binnen deze app</string>
<string name="pref_tap_to_reveal_title">Drukken om te laten zien</string>
<string name="pref_tap_to_reveal_summary">Tokens zullen standaard worden verborgen. Druk op de tokens om de code te laten zien.</string>
<string name="pref_tap_to_reveal_time_title">Time-out voor drukken om te laten zien</string>
<string name="pref_encryption_title">Encryptie</string>
<string name="pref_encryption_summary">Encrypt de database en ontgrendel deze met een wachtwoord of vingerafdruk</string>
<string name="pref_fingerprint_title">Vingerafdruk</string>
<string name="pref_fingerprint_summary">Schakel in om met de op dit toestel geregistreerde vingerafdrukken de kluis te kunnen ontgrendelen</string>
<string name="pref_set_password_title">Wachtwoord wijzigen</string>
<string name="pref_set_password_summary">Stel een nieuw wachtwoord in waarmee je de kluis kunt ontgrendelen</string>
<string name="fingerprint_hint">Aanraaksensor</string>
<string name="fingerprint_not_recognized">Vingerafdruk niet herkend. Probeer opnieuw.</string>
<string name="fingerprint_success">Vingerafdruk herkend</string>
<string name="choose_authentication_method">Beveiliging</string>
<string name="authentication_method_none">Geen</string>
<string name="authentication_method_none_description">Je hebt geen wachtwoord nodig om de kluis te ontgrendelen en de kluis zal niet worden geëncrypt. Deze optie wordt niet aanbevolen.</string>
<string name="authentication_method_password">Wachtwoord</string>
<string name="authentication_method_password_description">Je hebt een wachtwoord nodig om de kluis te ontgrendelen.</string>
<string name="authentication_method_fingerprint">Vingerafdruk</string>
<string name="authentication_method_fingerprint_description">Als toevoeging op een wachtwoord kan er ook gebruik gemaakt worden van de op dit toestel geregistreerde vingerafdrukken om de kluis te ontgrendelen.</string>
<string name="authentication_method_set_password">Wachtwoord</string>
<string name="authentication_enter_password">Vul je wachtwoord in</string>
<string name="authentication">Kluis ontgrendelen</string>
<string name="set_password">Vul een wachtwoord in</string>
<string name="set_group">Vul een groepsnaam in</string>
<string name="set_number">Kies een getal</string>
<string name="set_password_confirm">Bevestig het wachtwoord</string>
<string name="invalidated_fingerprint">Er is een verandering in de beveiligingsinstellingen van je toestel gedetecteerd. Ga naar \"Instellingen -> Vingerafdruk\" om je vingerafdruk opnieuw toe te voegen.</string>
<string name="unlock">Ontgrendel</string>
<string name="advanced">Geavanceerd</string>
<string name="seconds">seconden</string>
<string name="counter">Counter</string>
<string name="digits">Cijfers</string>
<string name="secret">Sleutel</string>
<string name="scan">Scan QR-code</string>
<string name="enter_manually">Handmatig invoeren</string>
<string name="add_fingerprint">Vingerafdruk toevoegen</string>
<string name="add_password">Wachtwoord toevoegen</string>
<string name="slots_warning">De kluis is slechts even veilig als je zwakste wachtwoord. Wanneer een nieuw vingerafdruk is toegevoegd aan je toestel, moet authenticatie met vingerafdruk opnieuw worden geactiveerd binnen Aegis.</string>
<string name="copy">Kopiëren</string>
<string name="edit">Bewerken</string>
<string name="delete">Verwijderen</string>
<string name="password">Wachtwoord</string>
<string name="confirm_password">Bevestig wachtwoord</string>
<string name="new_profile">Nieuw profiel</string>
<string name="add_new_profile">Profiel toevoegen</string>
<string name="unlock_vault_error">Kluis kon niet worden ontgrendeld</string>
<string name="unlock_vault_error_description">Wachtwoord onjuist. Controleer of je het wachtwoord correct hebt ingetypt.</string>
<string name="password_equality_error">Wachtwoorden moeten overeen komen en mogen niet leeg zijn</string>
<string name="register_fingerprint">Registreer je vingerafdruk</string>
<string name="snackbar_authentication_method">Kies een inlogmethode</string>
<string name="encrypting_vault">De kluis wordt geëncrypt</string>
<string name="delete_entry">Item verwijderen</string>
<string name="delete_entry_description">Weet je zeker dat je dit item wilt verwijderen?</string>
<string name="discard_changes">Wijzigingen ongedaan maken?</string>
<string name="discard_changes_description">Je wijzigingen zijn niet opgeslagen</string>
<string name="folder">Map</string>
<string name="tap_to_select">Druk om te selecteren</string>
<string name="saving_profile_error">Fout bij opslaan profiel</string>
<string name="welcome">Welkom</string>
<string name="app_description">Aegis is een gratis en veilige open-source 2FA-app.</string>
<string name="setup_completed">Configuratie voltooid</string>
<string name="setup_completed_description">Aegis is geconfigureerd en is klaar voor gebruik.</string>
<string name="vault_not_found">Kluis niet gevonden, configuratie wordt gestart…</string>
<string name="code_copied">Code gekopieerd naar klembord</string>
<string name="errors_copied">Fouten zijn gekopieerd naar klembord</string>
<string name="decryption_error">Er is een fout opgetreden tijdens het ontgrendelen van de kluis</string>
<string name="saving_error">Er is een fout opgetreden tijdens het opslaan van de kluis</string>
<string name="disable_encryption">Encryptie uitschakelen</string>
<string name="disable_encryption_description">Weet je zeker dat je encryptie wilt uitschakelen? Dit zorgt ervoor dat de kluis in leesbare tekst wordt opgeslagen.</string>
<string name="permission_denied">Toestemming geweigerd</string>
<string name="choose_application">Selecteer de applicatie waarvan je een database wilt importeren</string>
<string name="choose_theme">Selecteer je gewenste thema</string>
<string name="choose_view_mode">Selecteer je gewenste weergavemodus</string>
<string name="parsing_file_error">Er is een fout opgetreden tijdens het vertalen van het bestand</string>
<string name="file_not_found">Fout: Bestand niet gevonden</string>
<string name="reading_file_error">Er is een fout opgetreden tijdens het lezen van het bestand</string>
<string name="root_error">Fout: Er kon geen root-access verkregen worden</string>
<string name="imported_entries_count">%d items geïmporteerd. %d fouten.</string>
<string name="import_error_title">Er zijn fouten opgetreden tijdens het importeren</string>
<string name="exporting_database_error">Er is een fout opgetreden tijdens het exporteren van de database</string>
<string name="export_database_location">De database is geëxporteerd naar:</string>
<string name="export_warning">Deze actie zal de database uit de privé-opslag van Aegis exporteren.</string>
<string name="encryption_set_password_error">Er is een fout opgetreden tijdens het instellen van het wachtwoord: </string>
<string name="encryption_enable_fingerprint_error">Er is een fout opgetreden tijdens het inschakelen van ontgrendeling met vingerafdruk: </string>
<string name="no_cameras_available">Geen camera\'s beschikbaar</string>
<string name="read_qr_error">Er is een fout opgetreden tijdens het lezen van de QR-code</string>
<string name="authentication_method_raw">Onbewerkt</string>
<string name="unlocking_vault">Kluis wordt ontgrendeld</string>
<string name="password_slot_error">Je moet minimaal één wachtwoordvergrendeling hebben</string>
<string name="remove_slot">Vergrendelingssleutel verwijderen</string>
<string name="remove_slot_description">Weet je zeker dat je deze vergrendelingssleutel wilt verwijderen?</string>
<string name="remove_group">Groep verwijderen</string>
<string name="remove_group_description">Weet je zeker dat je deze groep wilt verwijderen? Alle items binnen deze groep worden automatisch verplaatst naar \'Geen groep\'.</string>
<string name="adding_new_slot_error">Er is een fout opgetreden tijdens het toevoegen van een nieuwe vergrendelingssleutel:</string>
<string name="details">Details</string>
<string name="filter">Filter</string>
<string name="lock">Vergrendel</string>
<string name="all">Alles</string>
<string name="name">Naam</string>
<string name="no_group">Geen groep</string>
<string name="sort_alphabetically">Uitgever (A t/m Z)</string>
<string name="sort_alphabetically_reverse">Uitgever (Z t/m A)</string>
<string name="sort_alphabetically_name">Account (A t/m Z)</string>
<string name="sort_alphabetically_name_reverse">Account (Z t/m A)</string>
<string name="sort_custom">Aangepast</string>
<string name="new_group">Nieuwe groep…</string>
<string name="enter_group_name">Vul een groepsnaam in</string>
<string name="group_name_hint">Groepsnaam</string>
<string name="preference_manage_groups">Groepen bewerken</string>
<string name="preference_manage_groups_summary">Beheer en verwijder hier je groepen</string>
<string name="tap_to_reveal">Verborgen</string>
<string name="dark_theme_title">Donker thema</string>
<string name="light_theme_title">Licht thema</string>
<string name="amoled_theme_title">Amoled thema</string>
<string name="selected">Geselecteerd</string>
<string name="normal_viewmode_title">Normaal</string>
<string name="compact_mode_title">Compact</string>
<string name="small_mode_title">Klein</string>
</resources>
+138
View File
@@ -0,0 +1,138 @@
<resources>
<string name="app_name">Aegis</string>
<string name="app_name_dev">AegisDev</string>
<string name="action_settings">Настройки</string>
<string name="action_import">Импорт</string>
<string name="action_delete">Удалить</string>
<string name="action_default_icon">Восстановить значок по умолчанию</string>
<string name="discard">Отмена</string>
<string name="save">Сохранить</string>
<string name="issuer">Эмитент</string>
<string name="settings">Предпочтения</string>
<string name="pref_appearance_group_title">Appearance</string>
<string name="pref_security_group_title">Security</string>
<string name="pref_tools_group_title">Tools</string>
<string name="pref_select_theme_title">Тема</string>
<string name="pref_view_mode_title">Select view mode</string>
<string name="pref_account_name_title">Показать имя аккаунта</string>
<string name="pref_account_name_summary">Включите это, чтобы показать имя учетной записи рядом с эмитентом</string>
<string name="pref_timeout_title">Тайм-аут</string>
<string name="pref_timeout_summary">Автоматическая блокировка базы данных после %1$s секунд бездействия</string>
<string name="pref_slots_title">Ключевые слоты</string>
<string name="pref_slots_summary">Управление списком ключей которые могут расшифровать базу данных</string>
<string name="pref_import_file_title">Импорт из файла</string>
<string name="pref_import_file_summary">Импортировать базу данных из файла</string>
<string name="pref_import_app_title">Импорт из приложения</string>
<string name="pref_import_app_summary">Импорт базы данных из приложения (требуется root-доступ)</string>
<string name="pref_export_title">Экспорт</string>
<string name="pref_export_summary">Экспорт базы данных</string>
<string name="pref_secure_screen_title">Безопасность экрана</string>
<string name="pref_secure_screen_summary">Блокировка скриншотов и других попыток захвата экрана в приложении</string>
<string name="pref_tap_to_reveal_title">Нажмите, чтобы показать</string>
<string name="pref_tap_to_reveal_summary">Токены будут скрыты по умолчанию. Нажмите на токены, чтобы показать код.</string>
<string name="pref_tap_to_reveal_time_title">Тайм-аут для показа экрана</string>
<string name="pref_encryption_title">Шифрование</string>
<string name="pref_encryption_summary">Зашифруйте базу данных и разблокируйте ее с помощью пароля или отпечатка пальца</string>
<string name="pref_fingerprint_title">Отпечаток пальца</string>
<string name="pref_fingerprint_summary">Разрешить отпечатки пальцев зарегистрированных на этом устройстве, чтобы разблокировать хранилище</string>
<string name="pref_set_password_title">Изменить пароль</string>
<string name="pref_set_password_summary">Установите новый пароль который вам понадобится, чтобы разблокировать ваше хранилище</string>
<string name="fingerprint_hint">Сенсорный датчик</string>
<string name="fingerprint_not_recognized">Отпечаток пальца не распознан. Попробуйте снова.</string>
<string name="fingerprint_success">Отпечаток пальца распознан</string>
<string name="choose_authentication_method">Безопасность</string>
<string name="authentication_method_none">Нет</string>
<string name="authentication_method_none_description">Вам не нужен пароль для разблокировки хранилища, и он не будет зашифрован. Этот вариант не рекомендуется.</string>
<string name="authentication_method_password">Пароль</string>
<string name="authentication_method_password_description">Вам нужен пароль чтобы разблокировать хранилище.</string>
<string name="authentication_method_fingerprint">Отпечаток</string>
<string name="authentication_method_fingerprint_description">В дополнение к паролю для разблокировки хранилища можно использовать отпечатки пальцев, зарегистрированные на этом устройстве.</string>
<string name="authentication_method_set_password">Пароль</string>
<string name="authentication_enter_password">Введите свой пароль</string>
<string name="authentication">Разблокировать хранилище</string>
<string name="set_password">Пожалуйста, введите пароль</string>
<string name="set_group">Пожалуйста, введите название группы</string>
<string name="set_number">Пожалуйста, введите номер</string>
<string name="set_password_confirm">Пожалуйста, подтвердите пароль</string>
<string name="invalidated_fingerprint">Обнаружено изменение настроек безопасности вашего устройства. Перейдите в «Настройки - Отпечаток пальца» и повторно добавьте свой отпечаток пальца.</string>
<string name="unlock">Разблокировать</string>
<string name="advanced">Расширенные</string>
<string name="seconds">секунд</string>
<string name="counter">Счетчик</string>
<string name="digits">Цифры</string>
<string name="secret">Секрет</string>
<string name="scan">Сканировать QR-код</string>
<string name="enter_manually">Введите вручную</string>
<string name="add_fingerprint">Добавить отпечаток</string>
<string name="add_password">Добавить паспорт</string>
<string name="slots_warning">Хранилище надежно защищено. При добавлении нового отпечатка пальца на ваше устройство вы активируете аутентификацию по отпечатку пальца в Aegis.</string>
<string name="copy">Копировать</string>
<string name="edit">Изменить</string>
<string name="delete">Удалить</string>
<string name="password">Пароль</string>
<string name="confirm_password">Подтвердите пароль</string>
<string name="new_profile">Новый профиль</string>
<string name="add_new_profile">Добавить новый профиль</string>
<string name="unlock_vault_error">Не удалось разблокировать хранилище</string>
<string name="unlock_vault_error_description">Неверный пароль. Убедитесь, что вы не опечатали свой пароль.</string>
<string name="password_equality_error">Пароли должны быть одинаковыми и непустыми</string>
<string name="register_fingerprint">Зарегистрируйте свой отпечаток пальца</string>
<string name="snackbar_authentication_method">Пожалуйста, выберите метод аутентификации</string>
<string name="encrypting_vault">Шифрование хранилища</string>
<string name="delete_entry">Удалить запись</string>
<string name="delete_entry_description">Вы уверены что хотите удалить эту запись?</string>
<string name="discard_changes">Отменить изменения?</string>
<string name="discard_changes_description">Ваши изменения не были сохранены</string>
<string name="folder">Папка</string>
<string name="tap_to_select">Нажмите, чтобы выбрать</string>
<string name="saving_profile_error">Ошибка сохранения профиля</string>
<string name="welcome">Добро пожаловать</string>
<string name="app_description">Aegis - бесплатное, безопасное и открытое приложение 2FA.</string>
<string name="setup_completed">Настройка завершена</string>
<string name="setup_completed_description">Aegis настроен и готов к работе.</string>
<string name="vault_not_found">Хранилище не найдено, запуск установки…</string>
<string name="code_copied">Код скопирован в буфер обмена</string>
<string name="decryption_error">Произошла ошибка при попытке разблокировать хранилище</string>
<string name="saving_error">Произошла ошибка при попытке сохранить хранилище</string>
<string name="disable_encryption">Отключить шифрование</string>
<string name="disable_encryption_description">Вы уверены что хотите отключить шифрование? Это приведет к хранению хранилища в виде простого текста.</string>
<string name="enable_encryption_error">Произошла ошибка при включении шифрования</string>
<string name="permission_denied">Доступ запрещен</string>
<string name="choose_application">Выберите приложение из которого вы хотите импортировать базу данных.</string>
<string name="choose_theme">Выберите тему</string>
<string name="parsing_file_error">Произошла ошибка при попытке разобрать файл</string>
<string name="file_not_found">Ошибка: файл не найден</string>
<string name="reading_file_error">Произошла ошибка при попытке прочитать файл</string>
<string name="root_error">Ошибка: невозможно получить root-доступ</string>
<string name="imported_entries_count">Импортироаано %d записей. %d ошибки.</string>
<string name="exporting_database_error">Произошла ошибка при попытке экспорта базы данных</string>
<string name="export_database_location">База данных была экспортирована в:</string>
<string name="export_warning">Это действие экспортирует базу данных из личного хранилища Aegis.</string>
<string name="encryption_set_password_error">Произошла ошибка при попытке установить пароль:</string>
<string name="encryption_enable_fingerprint_error">Произошла ошибка при попытке разблокировать отпечаток пальца:</string>
<string name="no_cameras_available">Нет доступных камер</string>
<string name="read_qr_error">Произошла ошибка при попытке прочитать QR-код</string>
<string name="authentication_method_raw">Необработанный</string>
<string name="unlocking_vault">Разблокировка хранилища</string>
<string name="password_slot_error">У вас должен быть хотя бы один слот для пароля</string>
<string name="remove_slot">Удалить слот</string>
<string name="remove_slot_description">Вы уверены что хотите удалить этот слот?</string>
<string name="remove_group">Удалить группу</string>
<string name="remove_group_description">Вы уверены что хотите удалить эту группу? Записи в этой группе автоматически переключатся на «Нет группы».</string>
<string name="adding_new_slot_error">Произошла ошибка при попытке добавить новый слот:</string>
<string name="filter">Фильтр</string>
<string name="lock">Заблокировать</string>
<string name="all">Все</string>
<string name="name">Имя</string>
<string name="no_group">Нет группы</string>
<string name="new_group">Новая группа...</string>
<string name="enter_group_name">Введите название группы</string>
<string name="group_name_hint">Название группы</string>
<string name="preference_manage_groups">Редактировать группы</string>
<string name="preference_manage_groups_summary">Управляйте и удаляйте свои группы здесь</string>
<string name="tap_to_reveal">Скрытый</string>
</resources>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="authentication_methods">
<item>@string/authentication_method_none</item>
<item>@string/authentication_method_password</item>
<item>@string/authentication_method_fingerprint</item>
</string-array>
<string-array name="otp_types_array">
<item>TOTP</item>
<item>HOTP</item>
<item>Steam</item>
</string-array>
<string-array name="otp_algo_array">
<item>SHA1</item>
<item>SHA256</item>
<item>SHA512</item>
</string-array>
</resources>
+27 -23
View File
@@ -8,10 +8,13 @@
<string name="discard">Discard</string>
<string name="save">Save</string>
<string name="issuer">Issuer</string>
<string name="title_activity_intro">IntroActivity</string>
<string name="settings">Preferences</string>
<string name="pref_appearance_group_title">Appearance</string>
<string name="pref_security_group_title">Security</string>
<string name="pref_tools_group_title">Tools</string>
<string name="pref_select_theme_title">Theme</string>
<string name="pref_view_mode_title">View mode</string>
<string name="pref_account_name_title">Show the account name</string>
<string name="pref_account_name_summary">Enable this to show the account name next to the issuer</string>
<string name="pref_timeout_title">Timeout</string>
@@ -29,6 +32,8 @@
<string name="pref_tap_to_reveal_title">Tap to reveal</string>
<string name="pref_tap_to_reveal_summary">Tokens will be hidden by default. Tap on the tokens to reveal code.</string>
<string name="pref_tap_to_reveal_time_title">Timeout for tap to reveal</string>
<string name="pref_auto_lock_title">Auto lock</string>
<string name="pref_auto_lock_summary">Automatically lock when you close the app or lock your device.</string>
<string name="pref_encryption_title">Encryption</string>
<string name="pref_encryption_summary">Encrypt the database and unlock it with a password or fingerprint</string>
<string name="pref_fingerprint_title">Fingerprint</string>
@@ -56,25 +61,6 @@
<string name="set_password_confirm">Please confirm the password</string>
<string name="invalidated_fingerprint">A change in your device\'s security settings has been detected. Please go to \"Settings -> Fingerprint\" and re-add your fingerprint.</string>
<string-array name="authentication_methods">
<item>None</item>
<item>Password</item>
<item>Fingerprint</item>
</string-array>
<string-array name="otp_types_array">
<item>TOTP</item>
<item>HOTP</item>
</string-array>
<string-array name="otp_algo_array">
<item>SHA1</item>
<item>SHA256</item>
<item>SHA512</item>
</string-array>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="unlock">Unlock</string>
<string name="advanced">Advanced</string>
<string name="seconds">seconds</string>
@@ -112,19 +98,23 @@
<string name="setup_completed_description">Aegis has been setup and is ready to go.</string>
<string name="vault_not_found">Vault not found, starting setup…</string>
<string name="code_copied">Code copied to the clipboard</string>
<string name="errors_copied">Errors copied to the clipboard</string>
<string name="decryption_error">An error occurred while trying to unlock the vault</string>
<string name="saving_error">An error occurred while trying to save the vault</string>
<string name="disable_encryption">Disable encryption</string>
<string name="disable_encryption_description">Are you sure you want to disable encryption? This will cause the vault to be stored in plain text.</string>
<string name="encrypting_error">An error occurred while enabling encryption</string>
<string name="enable_encryption_error">An error occurred while enabling encryption</string>
<string name="disable_encryption_error">An error occurred while disabling encryption</string>
<string name="permission_denied">Permission denied</string>
<string name="choose_application">Select the application you\'d like to import a database from</string>
<string name="choose_theme">Select your desired theme</string>
<string name="choose_view_mode">Select your desired view mode</string>
<string name="parsing_file_error">An error occurred while trying to parse the file</string>
<string name="file_not_found">Error: File not found</string>
<string name="reading_file_error">An error occurred while trying to read the file</string>
<string name="root_error">Error: unable to obtain root access</string>
<string name="imported_entries_count">Imported %d entries</string>
<string name="imported_entries_count">Imported %d entries. %d errors.</string>
<string name="import_error_title">One or more errors occurred during the import</string>
<string name="exporting_database_error">An error occurred while trying to export the database</string>
<string name="export_database_location">The database has been exported to:</string>
<string name="export_warning">This action will export the database out of Aegis\' private storage.</string>
@@ -140,15 +130,29 @@
<string name="remove_group">Remove group</string>
<string name="remove_group_description">Are you sure you want to remove this group? Entries in this group will automatically switch to \'No group\'.</string>
<string name="adding_new_slot_error">An error occurred while trying to add a new slot:</string>
<string name="progressbar_error">Unable to reset animator duration scale. Progress bars will be invisible</string>
<string name="details">Details</string>
<string name="filter">Filter</string>
<string name="lock">Lock</string>
<string name="all">All</string>
<string name="name">Name</string>
<string name="no_group">No group</string>
<string name="new_group">New group...</string>
<string name="sort_alphabetically">Issuer (A to Z)</string>
<string name="sort_alphabetically_reverse">Issuer (Z to A)</string>
<string name="sort_alphabetically_name">Account (A to Z)</string>
<string name="sort_alphabetically_name_reverse">Account (Z to A)</string>
<string name="sort_custom">Custom</string>
<string name="new_group">New group…</string>
<string name="enter_group_name">Enter a group name</string>
<string name="group_name_hint">Group name</string>
<string name="preference_manage_groups">Edit groups</string>
<string name="preference_manage_groups_summary">Manage and delete your groups here</string>
<string name="tap_to_reveal">Hidden</string>
<string name="selected">Selected</string>
<string name="dark_theme_title">Dark theme</string>
<string name="light_theme_title">Light theme</string>
<string name="amoled_theme_title">Amoled theme</string>
<string name="normal_viewmode_title">Normal</string>
<string name="compact_mode_title">Compact</string>
<string name="small_mode_title">Small</string>
</resources>
+15 -4
View File
@@ -5,7 +5,7 @@
android:title="@string/settings">
<PreferenceCategory
android:title="Appearance"
android:title="@string/pref_appearance_group_title"
app:iconSpaceReserved="false">
<Preference
android:defaultValue="false"
@@ -13,6 +13,12 @@
android:title="@string/pref_select_theme_title"
app:iconSpaceReserved="false"/>
<Preference
android:defaultValue="false"
android:key="pref_view_mode"
android:title="@string/pref_view_mode_title"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_account_name"
@@ -28,7 +34,7 @@
</PreferenceCategory>
<PreferenceCategory
android:title="Security"
android:title="@string/pref_security_group_title"
app:iconSpaceReserved="false">
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
@@ -36,13 +42,18 @@
android:title="@string/pref_secure_screen_title"
android:summary="@string/pref_secure_screen_summary"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_auto_lock"
android:title="@string/pref_auto_lock_title"
android:summary="@string/pref_auto_lock_summary"
app:iconSpaceReserved="false"/>
<androidx.preference.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_tap_to_reveal"
android:title="@string/pref_tap_to_reveal_title"
android:summary="@string/pref_tap_to_reveal_summary"
app:iconSpaceReserved="false"/>
<Preference
android:key="pref_tap_to_reveal_time"
android:title="@string/pref_tap_to_reveal_time_title"
@@ -86,7 +97,7 @@
</PreferenceCategory>
<PreferenceCategory
android:title="Tools"
android:title="@string/pref_tools_group_title"
app:iconSpaceReserved="false">
<Preference
android:key="pref_import"
@@ -1,12 +1,13 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import org.junit.Test;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import com.beemdevelopment.aegis.crypto.otp.HOTP;
import static org.junit.Assert.*;
public class HOTPTest {
@@ -27,8 +28,8 @@ public class HOTPTest {
@Test
public void vectorsMatch() throws InvalidKeyException, NoSuchAlgorithmException {
for (int i = 0; i < _vectors.length; i++) {
String otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
assertEquals(_vectors[i], otp);
OTP otp = HOTP.generateOTP(_secret, "HmacSHA1", 6, i);
assertEquals(_vectors[i], otp.toString());
}
}
}
@@ -1,13 +1,11 @@
package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import com.beemdevelopment.aegis.encoding.HexException;
import org.junit.Test;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.encoding.HexException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@@ -87,8 +85,8 @@ public class TOTPTest {
return;
}
String otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
assertEquals(vector.OTP, otp);
OTP otp = TOTP.generateOTP(seed, vector.Algo, 8, 30, vector.Time);
assertEquals(vector.OTP, otp.toString());
}
}
}
@@ -1,18 +1,18 @@
Aegis Authenticator is a free, secure and open source app to manage your
2-step verification tokens for your online services.
'''Encryption'''
<b>Encryption</b>
All of your one-time passwords are stored in a vault. If you choose
to set a password, which is highly recommended, the vault will be encrypted
using AES-256. If someone with malicious intent gets a hold of the vault file,
its impossible for them to retrieve the contents without knowing the password.
'''Fingerprint unlock'''
<b>Fingerprint unlock</b>
Entering your password each time you need access to a
one-time password can be cumbersome. Fortunately, you can also enable
fingerprint unlock if your device has a fingerprint scanner.
'''Compatibility'''
<b>Compatibility</b>
Aegis supports the HOTP and TOTP algorithms. These two algorithms are
industry-standard and widely supported, making Aegis compatible with
thousands of services. Some examples are: Google, GitHub, Dropbox, Facebook
@@ -21,19 +21,19 @@ and Instagram.
It is also compatible with Google Authenticator. Any website that shows a QR
code for Google Authenticator also works with Aegis.
'''Groups'''
<b>Groups</b>
Have a lot of one-time passwords? Add them to custom groups for easier
access. Personal, Work and Social can each get their own group.
'''Backups'''
<b>Backups</b>
To make sure you will never lose access to your online accounts Aegis
Authenticator supports exporting your vault which you can import onto a new
device. Aegis Authenticator also allows you to import AndOTP and FreeOTP
databases so switching to Aegis is made easier for you.
'''Open source and license'''
<b>Open source and license</b>
Aegis Authenticator is open source (licensed under GPL v3) and the source code
can be found here: http://github.com/beemdevelopment/Aegis
'''Attribution'''
<b>Attribution</b>
The icon is largely based on a design by Freepik from Flaticon.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Before

Width:  |  Height:  |  Size: 233 KiB

After

Width:  |  Height:  |  Size: 233 KiB

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

Before

Width:  |  Height:  |  Size: 192 KiB

After

Width:  |  Height:  |  Size: 192 KiB

+8 -8
View File
File diff suppressed because one or more lines are too long