mirror of https://github.com/beemdevelopment/Aegis
parent
b33c9383fe
commit
ad1d3f04a6
@ -0,0 +1,50 @@
|
|||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
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 = Collections.unmodifiableMap(importers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Context _context;
|
||||||
|
|
||||||
|
protected DatabaseAppImporter(Context context) {
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void parse() throws DatabaseImporterException;
|
||||||
|
|
||||||
|
public abstract List<DatabaseEntry> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
|
import com.beemdevelopment.aegis.util.ByteInputStream;
|
||||||
|
|
||||||
|
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 List<DatabaseEntry> 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,47 +1,14 @@
|
|||||||
package com.beemdevelopment.aegis.importers;
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import android.content.Context;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
import com.beemdevelopment.aegis.db.DatabaseEntry;
|
||||||
import com.beemdevelopment.aegis.util.ByteInputStream;
|
|
||||||
|
|
||||||
public abstract class DatabaseImporter {
|
|
||||||
private static Map<String, Class<? extends DatabaseImporter>> _importers;
|
|
||||||
static {
|
|
||||||
// note: keep this list sorted alphabetically
|
|
||||||
LinkedHashMap<String, Class<? extends DatabaseImporter>> importers = new LinkedHashMap<>();
|
|
||||||
importers.put("Aegis", AegisImporter.class);
|
|
||||||
importers.put("andOTP", AndOtpImporter.class);
|
|
||||||
importers.put("FreeOTP", FreeOtpImporter.class);
|
|
||||||
_importers = Collections.unmodifiableMap(importers);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ByteInputStream _stream;
|
|
||||||
|
|
||||||
protected DatabaseImporter(ByteInputStream stream) {
|
|
||||||
_stream = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void parse() throws DatabaseImporterException;
|
import java.util.List;
|
||||||
|
|
||||||
public abstract List<DatabaseEntry> convert() throws DatabaseImporterException;
|
|
||||||
|
|
||||||
public abstract boolean isEncrypted();
|
|
||||||
|
|
||||||
public static DatabaseImporter create(ByteInputStream stream, Class<? extends DatabaseImporter> type) {
|
|
||||||
try {
|
|
||||||
return type.getConstructor(ByteInputStream.class).newInstance(stream);
|
|
||||||
} catch (IllegalAccessException | InstantiationException
|
|
||||||
| NoSuchMethodException | InvocationTargetException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, Class<? extends DatabaseImporter>> getImporters() {
|
public interface DatabaseImporter {
|
||||||
return _importers;
|
void parse() throws DatabaseImporterException;
|
||||||
}
|
List<DatabaseEntry> convert() throws DatabaseImporterException;
|
||||||
|
boolean isEncrypted();
|
||||||
|
Context getContext();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteException;
|
||||||
|
|
||||||
|
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.TotpInfo;
|
||||||
|
import com.topjohnwu.superuser.ShellUtils;
|
||||||
|
import com.topjohnwu.superuser.io.SuFile;
|
||||||
|
import com.topjohnwu.superuser.io.SuFileInputStream;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY;
|
||||||
|
|
||||||
|
public class GoogleAuthAppImporter extends DatabaseAppImporter {
|
||||||
|
private static final int TYPE_TOTP = 0;
|
||||||
|
private static final int TYPE_HOTP = 1;
|
||||||
|
|
||||||
|
@SuppressLint("SdCardPath")
|
||||||
|
private static final String _filename = "/data/data/com.google.android.apps.authenticator2/databases/databases";
|
||||||
|
|
||||||
|
private List<DatabaseEntry> _entries = new ArrayList<>();
|
||||||
|
|
||||||
|
public GoogleAuthAppImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void parse() throws DatabaseImporterException {
|
||||||
|
File file;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// create a temporary copy of the database so that SQLiteDatabase can open it
|
||||||
|
file = File.createTempFile("google-import-", "", getContext().getCacheDir());
|
||||||
|
try (SuFileInputStream in = new SuFileInputStream(new SuFile(_filename))) {
|
||||||
|
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||||
|
ShellUtils.pump(in, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) {
|
||||||
|
try (Cursor cursor = db.rawQuery("SELECT * FROM accounts", null)) {
|
||||||
|
if (!cursor.moveToFirst()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
int type = getInt(cursor, "type");
|
||||||
|
byte[] secret = Base32.decode(getString(cursor, "secret").toCharArray());
|
||||||
|
|
||||||
|
OtpInfo info;
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_TOTP:
|
||||||
|
info = new TotpInfo(secret);
|
||||||
|
break;
|
||||||
|
case TYPE_HOTP:
|
||||||
|
info = new HotpInfo(secret, getInt(cursor, "counter"));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new DatabaseImporterException("unsupported otp type: " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = getString(cursor, "email", "");
|
||||||
|
String issuer = getString(cursor, "issuer", "");
|
||||||
|
|
||||||
|
String[] parts = name.split(":");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
name = parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseEntry entry = new DatabaseEntry(info, name, issuer);
|
||||||
|
_entries.add(entry);
|
||||||
|
} while(cursor.moveToNext());
|
||||||
|
}
|
||||||
|
} catch (SQLiteException | OtpInfoException | Base32Exception e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
} finally {
|
||||||
|
// always delete the temporary file
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DatabaseEntry> convert() {
|
||||||
|
return _entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEncrypted() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(Cursor cursor, String columnName) {
|
||||||
|
return getString(cursor, columnName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getString(Cursor cursor, String columnName, String def) {
|
||||||
|
String res = cursor.getString(cursor.getColumnIndex(columnName));
|
||||||
|
if (res == null) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getInt(Cursor cursor, String columnName) {
|
||||||
|
return cursor.getInt(cursor.getColumnIndex(columnName));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package me.impy.aegis.db;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class DatabaseEntryGroup implements Serializable {
|
||||||
|
private UUID _uuid;
|
||||||
|
private String _name;
|
||||||
|
|
||||||
|
private DatabaseEntryGroup(UUID uuid, String name) {
|
||||||
|
_uuid = uuid;
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DatabaseEntryGroup(String name) {
|
||||||
|
this(UUID.randomUUID(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JSONObject toJson() {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
|
try {
|
||||||
|
obj.put("uuid", _uuid.toString());
|
||||||
|
obj.put("name", _name);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DatabaseEntryGroup fromJson(JSONObject obj) throws JSONException {
|
||||||
|
// if there is no uuid, generate a new one
|
||||||
|
UUID uuid;
|
||||||
|
if (!obj.has("uuid")) {
|
||||||
|
uuid = UUID.randomUUID();
|
||||||
|
} else {
|
||||||
|
uuid = UUID.fromString(obj.getString("uuid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DatabaseEntryGroup(uuid, obj.getString("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUUID() {
|
||||||
|
return _uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
_name = name;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue