diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java
index fe1b5b5f..6c9bc6fe 100644
--- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java
+++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java
@@ -44,6 +44,7 @@ public abstract class DatabaseImporter {
_importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true));
_importers.add(new Definition("Microsoft Authenticator", MicrosoftAuthImporter.class, R.string.importer_help_microsoft_authenticator, true));
_importers.add(new Definition("Plain text", GoogleAuthUriImporter.class, R.string.importer_help_plain_text, false));
+ _importers.add(new Definition("Proton Authenticator", ProtonAuthenticatorImporter.class, R.string.importer_help_proton_authenticator, false));
_importers.add(new Definition("Steam", SteamImporter.class, R.string.importer_help_steam, true));
_importers.add(new Definition("Stratum (Authenticator Pro)", StratumImporter.class, R.string.importer_help_stratum, true));
_importers.add(new Definition("TOTP Authenticator", TotpAuthenticatorImporter.class, R.string.importer_help_totp_authenticator, true));
diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/ProtonAuthenticatorImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/ProtonAuthenticatorImporter.java
new file mode 100644
index 00000000..2ac34db4
--- /dev/null
+++ b/app/src/main/java/com/beemdevelopment/aegis/importers/ProtonAuthenticatorImporter.java
@@ -0,0 +1,96 @@
+package com.beemdevelopment.aegis.importers;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+
+import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
+import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
+import com.beemdevelopment.aegis.otp.OtpInfo;
+import com.beemdevelopment.aegis.util.IOUtils;
+import com.beemdevelopment.aegis.vault.VaultEntry;
+import com.topjohnwu.superuser.io.SuFile;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtonAuthenticatorImporter extends DatabaseImporter {
+
+ public ProtonAuthenticatorImporter(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected SuFile getAppPath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected @NonNull State read(@NonNull InputStream stream, boolean isInternal) throws DatabaseImporterException {
+ try {
+ String contents = new String(IOUtils.readAll(stream), UTF_8);
+ JSONObject json = new JSONObject(contents);
+
+ return new DecryptedState(json);
+ } catch (JSONException | IOException e) {
+ throw new DatabaseImporterException(e);
+ }
+ }
+
+ public static class DecryptedState extends DatabaseImporter.State {
+ private final JSONObject _json;
+
+ public DecryptedState(@NonNull JSONObject json) {
+ super(false);
+ _json = json;
+ }
+
+ @Override
+ public @NonNull Result convert() throws DatabaseImporterException {
+ Result result = new Result();
+
+ try {
+ JSONArray entries = _json.getJSONArray("entries");
+ for (int i = 0; i < entries.length(); i++) {
+ JSONObject entry = entries.getJSONObject(i);
+ try {
+ result.addEntry(convertEntry(entry));
+ } catch (DatabaseImporterEntryException e) {
+ result.addError(e);
+ }
+ }
+ } catch (JSONException e) {
+ throw new DatabaseImporterException(e);
+ }
+
+ return result;
+ }
+
+ private static @NonNull VaultEntry convertEntry(@NonNull JSONObject entry) throws DatabaseImporterEntryException {
+ try {
+ JSONObject content = entry.getJSONObject("content");
+ String name = content.getString("name");
+ String uriString = content.getString("uri");
+
+ Uri uri = Uri.parse(uriString);
+ try {
+ GoogleAuthInfo info = GoogleAuthInfo.parseUri(uri);
+ OtpInfo otp = info.getOtpInfo();
+
+ return new VaultEntry(otp, name, info.getIssuer());
+ } catch (GoogleAuthInfoException e) {
+ throw new DatabaseImporterEntryException(e, uriString);
+ }
+ } catch (JSONException e) {
+ throw new DatabaseImporterEntryException(e, entry.toString());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4fe617bd..9014aa46 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -565,6 +565,7 @@
Only database files from Google Authenticator v5.10 and prior are supported.\n\nSupply a copy of /data/data/com.google.android.apps.authenticator2/databases/databases, located in the internal storage directory of Google Authenticator.
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.
+ Supply a Proton Authenticator export file (.json) obtained through Settings -> Export.
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.
diff --git a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java
index 1457d118..d1b7beb6 100644
--- a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java
+++ b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java
@@ -390,6 +390,12 @@ public class DatabaseImporterTest {
checkImportedEntries(entries);
}
+ @Test
+ public void testImportProtonAuthenticator() throws IOException, DatabaseImporterException, OtpInfoException {
+ List entries = importPlain(ProtonAuthenticatorImporter.class, "proton_authenticator.json");
+ checkImportedEntries(entries);
+ }
+
private List importPlain(Class extends DatabaseImporter> type, String resName)
throws IOException, DatabaseImporterException {
return importPlain(type, resName, false);
diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/proton_authenticator.json b/app/src/test/resources/com/beemdevelopment/aegis/importers/proton_authenticator.json
new file mode 100644
index 00000000..d7897990
--- /dev/null
+++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/proton_authenticator.json
@@ -0,0 +1,32 @@
+{
+ "version": 1,
+ "entries": [
+ {
+ "id": "a19e0019-47e3-4d3b-af63-6dad0cb49e31",
+ "content": {
+ "uri": "otpauth://totp/Deno:Mason?secret=4SJHB4GSD43FZBAI7C2HLRJGPQ&issuer=Deno&algorithm=SHA1&digits=6&period=30",
+ "entry_type": "Totp",
+ "name": "Mason"
+ },
+ "note": null
+ },
+ {
+ "id": "4603c576-5d93-4c7f-8c26-bed6dc4493fc",
+ "content": {
+ "uri": "otpauth://totp/SPDX:James?secret=5OM4WOOGPLQEF6UGN3CPEOOLWU&issuer=SPDX&algorithm=SHA256&digits=7&period=20",
+ "entry_type": "Totp",
+ "name": "James"
+ },
+ "note": null
+ },
+ {
+ "id": "4fae25ae-16ed-4f15-a887-3bbb50b34d5e",
+ "content": {
+ "uri": "otpauth://totp/Airbnb:Elijah?secret=7ELGJSGXNCCTV3O6LKJWYFV2RA&issuer=Airbnb&algorithm=SHA512&digits=8&period=50",
+ "entry_type": "Totp",
+ "name": "Elijah"
+ },
+ "note": null
+ }
+ ]
+}
\ No newline at end of file