From d3463b82bcfb6b76f4e6c84dc7d6c9665e9c2b28 Mon Sep 17 00:00:00 2001 From: Alexander Bakker Date: Thu, 26 Dec 2019 15:47:26 +0100 Subject: [PATCH] Add support for importing from Microsoft Authenticator Adds support for importing TOTP and Microsoft accounts from Microsoft Authenticator. Microsoft accounts also use TOTP, but with 8 digits instead of 6. This implementation suffers from the same bug (#82) as Google Authenticator, but I haven't thought of a nice way to solve it yet. We should fix that before including this feature in a release, though. --- .../aegis/importers/DatabaseImporter.java | 2 + .../importers/MicrosoftAuthImporter.java | 126 ++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java 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 f72711bb..a8dabdb5 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/DatabaseImporter.java @@ -32,6 +32,7 @@ public abstract class DatabaseImporter { _importers.put("FreeOTP", FreeOtpImporter.class); _importers.put("FreeOTP+", FreeOtpPlusImporter.class); _importers.put("Google Authenticator", GoogleAuthImporter.class); + _importers.put("Microsoft Authenticator", MicrosoftAuthImporter.class); _importers.put("Steam", SteamImporter.class); _importers.put("WinAuth", WinAuthImporter.class); @@ -40,6 +41,7 @@ public abstract class DatabaseImporter { _appImporters.put("FreeOTP", FreeOtpImporter.class); _appImporters.put("FreeOTP+", FreeOtpPlusImporter.class); _appImporters.put("Google Authenticator", GoogleAuthImporter.class); + _appImporters.put("Microsoft Authenticator", MicrosoftAuthImporter.class); _appImporters.put("Steam", SteamImporter.class); } diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java new file mode 100644 index 00000000..33af0eae --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/MicrosoftAuthImporter.java @@ -0,0 +1,126 @@ +package com.beemdevelopment.aegis.importers; + +import android.content.Context; +import android.database.Cursor; + +import com.beemdevelopment.aegis.encoding.Base32; +import com.beemdevelopment.aegis.encoding.Base64; +import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.otp.OtpInfo; +import com.beemdevelopment.aegis.otp.OtpInfoException; +import com.beemdevelopment.aegis.otp.TotpInfo; +import com.beemdevelopment.aegis.vault.VaultEntry; + +import java.util.List; + +public class MicrosoftAuthImporter extends DatabaseImporter { + private static final String _subPath = "databases/PhoneFactor"; + private static final String _pkgName = "com.azure.authenticator"; + + private static final int TYPE_TOTP = 0; + private static final int TYPE_MICROSOFT = 1; + + public MicrosoftAuthImporter(Context context) { + super(context); + } + + @Override + protected String getAppPkgName() { + return _pkgName; + } + + @Override + protected String getAppSubPath() { + return _subPath; + } + + @Override + public State read(FileReader reader) throws DatabaseImporterException { + SqlImporterHelper helper = new SqlImporterHelper(getContext()); + List entries = helper.read(Entry.class, reader.getStream(), "accounts"); + return new State(entries); + } + + public static class State extends DatabaseImporter.State { + private List _entries; + + private State(List entries) { + super(false); + _entries = entries; + } + + @Override + public Result convert() { + Result result = new Result(); + + for (Entry sqlEntry : _entries) { + try { + int type = sqlEntry.getType(); + if (type == TYPE_TOTP || type == TYPE_MICROSOFT) { + VaultEntry entry = convertEntry(sqlEntry); + result.addEntry(entry); + } + } catch (DatabaseImporterEntryException e) { + result.addError(e); + } + } + + return result; + } + + private static VaultEntry convertEntry(Entry entry) throws DatabaseImporterEntryException { + try { + byte[] secret; + int digits = 6; + + switch (entry.getType()) { + case TYPE_TOTP: + secret = Base32.decode(entry.getSecret()); + break; + case TYPE_MICROSOFT: + digits = 8; + secret = Base64.decode(entry.getSecret()); + break; + default: + throw new DatabaseImporterEntryException(String.format("Unsupported OTP type: %d", entry.getType()), entry.toString()); + } + + OtpInfo info = new TotpInfo(secret, "SHA1", digits, 30); + return new VaultEntry(info, entry.getUserName(), entry.getIssuer()); + } catch (EncodingException | OtpInfoException e) { + throw new DatabaseImporterEntryException(e, entry.toString()); + } + } + } + + private static class Entry extends SqlImporterHelper.Entry { + private int _type; + private String _secret; + private String _issuer; + private String _userName; + + public Entry(Cursor cursor) { + super(cursor); + _type = SqlImporterHelper.getInt(cursor, "account_type"); + _secret = SqlImporterHelper.getString(cursor, "oath_secret_key"); + _issuer = SqlImporterHelper.getString(cursor, "name"); + _userName = SqlImporterHelper.getString(cursor, "username"); + } + + public int getType() { + return _type; + } + + public String getSecret() { + return _secret; + } + + public String getIssuer() { + return _issuer; + } + + public String getUserName() { + return _userName; + } + } +}