diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8132f216..729ce17f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -116,6 +116,7 @@ + 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 2e5fa4b4..cd72dca4 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -32,6 +32,7 @@ public abstract class DatabaseImporter { _importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false)); _importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false)); _importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true)); + _importers.add(new Definition("DUO", DuoImporter.class, R.string.importer_help_duo, true)); _importers.add(new Definition("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true)); _importers.add(new Definition("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true)); _importers.add(new Definition("Google Authenticator", GoogleAuthImporter.class, R.string.importer_help_google_authenticator, true)); diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java new file mode 100644 index 00000000..b449e940 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DuoImporter.java @@ -0,0 +1,103 @@ +package com.beemdevelopment.aegis.importers; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.otp.HotpInfo; +import com.beemdevelopment.aegis.otp.OtpInfo; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.TotpInfo; +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 DuoImporter extends DatabaseImporter { + private static final String _pkgName = "com.duosecurity.duomobile"; + private static final String _subPath = "files/duokit/accounts.json"; + + public DuoImporter(Context context) { + super(context); + } + + @Override + protected @NonNull SuFile getAppPath() throws DatabaseImporterException, NameNotFoundException { + return getAppPath(_pkgName, _subPath); + } + + @Override + protected @NonNull State read( + @NonNull InputStream stream, boolean isInternal + ) throws DatabaseImporterException { + try { + String contents = new String(IOUtils.readAll(stream), UTF_8); + return new DecryptedState(new JSONArray(contents)); + } catch (JSONException | IOException e) { + throw new DatabaseImporterException(e); + } + } + + public static class DecryptedState extends DatabaseImporter.State { + private final JSONArray _array; + + public DecryptedState(@NonNull JSONArray array) { + super(false); + _array = array; + } + + @Override + public @NonNull Result convert() throws DatabaseImporterException { + Result result = new Result(); + + try { + for (int i = 0; i < _array.length(); i++) { + JSONObject entry = _array.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 { + String label = entry.optString("name"); + + JSONObject otpData = entry.getJSONObject("otpGenerator"); + + byte[] secret = Base32.decode(otpData.getString("otpSecret")); + + Long counter = otpData.has("counter") ? otpData.getLong("counter") : null; + + OtpInfo otp = counter == null + ? new TotpInfo(secret) + : new HotpInfo(secret, counter); + + return new VaultEntry(otp, label, ""); + } catch (JSONException | OtpInfoException | EncodingException e) { + throw new DatabaseImporterEntryException(e, entry.toString()); + } + } + } +} diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index cadf7b53..d5fe5aaf 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -370,6 +370,7 @@ Предоставьте файл экспорта Authenticator Plus, полученный через «Настройки» → «Рез. копия и восстановление» → «Экспорт текста и HTML». Предоставьте копию файла /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, расположенного в папке Authy во внутренней памяти. Предоставьте файл экспорта/резервной копии andOTP. + Предоставьте копию файла /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, расположенного в папке DUO во внутренней памяти. Предоставьте копию файла /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, расположенного в папке FreeOTP во внутренней памяти. Предоставьте файл экспорта FreeOTP+. Предоставьте копию файла /data/data/com.google.android.apps.authenticator2/databases/databases, расположенного в папке Google Authenticator во внутренней памяти. diff --git a/app/src/main/res/values-uk-rUA/strings.xml b/app/src/main/res/values-uk-rUA/strings.xml index cc4c4d86..2d7b675e 100644 --- a/app/src/main/res/values-uk-rUA/strings.xml +++ b/app/src/main/res/values-uk-rUA/strings.xml @@ -337,6 +337,7 @@ Надайте файл експорту Authenticator Plus, отриманий через Settings -> Backup & Restore -> Export as Text and HTML. Надайте копію /data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, що знаходиться в каталозі Authy у внутрішній пам\'яті. Надайте файл експорту/резервної копії andOTP. + Надайте копію /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, що знаходиться в каталозі DUO у внутрішній пам\'яті. Надайте копію /data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, що знаходиться в каталозі FreeOTP у внутрішній пам\'яті. Надайте файл експортований з FreeOTP+. Надайте копію /data/com.google.android.apps.authenticator2/databases/databases, що знаходиться в каталозі Google Authenticator у внутрішній пам\'яті. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bcf88bfb..e50b47c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -391,6 +391,7 @@ Supply an Authenticator Plus export file obtained through Settings -> Backup & Restore -> Export as Text and HTML. 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 copy of /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, located in the internal storage directory of DUO. Supply a copy of /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, located in the internal storage directory of FreeOTP. Supply a FreeOTP+ export file. Supply a copy of /data/data/com.google.android.apps.authenticator2/databases/databases, located in the internal storage directory of Google Authenticator.