From f3731c23a35f36eef7c98ab492d5cd5b3d0323ef Mon Sep 17 00:00:00 2001 From: JordanPlayz158 Date: Mon, 21 Nov 2022 14:56:53 -0500 Subject: [PATCH] Battle.net Importing Support Fixed issue caused by supplying the VaultEntry with the Base32 encoded string rather than the raw secret Added blizzard package to manifest so Aegis is allowed to query whether the app is installed Fixed VaultEntry to be more inline with other entries Removed the unnecessary encoding of the secret as it is used as is without encoding and changed the way the TotpInfo object is supplied with the relevant information. Credits to alexbakker (https://github.com/beemdevelopment/Aegis/pull/1032#pullrequestreview-1203477313) --- app/src/main/AndroidManifest.xml | 1 + .../aegis/importers/BattleNetImporter.java | 116 ++++++++++++++++++ .../aegis/importers/DatabaseImporter.java | 8 ++ app/src/main/res/values/strings.xml | 1 + .../aegis/importers/DatabaseImporterTest.java | 9 ++ .../aegis/vectors/VaultEntries.java | 3 +- .../importers/battle_net_authenticator.xml | 7 ++ 7 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/importers/BattleNetImporter.java create mode 100644 app/src/test/resources/com/beemdevelopment/aegis/importers/battle_net_authenticator.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a6616bf..e375fbfc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -149,6 +149,7 @@ + diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/BattleNetImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/BattleNetImporter.java new file mode 100644 index 00000000..8ff50ff4 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/BattleNetImporter.java @@ -0,0 +1,116 @@ +package com.beemdevelopment.aegis.importers; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Xml; + +import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.encoding.Hex; +import com.beemdevelopment.aegis.otp.OtpInfo; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.util.PreferenceParser; +import com.beemdevelopment.aegis.vault.VaultEntry; +import com.topjohnwu.superuser.io.SuFile; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class BattleNetImporter extends DatabaseImporter { + private static final String _pkgName = "com.blizzard.bma"; + private static final String _subPath = "shared_prefs/com.blizzard.bma.AUTH_STORE.xml"; + + private static final byte[] _key; + + public BattleNetImporter(Context context) { + super(context); + } + + static { + try { + _key = Hex.decode("398e27fc50276a656065b0e525f4c06c04c61075286b8e7aeda59da9813b5dd6c80d2fb38068773fa59ba47c17ca6c6479015c1d5b8b8f6b9a"); + } catch (EncodingException e) { + throw new RuntimeException(e); + } + } + + @Override + protected SuFile getAppPath() throws DatabaseImporterException, PackageManager.NameNotFoundException { + return getAppPath(_pkgName, _subPath); + } + + @Override + protected State read(InputStream stream, boolean isInternal) throws DatabaseImporterException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(stream, null); + parser.nextTag(); + + List entries = new ArrayList<>(); + for (PreferenceParser.XmlEntry entry : PreferenceParser.parse(parser)) { + if (entry.Name.equals("com.blizzard.bma.AUTH_STORE.HASH")) { + entries.add(entry.Value); + break; + } + } + return new BattleNetImporter.State(entries); + } catch (XmlPullParserException | IOException e) { + throw new DatabaseImporterException(e); + } + } + + public static class State extends DatabaseImporter.State { + private final List _entries; + + public State(List entries) { + super(false); + _entries = entries; + } + + @Override + public Result convert() { + Result result = new Result(); + + for (String str : _entries) { + try { + VaultEntry entry = convertEntry(str); + result.addEntry(entry); + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + + return result; + } + + private static VaultEntry convertEntry(String hashString) throws DatabaseImporterEntryException { + try { + byte[] hash = Hex.decode(hashString); + if (hash.length != _key.length) { + throw new DatabaseImporterEntryException(String.format("Unexpected hash length: %d", hash.length), hashString); + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < hash.length; i++) { + char c = (char) (hash[i] ^ _key[i]); + sb.append(c); + } + + final int secretLen = 40; + byte[] secret = Hex.decode(sb.substring(0, secretLen)); + String serial = sb.substring(secretLen); + + OtpInfo info = new TotpInfo(secret, OtpInfo.DEFAULT_ALGORITHM, 8, TotpInfo.DEFAULT_PERIOD); + return new VaultEntry(info, serial, "Battle.net"); + } catch (OtpInfoException | EncodingException e) { + throw new DatabaseImporterEntryException(e, hashString); + } + } + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java index daf8d416..24a2153c 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -33,6 +33,7 @@ public abstract class DatabaseImporter { _importers.add(new Definition("andOTP", AndOtpImporter.class, R.string.importer_help_andotp, false)); _importers.add(new Definition("Authenticator Plus", AuthenticatorPlusImporter.class, R.string.importer_help_authenticator_plus, false)); _importers.add(new Definition("Authy", AuthyImporter.class, R.string.importer_help_authy, true)); + _importers.add(new Definition("Battle.net Authenticator", BattleNetImporter.class, R.string.importer_help_battle_net_authenticator, 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("FreeOTP", FreeOtpImporter.class, R.string.importer_help_freeotp, true)); @@ -102,6 +103,13 @@ public abstract class DatabaseImporter { private final @StringRes int _help; private final boolean _supportsDirect; + /** + * + * @param name The name of the Authenticator the importer handles. + * @param type The class which does the importing. + * @param help The string that explains the type of file needed (and optionally where it can be obtained). + * @param supportsDirect Whether the importer can directly import the entries from the app's internal storage using root access. + */ public Definition(String name, Class type, @StringRes int help, boolean supportsDirect) { _name = name; _type = type; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 383bc18e..f1ce9fb9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -441,6 +441,7 @@ Supply a copy of /data/data/com.authy.authy/shared_prefs/com.authy.storage.tokens.authenticator.xml, located in the internal storage directory of Authy. Supply an andOTP export/backup file. Supply a Bitwarden export/backup file. Encrypted files are not supported. + Supply a copy of /data/data/com.blizzard.bma/shared_prefs/com.blizzard.bma.AUTH_STORE.xml, located in the internal storage directory of Battle.net Authenticator. Supply a copy of /data/data/com.duosecurity.duomobile/files/duokit/accounts.json, located in the internal storage directory of DUO. Supply a copy of /data/data/org.fedorahosted.freeotp/shared_prefs/tokens.xml, located in the internal storage directory of FreeOTP. Supply a FreeOTP+ export file. diff --git a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java index 2fc3d89d..9b00db22 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java @@ -158,6 +158,15 @@ public class DatabaseImporterTest { checkImportedAuthyEntries(entries); } + @Test + public void testImportBattleNetXml() throws DatabaseImporterException, IOException, OtpInfoException { + List entries = importPlain(BattleNetImporter.class, "battle_net_authenticator.xml"); + + for (VaultEntry entry : entries) { + checkImportedEntry(entry); + } + } + @Test public void testImportBitwardenJson() throws IOException, DatabaseImporterException, OtpInfoException { List entries = importPlain(BitwardenImporter.class, "bitwarden.json"); diff --git a/app/src/test/java/com/beemdevelopment/aegis/vectors/VaultEntries.java b/app/src/test/java/com/beemdevelopment/aegis/vectors/VaultEntries.java index 683ae047..290d6ff5 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/vectors/VaultEntries.java +++ b/app/src/test/java/com/beemdevelopment/aegis/vectors/VaultEntries.java @@ -25,7 +25,8 @@ public class VaultEntries { new VaultEntry(new HotpInfo(Base32.decode("YOOMIXWS5GN6RTBPUFFWKTW5M4"), "SHA1", 6, 1), "James", "Issuu"), new VaultEntry(new HotpInfo(Base32.decode("KUVJJOM753IHTNDSZVCNKL7GII"), "SHA256", 7, 50), "Benjamin", "Air Canada"), new VaultEntry(new HotpInfo(Base32.decode("5VAML3X35THCEBVRLV24CGBKOY"), "SHA512", 8, 10300), "Mason", "WWE"), - new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing") + new VaultEntry(new SteamInfo(Base32.decode("JRZCL47CMXVOQMNPZR2F7J4RGI"), "SHA1", 5, 30), "Sophia", "Boeing"), + new VaultEntry(new TotpInfo(Base32.decode("BMGRXPGFARQQF4GMT25JATL2VYLAHDBI"), "SHA1", 8, 30), "US-2211-2050-3346", "Battle.net") ); } catch (OtpInfoException | EncodingException e) { throw new RuntimeException(e); diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/battle_net_authenticator.xml b/app/src/test/resources/com/beemdevelopment/aegis/importers/battle_net_authenticator.xml new file mode 100644 index 00000000..16714447 --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/battle_net_authenticator.xml @@ -0,0 +1,7 @@ + + + + 09ec179861450806035080d113c5f05e62f67316110eec1bd495a9cdb65a3cb3f93b1f80b80b4507f0c8894e25fb5d494b31692d76b8bc5fac + + + \ No newline at end of file