diff --git a/.crowdin/config.example.yml b/.crowdin/config.example.yml deleted file mode 100644 index 4ef788a3..00000000 --- a/.crowdin/config.example.yml +++ /dev/null @@ -1,10 +0,0 @@ -project_id: "372633" -api_token: "" -base_path: "../app/src/main" -base_url: "https://api.crowdin.com" -preserve_hierarchy: true - -files: -- source: "res/values/strings.xml" - dest: "strings.xml" - translation: "res/values-%android_code%/%original_file_name%" diff --git a/.github/workflows/build-app-workflow.yaml b/.github/workflows/build-app-workflow.yaml index d3c55f8e..ab511ab7 100644 --- a/.github/workflows/build-app-workflow.yaml +++ b/.github/workflows/build-app-workflow.yaml @@ -35,7 +35,7 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - name: Tests - uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2 + uses: reactivecircus/android-emulator-runner@62dbb605bba737720e10b196cb4220d374026a6d with: api-level: 31 arch: x86_64 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d4570016..b575535b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -14,6 +14,7 @@ jobs: actions: read contents: read security-events: write + if: github.event_name != 'schedule' || github.repository == 'beemdevelopment/Aegis' steps: - name: Checkout uses: actions/checkout@v4 diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index e0fd559c..e7898362 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -13,19 +13,13 @@ jobs: - uses: actions/checkout@v4 - name: Install crowdin-cli run: | - wget https://github.com/crowdin/crowdin-cli/releases/download/3.7.2/crowdin-cli.zip - echo "ee9f838b819ccedc33c9b2537055e5ba7d7934561b24df1e1a6274cbd6e27f2d crowdin-cli.zip" | sha256sum -c + wget https://github.com/crowdin/crowdin-cli/releases/download/4.6.1/crowdin-cli.zip + echo "7afd70de3a747ac631a5bad7866008163ae1d50c4606b5773f0b90a5481ffde2 crowdin-cli.zip" | sha256sum -c unzip crowdin-cli.zip -d crowdin-cli - name: Upload to Crowdin env: - CROWDIN_TOKEN: "${{ secrets.CROWDIN_TOKEN }}" + CROWDIN_PERSONAL_TOKEN: "${{ secrets.CROWDIN_TOKEN }}" run: | - java -jar ./crowdin-cli/3.7.2/crowdin-cli.jar upload sources \ + java -jar ./crowdin-cli/4.6.1/crowdin-cli.jar upload sources \ --no-progress \ - --token "$CROWDIN_TOKEN" \ - --project-id 372633 \ - --base-path app/src/main \ - --source res/values/strings.xml \ - --translation "res/values-%android_code%/%original_file_name%" \ - --dest strings.xml \ --branch master diff --git a/FAQ.md b/FAQ.md index 815490aa..6d514574 100644 --- a/FAQ.md +++ b/FAQ.md @@ -86,6 +86,14 @@ Another common setup is to configure Aegis to back up to a folder on local storage of your device and then have a separate app (like [Syncthing](https://syncthing.net/)) sync that folder anywhere you want. +## Encrypted Backups + +### Why do I not get prompted to enter an encryption password when exporting? + +Aegis uses the same password you have configured to encrypt your vault as the +password which is used when exporting and importing your vault; so when prompted, +you will enter that when importing your vault. + ## Importing ### When importing from Authenticator Plus, an error is shown claiming that Accounts.txt is missing diff --git a/README.md b/README.md index daf1b813..195bd4ed 100644 --- a/README.md +++ b/README.md @@ -123,24 +123,33 @@ documentation](docs/iconpacks.md). Unofficial monochrome-styled 2FA icons. [aegis-icons preview](https://github.com/aegis-icons/aegis-icons) + src="metadata/en-US/images/iconPacks/aegis-icons.png">](https://github.com/aegis-icons/aegis-icons) - [delta-aegis-icons](https://github.com/Delta-Icons/aegis-icons) Delta version of the unofficial monochrome-styled 2FA icon pack aegis-icons. -- [aegis-simple-icons](https://github.com/alexbakker/aegis-simple-icons) * + [delta-icons preview](https://github.com/Delta-Icons/aegis-icons) + +- [aegis-simple-icons](https://github.com/alexbakker/aegis-simple-icons) \* This project periodically generates an icon pack for Aegis based on [Simple Icons](https://simpleicons.org/). -- [aegis-simple-icons-outlined](https://github.com/michaelschattgen/aegis-simple-icons-outlined) * + [aegis-simple-icons preview](https://github.com/alexbakker/aegis-simple-icons) + +- [aegis-simple-icons-outlined](https://github.com/michaelschattgen/aegis-simple-icons-outlined) \* This is a variant on the aegis-simple-icons pack where the icons contain no solid background and just the outlines are being used. - + + [aegis-simple-icons-outlined preview](https://github.com/michaelschattgen/aegis-simple-icons-outlined) + \* The icons are automatically generated, so - not all of them are as high quality as the ones you'll find in - [aegis-icons](https://github.com/aegis-icons/aegis-icons). +not all of them are as high quality as the ones you'll find in +[aegis-icons](https://github.com/aegis-icons/aegis-icons). ## Contributing @@ -158,5 +167,6 @@ This project is licensed under the GNU General Public License v3.0. See the A couple of libraries vendored in Aegis' repository are licensed under a different license: + - [TextDrawable](app/src/main/java/com/amulyakhare/textdrawable) - [TrustedIntents](app/src/main/java/info/guardianproject/trustedintents) diff --git a/app/build.gradle b/app/build.gradle index a5d0d21c..43512d69 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,10 +26,10 @@ android { defaultConfig { applicationId "${packageName}" - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 35 - versionCode 74 - versionName "3.3.1" + versionCode 79 + versionName "3.4" multiDexEnabled true buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\"" buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\"" @@ -93,16 +93,27 @@ android { } } + // Required to make the APK reproducible + aaptOptions { + cruncherEnabled = false + } + defaultConfig { + vectorDrawables.generatedDensities = [] + } + packagingOptions { // R8 doesn't remove these resources, so exclude them manually. This reduces APK size by 4MB. resources { - excludes += ['/org/bouncycastle/pqc/**/*.properties'] + excludes += [ + '/org/bouncycastle/pqc/**/*.properties', + 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' + ] } } compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 + targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_17 coreLibraryDesugaringEnabled true } lint { @@ -141,13 +152,13 @@ aboutLibraries { } dependencies { - def cameraxVersion = '1.4.0' + def cameraxVersion = '1.4.2' def glideVersion = '4.16.0' - def guavaVersion = '33.3.1' - def hiltVersion = '2.52' + def guavaVersion = '33.4.8' + def hiltVersion = '2.56.2' def junitVersion = '4.13.2' def libsuVersion = '6.0.0' - def roomVersion = "2.6.1" + def roomVersion = '2.7.1' annotationProcessor 'androidx.annotation:annotation:1.9.1' annotationProcessor "androidx.room:room-compiler:$roomVersion" @@ -155,18 +166,18 @@ dependencies { annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}" implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.activity:activity:1.9.3' + implementation 'androidx.activity:activity:1.10.1' implementation 'androidx.appcompat:appcompat:1.7.0' implementation "androidx.biometric:biometric:1.1.0" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" implementation "androidx.camera:camera-view:$cameraxVersion" - implementation 'androidx.core:core:1.15.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.0' - implementation 'androidx.documentfile:documentfile:1.0.1' - implementation 'androidx.lifecycle:lifecycle-process:2.8.7' + implementation 'androidx.core:core:1.16.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.documentfile:documentfile:1.1.0' + implementation 'androidx.lifecycle:lifecycle-process:2.9.0' implementation "androidx.preference:preference:1.2.1" - implementation 'androidx.recyclerview:recyclerview:1.3.2' + implementation 'androidx.recyclerview:recyclerview:1.4.0' implementation "androidx.room:room-runtime:$roomVersion" implementation 'androidx.viewpager2:viewpager2:1.1.0' implementation 'com.caverock:androidsvg-aar:1.4' @@ -181,7 +192,7 @@ dependencies { implementation "com.github.topjohnwu.libsu:io:${libsuVersion}" implementation "com.google.guava:guava:${guavaVersion}-android" implementation 'com.google.android.material:material:1.12.0' - implementation 'com.google.protobuf:protobuf-javalite:4.28.3' + implementation 'com.google.protobuf:protobuf-javalite:4.31.0' implementation 'com.google.zxing:core:3.5.3' implementation('com.mikepenz:aboutlibraries:11.2.3') { exclude group: 'com.mikepenz', module: 'aboutlibraries-core' @@ -189,7 +200,7 @@ dependencies { implementation 'com.mikepenz:aboutlibraries-core-android:11.2.3' implementation 'com.nulab-inc:zxcvbn:1.9.0' implementation 'net.lingala.zip4j:zip4j:2.11.5' - implementation 'org.bouncycastle:bcprov-jdk18on:1.79' + implementation 'org.bouncycastle:bcprov-jdk18on:1.80' implementation 'org.simpleflatmapper:sfm-csv:8.2.3' androidTestAnnotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion" @@ -207,8 +218,8 @@ dependencies { testImplementation 'androidx.test:core:1.6.1' testImplementation "com.google.guava:guava:${guavaVersion}-jre" testImplementation "junit:junit:${junitVersion}" - testImplementation 'org.json:json:20240303' - testImplementation 'org.robolectric:robolectric:4.14' + testImplementation 'org.json:json:20250517' + testImplementation 'org.robolectric:robolectric:4.14.1' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.3' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' } diff --git a/app/src/androidTest/java/com/beemdevelopment/aegis/BackupExportTest.java b/app/src/androidTest/java/com/beemdevelopment/aegis/BackupExportTest.java index 9f15019b..e0151395 100644 --- a/app/src/androidTest/java/com/beemdevelopment/aegis/BackupExportTest.java +++ b/app/src/androidTest/java/com/beemdevelopment/aegis/BackupExportTest.java @@ -61,13 +61,20 @@ import org.junit.Test; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.RunWith; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.List; +import java.util.Locale; import javax.crypto.Cipher; import javax.crypto.SecretKey; @@ -183,7 +190,9 @@ public class BackupExportTest extends AegisTest { onView(withText(R.string.export_format_html)).inRoot(RootMatchers.isPlatformPopup()).perform(click()); onView(withId(android.R.id.button1)).perform(click()); onView(withId(R.id.checkbox_accept)).perform(click()); - doExport(); + File file = doExport(); + + checkHtmlExport(file); } @Test @@ -196,7 +205,9 @@ public class BackupExportTest extends AegisTest { onView(withText(R.string.export_format_html)).inRoot(RootMatchers.isPlatformPopup()).perform(click()); onView(withId(android.R.id.button1)).perform(click()); onView(withId(R.id.checkbox_accept)).perform(click()); - doExport(); + File file = doExport(); + + checkHtmlExport(file); } @Test @@ -380,6 +391,26 @@ public class BackupExportTest extends AegisTest { checkReadEntries(entries); } + private void checkHtmlExport(File file) { + try (InputStream inStream = new FileInputStream(file)) { + Reader inReader = new InputStreamReader(inStream, StandardCharsets.UTF_8); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(inReader); + while (parser.getEventType() != XmlPullParser.START_TAG) { + parser.next(); + } + if (!parser.getName().toLowerCase(Locale.ROOT).equals("html")) { + throw new RuntimeException("not an html document!"); + } + while (parser.getEventType() != XmlPullParser.END_DOCUMENT) { + parser.next(); + } + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException("Unable to read html export file", e); + } + } + private void checkReadEntries(Collection entries) { List vectors = VaultEntries.get(); assertEquals(vectors.size(), entries.size()); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d3ea78bb..09337184 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + @android:color/transparent diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2725c77f..4fe617bd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,6 +236,8 @@ Please select an authentication method Encrypting the vault Exporting the vault + Optimizing icon + Optimizing icons %1$d/%2$d Reading file Requesting root access Analyzing QR code @@ -371,8 +373,27 @@ Note Clear + Duplicate entry + This entry has the same name and issuer as one or more existing entries. How would you like to proceed? + Overwrite existing entry/entries + Replace the existing entry or entries with the new one. This action cannot be undone + Add suffix + Add a suffix to the name of this new entry. The new name will be: %s + Cancel save + Allows you to edit the entry before attempting to save it again + + Are you sure you want to delete %d entry with the following name:\n\n%s - %s + Are you sure you want to delete %d entries with the following name:\n\n%s - %s + + + Confirm deletion + + Make your device vibrate when codes are refreshing + Haptic feedback Highlight tokens when tapped Make tokens easier to distinguish from each other by temporarily highlighting them when tapped + Multiselect groups + Allow the selection of multiple groups at the same time Minimize on copy Minimize the app after copying a token Copy tokens to the clipboard @@ -493,6 +514,7 @@ Unable to copy URI to clipboard URI copied to clipboard Scan this QR code with the authenticator app you would like to transfer this entry to + Tap the QR code to toggle full screen brightness Scan these QR codes with Aegis or Google Authenticator.\n\nDue to limitations of the Google Authenticator app, only TOTP & HOTP tokens that use SHA1 and produce 6-digit codes are included Very weak @@ -530,7 +552,6 @@ Supply a 2FAS Authenticator backup file. Supply an Aegis export/backup file. Supply an Authenticator Plus export file obtained through Settings -> Backup & Restore -> Export as Text and HTML. - Supply an Authenticator Pro export file obtained through Settings -> Back up -> Back up to encrypted file (recommended). Supply a copy of /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, located in the internal storage directory of Authy. Supply an andOTP export/backup file. Supply a Bitwarden export/backup file. Encrypted files are not supported. @@ -545,6 +566,7 @@ Supply a copy of /data/data/com.azure.authenticator/databases/PhoneFactor, located in the internal storage directory of Microsoft Authenticator. Supply a plain text file with a Google Authenticator URI on each line. Steam v3.0 and newer are not supported. Supply a copy of /data/data/com.valvesoftware.android.steam.community/files/Steamguard-*.json, located in the internal storage directory of Steam. + Supply a Stratum export file obtained through Settings -> Back up -> Back up to encrypted file (recommended). Supply a TOTP Authenticator export file. Supply a WinAuth export file. Assign icons diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 7ae582e9..f490f38b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -62,6 +62,7 @@ @color/aegis_theme_light_success @color/aegis_theme_light_onSurfaceDim ?attr/colorPrimary + ?attr/colorOutlineVariant ?attr/colorSurfaceVariant ?attr/colorOnSurfaceVariant @@ -132,6 +133,7 @@ @color/aegis_theme_dark_success @color/aegis_theme_dark_onSurfaceDim ?attr/colorPrimary + ?attr/colorOutlineVariant ?attr/colorSurfaceVariant ?attr/colorOnSurfaceVariant @@ -159,6 +161,7 @@ #000000 #000000 @android:color/white + #2F2F2F @android:color/white @@ -179,16 +182,25 @@ #000000 #000000 @android:color/white + #2F2F2F @android:color/white