mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-15 22:42:51 +00:00
Added Database Import from Authenticator Plus
Added copyright notice Rearrange item position in import dialog Refactor SqlImportHelper to support SQLCipher Improved stream handling
This commit is contained in:
parent
3d51b84188
commit
dc0f7c4668
5 changed files with 200 additions and 4 deletions
|
@ -90,6 +90,7 @@ dependencies {
|
||||||
implementation("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
implementation("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
|
||||||
transitive = false
|
transitive = false
|
||||||
}
|
}
|
||||||
|
implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
|
||||||
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
annotationProcessor 'androidx.annotation:annotation:1.1.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
|
||||||
|
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.ui.Dialogs;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AuthenticatorPlusImporter extends DatabaseImporter {
|
||||||
|
private static final String _pkgName = "com.mufri.authenticatorplus";
|
||||||
|
|
||||||
|
public AuthenticatorPlusImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppPkgName() {
|
||||||
|
return _pkgName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getAppSubPath() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public State read(FileReader reader) throws DatabaseImporterException {
|
||||||
|
try {
|
||||||
|
return new EncryptedState(reader.readAll());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EncryptedState extends DatabaseImporter.State {
|
||||||
|
private byte[] _data;
|
||||||
|
|
||||||
|
private EncryptedState(byte[] data) {
|
||||||
|
super(true);
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decrypt(Context context, DecryptListener listener) {
|
||||||
|
Dialogs.showPasswordInputDialog(context, password -> {
|
||||||
|
try {
|
||||||
|
// recreate InputStream from saved byte array
|
||||||
|
InputStream stream = new ByteArrayInputStream(_data);
|
||||||
|
|
||||||
|
SqlImporterHelper helper = new SqlImporterHelper(context, password, true);
|
||||||
|
List<Entry> entries = helper.read(Entry.class, stream, "accounts");
|
||||||
|
|
||||||
|
DecryptedState state = new DecryptedState(entries);
|
||||||
|
listener.onStateDecrypted(state);
|
||||||
|
} catch (DatabaseImporterException e) {
|
||||||
|
listener.onError(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DecryptedState extends DatabaseImporter.State {
|
||||||
|
private List<Entry> _entries;
|
||||||
|
|
||||||
|
private DecryptedState(List<Entry> entries) {
|
||||||
|
super(false);
|
||||||
|
_entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VaultEntry convertEntry(Entry entry) throws DatabaseImporterEntryException {
|
||||||
|
try {
|
||||||
|
String secretString = entry.getSecret().replaceAll("\\s", "");
|
||||||
|
byte[] secret = Base32.decode(secretString);
|
||||||
|
|
||||||
|
OtpInfo info;
|
||||||
|
switch (entry.getType()) {
|
||||||
|
case 0:
|
||||||
|
info = new TotpInfo(secret);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
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 VaultEntry(info, name, entry.getIssuer());
|
||||||
|
} catch (EncodingException | OtpInfoException | DatabaseImporterException e) {
|
||||||
|
throw new DatabaseImporterEntryException(e, entry.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
for (Entry sqlEntry : _entries) {
|
||||||
|
try {
|
||||||
|
VaultEntry entry = convertEntry(sqlEntry);
|
||||||
|
result.addEntry(entry);
|
||||||
|
} catch (DatabaseImporterEntryException e) {
|
||||||
|
result.addError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Entry extends SqlImporterHelper.Entry {
|
||||||
|
private int _type;
|
||||||
|
private String _secret;
|
||||||
|
private String _email;
|
||||||
|
private String _issuer;
|
||||||
|
private long _counter;
|
||||||
|
|
||||||
|
public Entry(Cursor cursor) {
|
||||||
|
super(cursor);
|
||||||
|
_type = SqlImporterHelper.getInt(cursor, "type");
|
||||||
|
_secret = SqlImporterHelper.getString(cursor, "secret");
|
||||||
|
_email = SqlImporterHelper.getString(cursor, "email", "");
|
||||||
|
_issuer = SqlImporterHelper.getString(cursor, "issuer", "");
|
||||||
|
_counter = SqlImporterHelper.getLong(cursor, "counter");
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return _secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return _email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIssuer() {
|
||||||
|
return _issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCounter() {
|
||||||
|
return _counter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ public abstract class DatabaseImporter {
|
||||||
// note: keep these lists sorted alphabetically
|
// note: keep these lists sorted alphabetically
|
||||||
_importers = new LinkedHashMap<>();
|
_importers = new LinkedHashMap<>();
|
||||||
_importers.put("Aegis", AegisImporter.class);
|
_importers.put("Aegis", AegisImporter.class);
|
||||||
|
_importers.put("Authenticator Plus", AuthenticatorPlusImporter.class);
|
||||||
_importers.put("Authy", AuthyImporter.class);
|
_importers.put("Authy", AuthyImporter.class);
|
||||||
_importers.put("andOTP", AndOtpImporter.class);
|
_importers.put("andOTP", AndOtpImporter.class);
|
||||||
_importers.put("FreeOTP", FreeOtpImporter.class);
|
_importers.put("FreeOTP", FreeOtpImporter.class);
|
||||||
|
|
|
@ -2,11 +2,13 @@ package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteException;
|
|
||||||
|
|
||||||
import com.topjohnwu.superuser.ShellUtils;
|
import com.topjohnwu.superuser.ShellUtils;
|
||||||
|
|
||||||
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||||
|
import net.sqlcipher.database.SQLiteException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -19,9 +21,18 @@ import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY;
|
||||||
|
|
||||||
public class SqlImporterHelper {
|
public class SqlImporterHelper {
|
||||||
private Context _context;
|
private Context _context;
|
||||||
|
private char[] _password;
|
||||||
|
private boolean _compatibilityMode;
|
||||||
|
|
||||||
public SqlImporterHelper(Context context) {
|
public SqlImporterHelper(Context context) {
|
||||||
_context = context;
|
_context = context;
|
||||||
|
_password = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SqlImporterHelper(Context context, char[] password, boolean compatibilityMode) {
|
||||||
|
_context = context;
|
||||||
|
_password = password;
|
||||||
|
_compatibilityMode = compatibilityMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T extends Entry> List<T> read(Class<T> type, InputStream inStream, String table) throws DatabaseImporterException {
|
public <T extends Entry> List<T> read(Class<T> type, InputStream inStream, String table) throws DatabaseImporterException {
|
||||||
|
@ -37,7 +48,22 @@ public class SqlImporterHelper {
|
||||||
throw new DatabaseImporterException(e);
|
throw new DatabaseImporterException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) {
|
SQLiteDatabase.loadLibs(_context);
|
||||||
|
SQLiteDatabaseHook compatibilityHook = new SQLiteDatabaseHook() {
|
||||||
|
@Override
|
||||||
|
public void preKey(SQLiteDatabase database) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postKey(SQLiteDatabase database) {
|
||||||
|
if (_compatibilityMode) {
|
||||||
|
database.compileStatement("PRAGMA cipher_compatibility = 3;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), _password, null, OPEN_READONLY, compatibilityHook)) {
|
||||||
try (Cursor cursor = db.rawQuery(String.format("SELECT * FROM %s", table), null)) {
|
try (Cursor cursor = db.rawQuery(String.format("SELECT * FROM %s", table), null)) {
|
||||||
List<T> entries = new ArrayList<>();
|
List<T> entries = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -82,7 +108,7 @@ public class SqlImporterHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static abstract class Entry {
|
public static abstract class Entry {
|
||||||
public Entry (Cursor cursor) {
|
public Entry(Cursor cursor) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,4 +70,9 @@
|
||||||
<copyright>Copyright (C) 2016 The Android Open Source Project</copyright>
|
<copyright>Copyright (C) 2016 The Android Open Source Project</copyright>
|
||||||
<license>Apache Software License 2.0</license>
|
<license>Apache Software License 2.0</license>
|
||||||
</notice>
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>SQLCipher for Android</name>
|
||||||
|
<url>https://github.com/sqlcipher/android-database-sqlcipher</url>
|
||||||
|
<license>Apache Software License 2.0</license>
|
||||||
|
</notice>
|
||||||
</notices>
|
</notices>
|
Loading…
Add table
Add a link
Reference in a new issue