mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-14 14:02:49 +00:00
Add Bitwarden importer
Added license notice for Simple Flat Mapper Added Steam secret support to Bitwarden importer https://bitwarden.com/help/authenticator-keys/#steam-guard-totps Added tests
This commit is contained in:
parent
beadf4db8b
commit
8d4a687817
8 changed files with 227 additions and 0 deletions
|
@ -172,6 +172,7 @@ dependencies {
|
||||||
implementation 'net.lingala.zip4j:zip4j:2.10.0'
|
implementation 'net.lingala.zip4j:zip4j:2.10.0'
|
||||||
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
implementation 'info.guardianproject.trustedintents:trustedintents:0.2'
|
||||||
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
|
||||||
|
implementation "org.simpleflatmapper:sfm-csv:8.2.3"
|
||||||
|
|
||||||
androidTestAnnotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion"
|
androidTestAnnotationProcessor "com.google.dagger:hilt-android-compiler:$hiltVersion"
|
||||||
androidTestImplementation "com.google.dagger:hilt-android-testing:$hiltVersion"
|
androidTestImplementation "com.google.dagger:hilt-android-testing:$hiltVersion"
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.encoding.Base32;
|
||||||
|
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||||
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||||
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
|
import com.beemdevelopment.aegis.otp.SteamInfo;
|
||||||
|
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 org.simpleflatmapper.csv.CsvParser;
|
||||||
|
import org.simpleflatmapper.lightningcsv.Row;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BitwardenImporter extends DatabaseImporter {
|
||||||
|
public BitwardenImporter(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SuFile getAppPath() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected State read(InputStream stream, boolean isInternal) throws DatabaseImporterException {
|
||||||
|
String fileString;
|
||||||
|
try {
|
||||||
|
fileString = new String(IOUtils.readAll(stream), StandardCharsets.UTF_8);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DatabaseImporterException(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONObject obj = new JSONObject(fileString);
|
||||||
|
JSONArray array = obj.getJSONArray("items");
|
||||||
|
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
String entry;
|
||||||
|
for (int i = 0; i < array.length(); i++) {
|
||||||
|
entry = array.getJSONObject(i).getJSONObject("login").getString("totp");
|
||||||
|
if (!entry.isEmpty()) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BitwardenImporter.State(entries);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
try {
|
||||||
|
Iterator<Row> rowIterator = CsvParser.separator(',').rowIterator(fileString);
|
||||||
|
List<String> entries = new ArrayList<>();
|
||||||
|
rowIterator.forEachRemaining((row -> {
|
||||||
|
String entry = row.get("login_totp");
|
||||||
|
if (entry != null && !entry.isEmpty()) {
|
||||||
|
entries.add(entry);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return new BitwardenImporter.State(entries);
|
||||||
|
} catch (IOException e2) {
|
||||||
|
throw new DatabaseImporterException(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class State extends DatabaseImporter.State {
|
||||||
|
private final List<String> _entries;
|
||||||
|
|
||||||
|
public State(List<String> entries) {
|
||||||
|
super(false);
|
||||||
|
_entries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result convert() {
|
||||||
|
Result result = new Result();
|
||||||
|
|
||||||
|
for (String obj : _entries) {
|
||||||
|
try {
|
||||||
|
VaultEntry entry = convertEntry(obj);
|
||||||
|
result.addEntry(entry);
|
||||||
|
} catch (DatabaseImporterEntryException e) {
|
||||||
|
result.addError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VaultEntry convertEntry(String obj) throws DatabaseImporterEntryException {
|
||||||
|
try {
|
||||||
|
GoogleAuthInfo info = BitwardenImporter.parseUri(obj);
|
||||||
|
return new VaultEntry(info);
|
||||||
|
} catch (GoogleAuthInfoException | EncodingException | OtpInfoException | URISyntaxException e) {
|
||||||
|
throw new DatabaseImporterEntryException(e, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GoogleAuthInfo parseUri(String obj) throws EncodingException, OtpInfoException, URISyntaxException, GoogleAuthInfoException {
|
||||||
|
Uri uri = Uri.parse(obj);
|
||||||
|
return uri.getScheme().equals("steam") ? new GoogleAuthInfo(new SteamInfo(Base32.decode(uri.getAuthority())), "Steam account", "Steam") : GoogleAuthInfo.parseUri(uri);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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("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("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("Authy", AuthyImporter.class, R.string.importer_help_authy, true));
|
||||||
|
_importers.add(new Definition("Bitwarden", BitwardenImporter.class, R.string.importer_help_bitwarden, false));
|
||||||
_importers.add(new Definition("Duo", DuoImporter.class, R.string.importer_help_duo, 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", 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("FreeOTP+", FreeOtpPlusImporter.class, R.string.importer_help_freeotp_plus, true));
|
||||||
|
|
|
@ -66,6 +66,12 @@
|
||||||
<url>https://github.com/protocolbuffers/protobuf/tree/master/java</url>
|
<url>https://github.com/protocolbuffers/protobuf/tree/master/java</url>
|
||||||
<license>Protocol Buffers License</license>
|
<license>Protocol Buffers License</license>
|
||||||
</notice>
|
</notice>
|
||||||
|
<notice>
|
||||||
|
<name>Simple Flat Mapper</name>
|
||||||
|
<url>https://github.com/arnaudroger/SimpleFlatMapper</url>
|
||||||
|
<copyright>Copyright (c) 2014 Arnaud Roger</copyright>
|
||||||
|
<license>MIT License</license>
|
||||||
|
</notice>
|
||||||
<notice>
|
<notice>
|
||||||
<name>TextDrawable</name>
|
<name>TextDrawable</name>
|
||||||
<url>https://github.com/amulyakhare/TextDrawable</url>
|
<url>https://github.com/amulyakhare/TextDrawable</url>
|
||||||
|
|
|
@ -377,6 +377,7 @@
|
||||||
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
<string name="importer_help_authenticator_plus">Supply an Authenticator Plus export file obtained through <b>Settings -> Backup & Restore -> Export as Text and HTML</b>.</string>
|
||||||
<string name="importer_help_authy">Supply a copy of <b>/data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml</b>, located in the internal storage directory of Authy.</string>
|
<string name="importer_help_authy">Supply a copy of <b>/data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml</b>, located in the internal storage directory of Authy.</string>
|
||||||
<string name="importer_help_andotp">Supply an andOTP export/backup file.</string>
|
<string name="importer_help_andotp">Supply an andOTP export/backup file.</string>
|
||||||
|
<string name="importer_help_bitwarden">Supply a Bitwarden export/backup file. Encrypted files are not supported.</string>
|
||||||
<string name="importer_help_duo">Supply a copy of <b>/data/data/com.duosecurity.duomobile/files/duokit/accounts.json</b>, located in the internal storage directory of DUO.</string>
|
<string name="importer_help_duo">Supply a copy of <b>/data/data/com.duosecurity.duomobile/files/duokit/accounts.json</b>, located in the internal storage directory of DUO.</string>
|
||||||
<string name="importer_help_freeotp">Supply a copy of <b>/data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml</b>, located in the internal storage directory of FreeOTP.</string>
|
<string name="importer_help_freeotp">Supply a copy of <b>/data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml</b>, located in the internal storage directory of FreeOTP.</string>
|
||||||
<string name="importer_help_freeotp_plus">Supply a FreeOTP+ export file.</string>
|
<string name="importer_help_freeotp_plus">Supply a FreeOTP+ export file.</string>
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.beemdevelopment.aegis.importers;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@ -168,6 +169,18 @@ public class DatabaseImporterTest {
|
||||||
checkImportedAuthyEntries(entries);
|
checkImportedAuthyEntries(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImportBitwardenJson() throws IOException, DatabaseImporterException {
|
||||||
|
List<VaultEntry> entries = importPlain(BitwardenImporter.class, "bitwarden.json");
|
||||||
|
checkImportedBitwardenEntries(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testImportBitwardenCsv() throws IOException, DatabaseImporterException {
|
||||||
|
List<VaultEntry> entries = importPlain(BitwardenImporter.class, "bitwarden.csv");
|
||||||
|
checkImportedBitwardenEntries(entries);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testImportFreeOtp() throws IOException, DatabaseImporterException, OtpInfoException {
|
public void testImportFreeOtp() throws IOException, DatabaseImporterException, OtpInfoException {
|
||||||
List<VaultEntry> entries = importPlain(FreeOtpImporter.class, "freeotp.xml");
|
List<VaultEntry> entries = importPlain(FreeOtpImporter.class, "freeotp.xml");
|
||||||
|
@ -347,6 +360,19 @@ public class DatabaseImporterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkImportedBitwardenEntries(List<VaultEntry> entries) {
|
||||||
|
byte[] secret, vectorSecret;
|
||||||
|
for (VaultEntry entry : entries) {
|
||||||
|
if(entry.getInfo().getTypeId().equals(SteamInfo.ID)) {
|
||||||
|
secret = entry.getInfo().getSecret();
|
||||||
|
vectorSecret = getEntryVectorBySecret(secret).getInfo().getSecret();
|
||||||
|
assertNotNull(String.format("Steam secret has not been found (%s)", vectorSecret));
|
||||||
|
} else {
|
||||||
|
checkImportedEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void checkImportedEntries(List<VaultEntry> entries) {
|
private void checkImportedEntries(List<VaultEntry> entries) {
|
||||||
for (VaultEntry entry : entries) {
|
for (VaultEntry entry : entries) {
|
||||||
checkImportedEntry(entry);
|
checkImportedEntry(entry);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp
|
||||||
|
,,login,Test 1,,,,,,otpauth://totp/Deno:Mason?secret=4SJHB4GSD43FZBAI7C2HLRJGPQ&issuer=Deno&algorithm=SHA1&digits=6&period=30
|
||||||
|
,,login,Test 2,,,,,,otpauth://totp/SPDX:James?secret=5OM4WOOGPLQEF6UGN3CPEOOLWU&issuer=SPDX&algorithm=SHA256&digits=7&period=20
|
||||||
|
,,login,Test 3,,,,,,otpauth://totp/Airbnb:Elijah?secret=7ELGJSGXNCCTV3O6LKJWYFV2RA&issuer=Airbnb&algorithm=SHA512&digits=8&period=50
|
||||||
|
,,login,Test 4,,,,,,steam://JRZCL47CMXVOQMNPZR2F7J4RGI
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
{
|
||||||
|
"encrypted": false,
|
||||||
|
"folders": [],
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"id": "ffdea2cc-923d-47a0-be99-ae7800fd51e4",
|
||||||
|
"organizationId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Test 1",
|
||||||
|
"notes": null,
|
||||||
|
"favorite": false,
|
||||||
|
"login": {
|
||||||
|
"uris": null,
|
||||||
|
"username": null,
|
||||||
|
"password": null,
|
||||||
|
"totp": "otpauth://totp/Deno:Mason?secret=4SJHB4GSD43FZBAI7C2HLRJGPQ&issuer=Deno&algorithm=SHA1&digits=6&period=30"
|
||||||
|
},
|
||||||
|
"collectionIds": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0f954b25-dbe9-4b38-b8ff-ae7800fd784d",
|
||||||
|
"organizationId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Test 2",
|
||||||
|
"notes": null,
|
||||||
|
"favorite": false,
|
||||||
|
"login": {
|
||||||
|
"uris": null,
|
||||||
|
"username": null,
|
||||||
|
"password": null,
|
||||||
|
"totp": "otpauth://totp/SPDX:James?secret=5OM4WOOGPLQEF6UGN3CPEOOLWU&issuer=SPDX&algorithm=SHA256&digits=7&period=20"
|
||||||
|
},
|
||||||
|
"collectionIds": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b0f3aae6-f4da-49bc-b981-ae7800fd9467",
|
||||||
|
"organizationId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Test 3",
|
||||||
|
"notes": null,
|
||||||
|
"favorite": false,
|
||||||
|
"login": {
|
||||||
|
"uris": null,
|
||||||
|
"username": null,
|
||||||
|
"password": null,
|
||||||
|
"totp": "otpauth://totp/Airbnb:Elijah?secret=7ELGJSGXNCCTV3O6LKJWYFV2RA&issuer=Airbnb&algorithm=SHA512&digits=8&period=50"
|
||||||
|
},
|
||||||
|
"collectionIds": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eb10632a-3bfd-40cd-9e4c-8c01c020b944",
|
||||||
|
"organizationId": null,
|
||||||
|
"folderId": null,
|
||||||
|
"type": 1,
|
||||||
|
"name": "Test Steam",
|
||||||
|
"notes": null,
|
||||||
|
"favorite": false,
|
||||||
|
"login": {
|
||||||
|
"uris": null,
|
||||||
|
"username": null,
|
||||||
|
"password": null,
|
||||||
|
"totp": "steam://JRZCL47CMXVOQMNPZR2F7J4RGI"
|
||||||
|
},
|
||||||
|
"collectionIds": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue