mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-28 17:48:11 +00:00
Merge pull request #73 from alexbakker/refactor-import
Refactor database importer logic
This commit is contained in:
commit
83b2f85ff4
14 changed files with 721 additions and 604 deletions
|
@ -1,84 +0,0 @@
|
||||||
package com.beemdevelopment.aegis.importers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public AegisFileImporter(Context context, ByteInputStream stream) {
|
|
||||||
super(context, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse() throws DatabaseImporterException {
|
|
||||||
try {
|
|
||||||
byte[] bytes = _stream.getBytes();
|
|
||||||
_file = DatabaseFile.fromBytes(bytes);
|
|
||||||
} catch (DatabaseFileException e) {
|
|
||||||
throw new DatabaseImporterException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DatabaseImporterResult convert() throws DatabaseImporterException {
|
|
||||||
DatabaseImporterResult result = new DatabaseImporterResult();
|
|
||||||
|
|
||||||
try {
|
|
||||||
JSONObject obj;
|
|
||||||
if (_file.isEncrypted() && _creds != null) {
|
|
||||||
obj = _file.getContent(_creds);
|
|
||||||
} else {
|
|
||||||
obj = _file.getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
public boolean isEncrypted() {
|
|
||||||
return _file.isEncrypted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCredentials(DatabaseFileCredentials creds) {
|
|
||||||
_creds = creds;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DatabaseFile getFile() {
|
|
||||||
return _file;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseFile;
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||||
|
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||||
|
import com.beemdevelopment.aegis.encoding.Base64Exception;
|
||||||
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class AegisImporter extends DatabaseImporter {
|
||||||
|
|
||||||
|
public AegisImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppPkgName() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
|
try {
|
||||||
|
byte[] bytes = reader.readAll();
|
||||||
|
DatabaseFile file = DatabaseFile.fromBytes(bytes);
|
||||||
|
if (file.isEncrypted()) {
|
||||||
|
return new EncryptedState(file);
|
||||||
|
}
|
||||||
|
return new DecryptedState(file.getContent());
|
||||||
|
} catch (DatabaseFileException | IOException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EncryptedState extends State {
|
||||||
|
private DatabaseFile _file;
|
||||||
|
|
||||||
|
private EncryptedState(DatabaseFile file) {
|
||||||
|
super(true);
|
||||||
|
_file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlotList getSlots() {
|
||||||
|
return _file.getHeader().getSlots();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State decrypt(DatabaseFileCredentials creds) throws DatabaseFileException {
|
||||||
|
JSONObject obj = _file.getContent(creds);
|
||||||
|
return new DecryptedState(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decrypt(Context context, DecryptListener listener) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecryptedState extends State {
|
||||||
|
private JSONObject _obj;
|
||||||
|
|
||||||
|
private DecryptedState(JSONObject obj) {
|
||||||
|
super(false);
|
||||||
|
_obj = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() throws DatabaseImporterException {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
try {
|
||||||
|
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 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,99 +0,0 @@
|
||||||
package com.beemdevelopment.aegis.importers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
public AndOtpFileImporter(Context context, ByteInputStream stream) {
|
|
||||||
super(context, stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void parse() throws DatabaseImporterException {
|
|
||||||
try {
|
|
||||||
_obj = new JSONArray(new String(_stream.getBytes(), StandardCharsets.UTF_8));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new DatabaseImporterException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DatabaseImporterResult convert() throws DatabaseImporterException {
|
|
||||||
DatabaseImporterResult result = new DatabaseImporterResult();
|
|
||||||
|
|
||||||
for (int i = 0; i < _obj.length(); i++) {
|
|
||||||
try {
|
|
||||||
JSONObject obj = _obj.getJSONObject(i);
|
|
||||||
DatabaseEntry entry = convertEntry(obj);
|
|
||||||
result.addEntry(entry);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new DatabaseImporterException(e);
|
|
||||||
} catch (DatabaseImporterEntryException e) {
|
|
||||||
result.addError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
public boolean isEncrypted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
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 org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class AndOtpImporter extends DatabaseImporter {
|
||||||
|
|
||||||
|
public AndOtpImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppPkgName() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
|
byte[] bytes;
|
||||||
|
try {
|
||||||
|
bytes = reader.readAll();
|
||||||
|
JSONArray array = new JSONArray(new String(bytes, StandardCharsets.UTF_8));
|
||||||
|
return new State(array);
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class State extends DatabaseImporter.State {
|
||||||
|
private JSONArray _obj;
|
||||||
|
|
||||||
|
private State(JSONArray obj) {
|
||||||
|
super(false);
|
||||||
|
_obj = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() throws DatabaseImporterException {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
for (int i = 0; i < _obj.length(); i++) {
|
||||||
|
try {
|
||||||
|
JSONObject obj = _obj.getJSONObject(i);
|
||||||
|
DatabaseEntry entry = convertEntry(obj);
|
||||||
|
result.addEntry(entry);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
} catch (DatabaseImporterEntryException e) {
|
||||||
|
result.addError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,63 +0,0 @@
|
||||||
package com.beemdevelopment.aegis.importers;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.io.SuFile;
|
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public abstract class DatabaseAppImporter implements DatabaseImporter {
|
|
||||||
private static Map<String, Class<? extends DatabaseAppImporter>> _importers;
|
|
||||||
static {
|
|
||||||
// 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, 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 DatabaseImporterResult convert() throws DatabaseImporterException;
|
|
||||||
|
|
||||||
public abstract boolean isEncrypted();
|
|
||||||
|
|
||||||
public Context getContext() {
|
|
||||||
return _context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DatabaseAppImporter create(Context context, Class<? extends DatabaseAppImporter> type) {
|
|
||||||
try {
|
|
||||||
return type.getConstructor(Context.class).newInstance(context);
|
|
||||||
} catch (IllegalAccessException | InstantiationException
|
|
||||||
| NoSuchMethodException | InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Class<? extends DatabaseAppImporter>> getImporters() {
|
|
||||||
return _importers;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
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.Map;
|
|
||||||
|
|
||||||
public abstract class DatabaseFileImporter implements DatabaseImporter {
|
|
||||||
private static Map<String, Class<? extends DatabaseFileImporter>> _importers;
|
|
||||||
static {
|
|
||||||
// note: keep this list sorted alphabetically
|
|
||||||
LinkedHashMap<String, Class<? extends DatabaseFileImporter>> importers = new LinkedHashMap<>();
|
|
||||||
importers.put("Aegis", AegisFileImporter.class);
|
|
||||||
importers.put("andOTP", AndOtpFileImporter.class);
|
|
||||||
importers.put("FreeOTP", FreeOtpFileImporter.class);
|
|
||||||
_importers = Collections.unmodifiableMap(importers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Context _context;
|
|
||||||
protected ByteInputStream _stream;
|
|
||||||
|
|
||||||
protected DatabaseFileImporter(Context context, ByteInputStream stream) {
|
|
||||||
_context = context;
|
|
||||||
_stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void parse() throws DatabaseImporterException;
|
|
||||||
|
|
||||||
public abstract DatabaseImporterResult convert() throws DatabaseImporterException;
|
|
||||||
|
|
||||||
public abstract boolean isEncrypted();
|
|
||||||
|
|
||||||
public Context getContext() {
|
|
||||||
return _context;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DatabaseFileImporter create(Context context, ByteInputStream stream, Class<? extends DatabaseFileImporter> type) {
|
|
||||||
try {
|
|
||||||
return type.getConstructor(Context.class, ByteInputStream.class).newInstance(context, stream);
|
|
||||||
} catch (IllegalAccessException | InstantiationException
|
|
||||||
| NoSuchMethodException | InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Class<? extends DatabaseFileImporter>> getImporters() {
|
|
||||||
return _importers;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,178 @@
|
||||||
package com.beemdevelopment.aegis.importers;
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
public interface DatabaseImporter {
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
void parse() throws DatabaseImporterException;
|
import com.beemdevelopment.aegis.util.ByteInputStream;
|
||||||
DatabaseImporterResult convert() throws DatabaseImporterException;
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
boolean isEncrypted();
|
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||||
Context getContext();
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public abstract class DatabaseImporter {
|
||||||
|
private Context _context;
|
||||||
|
|
||||||
|
private static Map<String, Class<? extends DatabaseImporter>> _importers;
|
||||||
|
private static Map<String, Class<? extends DatabaseImporter>> _appImporters;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// note: keep these lists sorted alphabetically
|
||||||
|
_importers = new LinkedHashMap<>();
|
||||||
|
_importers.put("Aegis", AegisImporter.class);
|
||||||
|
_importers.put("andOTP", AndOtpImporter.class);
|
||||||
|
_importers.put("FreeOTP", FreeOtpImporter.class);
|
||||||
|
_importers.put("Google Authenticator", GoogleAuthImporter.class);
|
||||||
|
_importers.put("Steam", SteamImporter.class);
|
||||||
|
|
||||||
|
_appImporters = new LinkedHashMap<>();
|
||||||
|
_appImporters.put("FreeOTP", FreeOtpImporter.class);
|
||||||
|
_appImporters.put("Google Authenticator", GoogleAuthImporter.class);
|
||||||
|
_appImporters.put("Steam", SteamImporter.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseImporter(Context context) {
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context getContext() {
|
||||||
|
return _context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SuFile getAppPath() throws DatabaseImporterException, PackageManager.NameNotFoundException {
|
||||||
|
return getAppPath(getAppPkgName(), getAppSubPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SuFile getAppPath(String pkgName, String subPath) throws PackageManager.NameNotFoundException {
|
||||||
|
PackageManager man = getContext().getPackageManager();
|
||||||
|
return new SuFile(man.getApplicationInfo(pkgName, 0).dataDir, subPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String getAppPkgName();
|
||||||
|
|
||||||
|
protected abstract String getAppSubPath() throws DatabaseImporterException, PackageManager.NameNotFoundException;
|
||||||
|
|
||||||
|
public abstract State read(FileReader reader) throws DatabaseImporterException;
|
||||||
|
|
||||||
|
public static DatabaseImporter create(Context context, Class<? extends DatabaseImporter> type) {
|
||||||
|
try {
|
||||||
|
return type.getConstructor(Context.class).newInstance(context);
|
||||||
|
} catch (IllegalAccessException | InstantiationException
|
||||||
|
| NoSuchMethodException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
|
||||||
|
return Collections.unmodifiableMap(_importers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Class<? extends DatabaseImporter>> getAppImporters() {
|
||||||
|
return Collections.unmodifiableMap(_appImporters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static abstract class State {
|
||||||
|
private boolean _encrypted;
|
||||||
|
|
||||||
|
public State(boolean encrypted) {
|
||||||
|
_encrypted = encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEncrypted() {
|
||||||
|
return _encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decrypt(Context context, DecryptListener listener) throws DatabaseImporterException {
|
||||||
|
if (!_encrypted) {
|
||||||
|
throw new RuntimeException("Attempted to decrypt a plain text database");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result convert() throws DatabaseImporterException {
|
||||||
|
if (_encrypted) {
|
||||||
|
throw new RuntimeException("Attempted to convert database before decrypting it");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Result {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FileReader implements Closeable {
|
||||||
|
private InputStream _stream;
|
||||||
|
|
||||||
|
private FileReader(InputStream stream) {
|
||||||
|
_stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileReader open(String filename)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
FileInputStream stream = new FileInputStream(filename);
|
||||||
|
return new FileReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileReader open(SuFile file)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
SuFileInputStream stream = new SuFileInputStream(file);
|
||||||
|
return new FileReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileReader open(Context context, Uri uri)
|
||||||
|
throws FileNotFoundException {
|
||||||
|
InputStream stream = context.getContentResolver().openInputStream(uri);
|
||||||
|
return new FileReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readAll() throws IOException {
|
||||||
|
ByteInputStream stream = ByteInputStream.create(_stream);
|
||||||
|
return stream.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getStream() {
|
||||||
|
return _stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
_stream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface DecryptListener {
|
||||||
|
void onStateDecrypted(State state);
|
||||||
|
void onError(Exception e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import com.beemdevelopment.aegis.otp.HotpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.beemdevelopment.aegis.util.ByteInputStream;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -20,81 +19,95 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FreeOtpFileImporter extends DatabaseFileImporter {
|
public class FreeOtpImporter extends DatabaseImporter {
|
||||||
private List<XmlEntry> _xmlEntries;
|
private static final String _subPath = "shared_prefs/tokens.xml";
|
||||||
|
private static final String _pkgName = "org.fedorahosted.freeotp";
|
||||||
|
|
||||||
public FreeOtpFileImporter(Context context, ByteInputStream stream) {
|
public FreeOtpImporter(Context context) {
|
||||||
super(context, stream);
|
super(context);
|
||||||
}
|
|
||||||
|
|
||||||
private static class XmlEntry {
|
|
||||||
String Name;
|
|
||||||
String Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parse() throws DatabaseImporterException {
|
protected String getAppPkgName() {
|
||||||
|
return _pkgName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() {
|
||||||
|
return _subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
try {
|
try {
|
||||||
XmlPullParser parser = Xml.newPullParser();
|
XmlPullParser parser = Xml.newPullParser();
|
||||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
|
||||||
parser.setInput(_stream, null);
|
parser.setInput(reader.getStream(), null);
|
||||||
parser.nextTag();
|
parser.nextTag();
|
||||||
_xmlEntries = parse(parser);
|
|
||||||
|
List<XmlEntry> entries = parse(parser);
|
||||||
|
return new State(entries);
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class State extends DatabaseImporter.State {
|
||||||
public DatabaseImporterResult convert() {
|
private List<XmlEntry> _entries;
|
||||||
DatabaseImporterResult result = new DatabaseImporterResult();
|
|
||||||
|
|
||||||
for (XmlEntry xmlEntry : _xmlEntries) {
|
private State(List<XmlEntry> entries) {
|
||||||
// TODO: order
|
super(false);
|
||||||
if (!xmlEntry.Name.equals("tokenOrder")) {
|
_entries = entries;
|
||||||
try {
|
}
|
||||||
DatabaseEntry entry = convertEntry(xmlEntry);
|
|
||||||
result.addEntry(entry);
|
@Override
|
||||||
} catch (DatabaseImporterEntryException e) {
|
public Result convert() {
|
||||||
result.addError(e);
|
Result result = new Result();
|
||||||
|
|
||||||
|
for (XmlEntry xmlEntry : _entries) {
|
||||||
|
// TODO: order
|
||||||
|
if (!xmlEntry.Name.equals("tokenOrder")) {
|
||||||
|
try {
|
||||||
|
DatabaseEntry entry = convertEntry(xmlEntry);
|
||||||
|
result.addEntry(entry);
|
||||||
|
} catch (DatabaseImporterEntryException e) {
|
||||||
|
result.addError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
private static DatabaseEntry convertEntry(XmlEntry entry) throws DatabaseImporterEntryException {
|
||||||
}
|
try {
|
||||||
|
JSONObject obj = new JSONObject(entry.Value);
|
||||||
|
|
||||||
private static DatabaseEntry convertEntry(XmlEntry xmlEntry) throws DatabaseImporterEntryException {
|
String type = obj.getString("type").toLowerCase();
|
||||||
try {
|
String algo = obj.getString("algo");
|
||||||
JSONObject obj = new JSONObject(xmlEntry.Value);
|
int digits = obj.getInt("digits");
|
||||||
|
byte[] secret = toBytes(obj.getJSONArray("secret"));
|
||||||
|
|
||||||
String type = obj.getString("type").toLowerCase();
|
OtpInfo info;
|
||||||
String algo = obj.getString("algo");
|
switch (type) {
|
||||||
int digits = obj.getInt("digits");
|
case "totp":
|
||||||
byte[] secret = toBytes(obj.getJSONArray("secret"));
|
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
|
||||||
|
break;
|
||||||
|
case "hotp":
|
||||||
|
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new DatabaseImporterException("unsupported otp type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
OtpInfo info;
|
String issuer = obj.getString("issuerExt");
|
||||||
if (type.equals("totp")) {
|
String name = obj.optString("label");
|
||||||
info = new TotpInfo(secret, algo, digits, obj.getInt("period"));
|
return new DatabaseEntry(info, name, issuer);
|
||||||
} else if (type.equals("hotp")) {
|
} catch (DatabaseImporterException | OtpInfoException | JSONException e) {
|
||||||
info = new HotpInfo(secret, algo, digits, obj.getLong("counter"));
|
throw new DatabaseImporterEntryException(e, entry.Value);
|
||||||
} 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
|
|
||||||
public boolean isEncrypted() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<XmlEntry> parse(XmlPullParser parser)
|
private static List<XmlEntry> parse(XmlPullParser parser)
|
||||||
throws IOException, XmlPullParserException {
|
throws IOException, XmlPullParserException {
|
||||||
List<XmlEntry> entries = new ArrayList<>();
|
List<XmlEntry> entries = new ArrayList<>();
|
||||||
|
@ -163,4 +176,9 @@ public class FreeOtpFileImporter extends DatabaseFileImporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class XmlEntry {
|
||||||
|
String Name;
|
||||||
|
String Value;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,6 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
import com.beemdevelopment.aegis.otp.TotpInfo;
|
import com.beemdevelopment.aegis.otp.TotpInfo;
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
import com.topjohnwu.superuser.ShellUtils;
|
||||||
import com.topjohnwu.superuser.io.SuFileInputStream;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -23,30 +22,35 @@ import java.util.List;
|
||||||
|
|
||||||
import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY;
|
import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY;
|
||||||
|
|
||||||
public class GoogleAuthAppImporter extends DatabaseAppImporter {
|
public class GoogleAuthImporter extends DatabaseImporter {
|
||||||
private static final int TYPE_TOTP = 0;
|
private static final int TYPE_TOTP = 0;
|
||||||
private static final int TYPE_HOTP = 1;
|
private static final int TYPE_HOTP = 1;
|
||||||
|
|
||||||
private static final String _subPath = "databases/databases";
|
private static final String _subPath = "databases/databases";
|
||||||
private static final String _pkgName = "com.google.android.apps.authenticator2";
|
private static final String _pkgName = "com.google.android.apps.authenticator2";
|
||||||
|
|
||||||
private List<Entry> _entries = new ArrayList<>();
|
public GoogleAuthImporter(Context context) {
|
||||||
|
super(context);
|
||||||
public GoogleAuthAppImporter(Context context) throws DatabaseImporterException {
|
|
||||||
super(context, _pkgName, _subPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void parse() throws DatabaseImporterException {
|
protected String getAppPkgName() {
|
||||||
|
return _pkgName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() {
|
||||||
|
return _subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
File file;
|
File file;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// create a temporary copy of the database so that SQLiteDatabase can open it
|
// create a temporary copy of the database so that SQLiteDatabase can open it
|
||||||
file = File.createTempFile("google-import-", "", getContext().getCacheDir());
|
file = File.createTempFile("google-import-", "", getContext().getCacheDir());
|
||||||
try (SuFileInputStream in = new SuFileInputStream(getPath())) {
|
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||||
try (FileOutputStream out = new FileOutputStream(file)) {
|
ShellUtils.pump(reader.getStream(), out);
|
||||||
ShellUtils.pump(in, out);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
|
@ -54,14 +58,16 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
|
||||||
|
|
||||||
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) {
|
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) {
|
||||||
try (Cursor cursor = db.rawQuery("SELECT * FROM accounts", null)) {
|
try (Cursor cursor = db.rawQuery("SELECT * FROM accounts", null)) {
|
||||||
if (!cursor.moveToFirst()) {
|
List<Entry> entries = new ArrayList<>();
|
||||||
return;
|
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
do {
|
||||||
|
Entry entry = new Entry(cursor);
|
||||||
|
entries.add(entry);
|
||||||
|
} while(cursor.moveToNext());
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
return new State(entries);
|
||||||
Entry entry = new Entry(cursor);
|
|
||||||
_entries.add(entry);
|
|
||||||
} while(cursor.moveToNext());
|
|
||||||
}
|
}
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
|
@ -71,53 +77,57 @@ public class GoogleAuthAppImporter extends DatabaseAppImporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class State extends DatabaseImporter.State {
|
||||||
public DatabaseImporterResult convert() {
|
private List<Entry> _entries;
|
||||||
DatabaseImporterResult result = new DatabaseImporterResult();
|
|
||||||
|
|
||||||
for (Entry sqlEntry : _entries) {
|
private State(List<Entry> entries) {
|
||||||
|
super(false);
|
||||||
|
_entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
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 {
|
try {
|
||||||
DatabaseEntry entry = convertEntry(sqlEntry);
|
byte[] secret = Base32.decode(entry.getSecret().toCharArray());
|
||||||
result.addEntry(entry);
|
|
||||||
} catch (DatabaseImporterEntryException e) {
|
OtpInfo info;
|
||||||
result.addError(e);
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
public boolean isEncrypted() {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getString(Cursor cursor, String columnName) {
|
private static String getString(Cursor cursor, String columnName) {
|
|
@ -1,83 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
|
||||||
|
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 org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class SteamImporter extends DatabaseImporter {
|
||||||
|
private static final String _subDir = "files";
|
||||||
|
private static final String _pkgName = "com.valvesoftware.android.steam.community";
|
||||||
|
|
||||||
|
public SteamImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppPkgName() {
|
||||||
|
return _pkgName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() throws DatabaseImporterException, PackageManager.NameNotFoundException {
|
||||||
|
// NOTE: this assumes that a global root shell has already been obtained by the caller
|
||||||
|
SuFile path = getAppPath(getAppPkgName(), _subDir);
|
||||||
|
SuFile[] files = path.listFiles((d, name) -> name.startsWith("Steamguard-"));
|
||||||
|
if (files == null || files.length == 0) {
|
||||||
|
throw new DatabaseImporterException(String.format("Empty directory: %s", path.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle multiple files (can this even occur?)
|
||||||
|
return new SuFile(_subDir, files[0].getName()).getPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
|
try (ByteInputStream stream = ByteInputStream.create(reader.getStream())) {
|
||||||
|
JSONObject obj = new JSONObject(new String(stream.getBytes(), StandardCharsets.UTF_8));
|
||||||
|
return new State(obj);
|
||||||
|
} catch (IOException | JSONException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class State extends DatabaseImporter.State {
|
||||||
|
private JSONObject _obj;
|
||||||
|
|
||||||
|
private State(JSONObject obj) {
|
||||||
|
super(false);
|
||||||
|
_obj = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DatabaseEntry entry = convertEntry(_obj);
|
||||||
|
result.addEntry(entry);
|
||||||
|
} catch (DatabaseImporterEntryException e) {
|
||||||
|
result.addError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -20,6 +21,7 @@ import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||||
|
@ -29,25 +31,22 @@ import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||||
import com.beemdevelopment.aegis.importers.AegisFileImporter;
|
import com.beemdevelopment.aegis.importers.AegisImporter;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseAppImporter;
|
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseFileImporter;
|
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
|
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporterResult;
|
|
||||||
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
import com.beemdevelopment.aegis.ui.preferences.SwitchPreference;
|
||||||
import com.beemdevelopment.aegis.util.ByteInputStream;
|
|
||||||
import com.takisoft.preferencex.PreferenceFragmentCompat;
|
import com.takisoft.preferencex.PreferenceFragmentCompat;
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -70,10 +69,9 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
private Intent _result;
|
private Intent _result;
|
||||||
private DatabaseManager _db;
|
private DatabaseManager _db;
|
||||||
|
|
||||||
// this is used to keep a reference to a database converter
|
// keep a reference to the type of database converter the user selected
|
||||||
// while the user provides credentials to decrypt it
|
private Class<? extends DatabaseImporter> _importerType;
|
||||||
private DatabaseFileImporter _importer;
|
private AegisImporter.State _importerState;
|
||||||
private Class<? extends DatabaseFileImporter> _importerType;
|
|
||||||
|
|
||||||
private SwitchPreference _encryptionPreference;
|
private SwitchPreference _encryptionPreference;
|
||||||
private SwitchPreference _fingerprintPreference;
|
private SwitchPreference _fingerprintPreference;
|
||||||
|
@ -377,6 +375,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
break;
|
break;
|
||||||
case CODE_GROUPS:
|
case CODE_GROUPS:
|
||||||
onGroupManagerResult(resultCode, data);
|
onGroupManagerResult(resultCode, data);
|
||||||
|
break;
|
||||||
case CODE_SELECT_ENTRIES:
|
case CODE_SELECT_ENTRIES:
|
||||||
onSelectEntriesResult(resultCode, data);
|
onSelectEntriesResult(resultCode, data);
|
||||||
break;
|
break;
|
||||||
|
@ -397,8 +396,8 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Class<? extends DatabaseFileImporter>> importers = DatabaseFileImporter.getImporters();
|
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getImporters();
|
||||||
String[] names = importers.keySet().toArray(new String[importers.size()]);
|
String[] names = importers.keySet().toArray(new String[0]);
|
||||||
|
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(getString(R.string.choose_application))
|
.setTitle(getString(R.string.choose_application))
|
||||||
|
@ -417,61 +416,95 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onImportApp() {
|
private void onImportApp() {
|
||||||
Map<String, Class<? extends DatabaseAppImporter>> importers = DatabaseAppImporter.getImporters();
|
Map<String, Class<? extends DatabaseImporter>> importers = DatabaseImporter.getAppImporters();
|
||||||
String[] names = importers.keySet().toArray(new String[importers.size()]);
|
String[] names = importers.keySet().toArray(new String[0]);
|
||||||
|
|
||||||
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
Dialogs.showSecureDialog(new AlertDialog.Builder(getActivity())
|
||||||
.setTitle(getString(R.string.choose_application))
|
.setTitle(getString(R.string.choose_application))
|
||||||
.setSingleChoiceItems(names, 0, null)
|
.setSingleChoiceItems(names, 0, null)
|
||||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
||||||
int i = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
|
Class<? extends DatabaseImporter> importerType = Objects.requireNonNull(importers.get(names[i]));
|
||||||
try {
|
DatabaseImporter importer = DatabaseImporter.create(getContext(), importerType);
|
||||||
DatabaseAppImporter importer;
|
importApp(importer);
|
||||||
// obtain the global root shell and close it immediately after we're done
|
|
||||||
// TODO: find a way to use SuFileInputStream with Shell.newInstance()
|
|
||||||
try (Shell shell = Shell.getShell()) {
|
|
||||||
if (!shell.isRoot()) {
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.root_error), Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
importer = DatabaseAppImporter.create(getContext(), importers.get(names[i]));
|
|
||||||
importer.parse();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.root_error), Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
importDatabase(importer);
|
|
||||||
} catch (DatabaseImporterException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage());
|
|
||||||
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.create());
|
.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void importApp(DatabaseImporter importer) {
|
||||||
|
// obtain the global root shell and close it immediately after we're done
|
||||||
|
// TODO: find a way to use SuFileInputStream with Shell.newInstance()
|
||||||
|
try (Shell shell = Shell.getShell()) {
|
||||||
|
if (!shell.isRoot()) {
|
||||||
|
Toast.makeText(getActivity(), R.string.root_error, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SuFile file = importer.getAppPath();
|
||||||
|
try (DatabaseImporter.FileReader reader = DatabaseImporter.FileReader.open(file)) {
|
||||||
|
importDatabase(importer, reader);
|
||||||
|
}
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Toast.makeText(getActivity(), R.string.app_lookup_error, Toast.LENGTH_SHORT).show();
|
||||||
|
} catch (IOException | DatabaseImporterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importDatabase(DatabaseImporter importer, DatabaseImporter.FileReader reader) {
|
||||||
|
try {
|
||||||
|
DatabaseImporter.State state = importer.read(reader);
|
||||||
|
if (state.isEncrypted()) {
|
||||||
|
// temporary special case for encrypted Aegis databases
|
||||||
|
if (state instanceof AegisImporter.EncryptedState) {
|
||||||
|
_importerState = state;
|
||||||
|
|
||||||
|
Intent intent = new Intent(getActivity(), AuthActivity.class);
|
||||||
|
intent.putExtra("slots", ((AegisImporter.EncryptedState) state).getSlots());
|
||||||
|
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
|
||||||
|
} else {
|
||||||
|
state.decrypt(getActivity(), new DatabaseImporter.DecryptListener() {
|
||||||
|
@Override
|
||||||
|
public void onStateDecrypted(DatabaseImporter.State state) {
|
||||||
|
importDatabase(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
Toast.makeText(getActivity(), R.string.decryption_error, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
importDatabase(state);
|
||||||
|
}
|
||||||
|
} catch (DatabaseImporterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage());
|
||||||
|
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onImportDecryptResult(int resultCode, Intent data) {
|
private void onImportDecryptResult(int resultCode, Intent data) {
|
||||||
if (resultCode != Activity.RESULT_OK) {
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
_importer = null;
|
_importerState = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
DatabaseFileCredentials creds = (DatabaseFileCredentials) data.getSerializableExtra("creds");
|
||||||
((AegisFileImporter)_importer).setCredentials(creds);
|
DatabaseImporter.State state;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
importDatabase(_importer);
|
state = ((AegisImporter.EncryptedState) _importerState).decrypt(creds);
|
||||||
} catch (DatabaseImporterException e) {
|
} catch (DatabaseFileException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
Toast.makeText(getActivity(), R.string.decryption_error, Toast.LENGTH_SHORT).show();
|
||||||
String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage());
|
return;
|
||||||
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_importer = null;
|
importDatabase(state);
|
||||||
|
_importerState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onImportResult(int resultCode, Intent data) {
|
private void onImportResult(int resultCode, Intent data) {
|
||||||
|
@ -480,44 +513,28 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteInputStream stream;
|
try (DatabaseImporter.FileReader reader = DatabaseImporter.FileReader.open(getContext(), uri)) {
|
||||||
|
DatabaseImporter importer = DatabaseImporter.create(getContext(), _importerType);
|
||||||
try (InputStream fileStream = getActivity().getContentResolver().openInputStream(uri)) {
|
importDatabase(importer, reader);
|
||||||
stream = ByteInputStream.create(fileStream);
|
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Toast.makeText(getActivity(), getString(R.string.file_not_found), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), getString(R.string.file_not_found), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), getString(R.string.reading_file_error), Toast.LENGTH_SHORT).show();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
DatabaseFileImporter importer = DatabaseFileImporter.create(getContext(), stream, _importerType);
|
|
||||||
importer.parse();
|
|
||||||
|
|
||||||
// special case to decrypt encrypted aegis databases
|
|
||||||
if (importer.isEncrypted() && importer instanceof AegisFileImporter) {
|
|
||||||
_importer = importer;
|
|
||||||
|
|
||||||
Intent intent = new Intent(getActivity(), AuthActivity.class);
|
|
||||||
intent.putExtra("slots", ((AegisFileImporter)_importer).getFile().getHeader().getSlots());
|
|
||||||
startActivityForResult(intent, CODE_IMPORT_DECRYPT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
importDatabase(importer);
|
|
||||||
} catch (DatabaseImporterException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
|
|
||||||
String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage());
|
|
||||||
Toast.makeText(getActivity(), msg, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importDatabase(DatabaseImporter importer) throws DatabaseImporterException {
|
private void importDatabase(DatabaseImporter.State state) {
|
||||||
DatabaseImporterResult result = importer.convert();
|
DatabaseImporter.Result result;
|
||||||
|
try {
|
||||||
|
result = state.convert();
|
||||||
|
} catch (DatabaseImporterException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
String msg = String.format("%s: %s", getString(R.string.parsing_file_error), e.getMessage());
|
||||||
|
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
List<DatabaseEntry> entries = result.getEntries();
|
List<DatabaseEntry> entries = result.getEntries();
|
||||||
List<DatabaseImporterEntryException> errors = result.getErrors();
|
List<DatabaseImporterEntryException> errors = result.getErrors();
|
||||||
|
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
<string name="parsing_file_error">An error occurred while trying to parse the file</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="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="reading_file_error">An error occurred while trying to read the file</string>
|
||||||
|
<string name="app_lookup_error">Error: App is not installed</string>
|
||||||
<string name="root_error">Error: unable to obtain root access</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</string>
|
||||||
<string name="read_entries_count">Read %d entries. %d errors.</string>
|
<string name="read_entries_count">Read %d entries. %d errors.</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue