diff --git a/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java b/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java index f13b3db3..8c896980 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java +++ b/app/src/main/java/com/beemdevelopment/aegis/importers/TwoFASImporter.java @@ -7,6 +7,7 @@ import com.beemdevelopment.aegis.crypto.CryptoUtils; import com.beemdevelopment.aegis.encoding.Base32; import com.beemdevelopment.aegis.encoding.Base64; 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; @@ -59,7 +60,7 @@ public class TwoFASImporter extends DatabaseImporter { String json = new String(IOUtils.readAll(stream), StandardCharsets.UTF_8); JSONObject obj = new JSONObject(json); int version = obj.getInt("schemaVersion"); - if (version > 2) { + if (version > 3) { throw new DatabaseImporterException(String.format("Unsupported schema version: %d", version)); } @@ -173,13 +174,23 @@ public class TwoFASImporter extends DatabaseImporter { try { byte[] secret = Base32.decode(obj.getString("secret")); JSONObject info = obj.getJSONObject("otp"); - String issuer = info.getString("issuer"); + String issuer = info.optString("issuer"); String name = info.optString("account"); int digits = info.optInt("digits", TotpInfo.DEFAULT_DIGITS); - int period = info.optInt("period", TotpInfo.DEFAULT_PERIOD); String algorithm = info.optString("algorithm", TotpInfo.DEFAULT_ALGORITHM); - OtpInfo otp = new TotpInfo(secret, algorithm, digits, period); + OtpInfo otp; + String tokenType = JsonUtils.optString(info, "tokenType"); + if (tokenType == null || tokenType.equals("TOTP")) { + int period = info.optInt("period", TotpInfo.DEFAULT_PERIOD); + otp = new TotpInfo(secret, algorithm, digits, period); + } else if (tokenType.equals("HOTP")) { + long counter = info.optLong("counter", 0); + otp = new HotpInfo(secret, algorithm, digits, counter); + } else { + throw new DatabaseImporterEntryException(String.format("Unrecognized tokenType: %s", tokenType), obj.toString()); + } + return new VaultEntry(otp, name, issuer); } catch (OtpInfoException | JSONException | EncodingException e) { throw new DatabaseImporterEntryException(e, obj.toString()); 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 9feef246..2fc3d89d 100644 --- a/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java +++ b/app/src/test/java/com/beemdevelopment/aegis/importers/DatabaseImporterTest.java @@ -238,7 +238,7 @@ public class DatabaseImporterTest { public void testImportTwoFASAuthenticatorSchema1() throws DatabaseImporterException, IOException, OtpInfoException { List entries = importPlain(TwoFASImporter.class, "2fas_authenticator.json"); for (VaultEntry entry : entries) { - // 2FAS Authenticator doesn't support HOTP, different hash algorithms, periods or digits, so fix those up here + // 2FAS Authenticator schema v1 doesn't support HOTP, different hash algorithms, periods or digits, so fix those up here VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); entryVector.setInfo(new TotpInfo(entryVector.getInfo().getSecret())); checkImportedEntry(entryVector, entry); @@ -248,7 +248,7 @@ public class DatabaseImporterTest { @Test public void testImportTwoFASAuthenticatorSchema2Plain() throws DatabaseImporterException, IOException, OtpInfoException { List entries = importPlain(TwoFASImporter.class, "2fas_authenticator_plain.2fas"); - checkImportedTwoFASEntries(entries); + checkImportedTwoFASSchema2Entries(entries); } @Test @@ -257,7 +257,22 @@ public class DatabaseImporterTest { final char[] password = "test".toCharArray(); return ((TwoFASImporter.EncryptedState) encryptedState).decrypt(password); }); - checkImportedTwoFASEntries(entries); + checkImportedTwoFASSchema2Entries(entries); + } + + @Test + public void testImportTwoFASAuthenticatorSchema3Plain() throws DatabaseImporterException, IOException, OtpInfoException { + List entries = importPlain(TwoFASImporter.class, "2fas_authenticator_plain_v3.2fas"); + checkImportedEntries(entries); + } + + @Test + public void testImportTwoFASAuthenticatorSchema3Encrypted() throws DatabaseImporterException, IOException, OtpInfoException { + List entries = importEncrypted(TwoFASImporter.class, "2fas_authenticator_encrypted_v3.2fas", encryptedState -> { + final char[] password = "test".toCharArray(); + return ((TwoFASImporter.EncryptedState) encryptedState).decrypt(password); + }); + checkImportedEntries(entries); } private List importPlain(Class type, String resName) @@ -302,7 +317,7 @@ public class DatabaseImporterTest { return result.getEntries(); } - private void checkImportedTwoFASEntries(List entries) throws OtpInfoException { + private void checkImportedTwoFASSchema2Entries(List entries) throws OtpInfoException { for (VaultEntry entry : entries) { // 2FAS Authenticator doesn't support certain features, so fix those entries up here VaultEntry entryVector = getEntryVectorBySecret(entry.getInfo().getSecret()); diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted_v3.2fas b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted_v3.2fas new file mode 100644 index 00000000..e4c7953b --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_encrypted_v3.2fas @@ -0,0 +1,11 @@ +{ + "services": [], + "updatedAt": 1668548484538, + "schemaVersion": 3, + "appVersionCode": 4000004, + "appVersionName": "4.0.0", + "appOrigin": "android", + "groups": [], + "servicesEncrypted": "oAAWclilpn+Y9ol1SENxfw2c1zejXyYdEkC6+G3tpbep7MXXvC\/xAgzwS+RFt\/jXoivKsrpVCFrRbuhziyHUfaWvOmHCydro5NlrQH9SHbeasoGBeoKH+x3H7v5oDXE4PO4L2+rShWfHILmgkum1HHPfc85ojm2qp5NgsCbVxuMPUnnwb+k8BLAsrPZMx708LccaoZLYz+Bkd7Vb6ONYn\/8gkOulNp9Ue9O\/3Q\/ZVLHpR+nveszobXoHAukRNDQ8kNDnmetjqH1xbuJf\/0UMO6kgRs0fdxZvrBg3afFasymlNH6Iwz8S+yfOT32b5YuLcZNo2OkhaV1oqcL+aZEYXSpcPZM3iqB817agI+qeUPQvsqwkgu\/whzMrGVBb1DAf+b7aPEHsxXCGk5E72xgU5dnZwAR6x12vO8LUhuRywEXi7X2klQ1cpiOSzi7ZefnE61dTgJAmbVoMBNM5tXtvjZmuM+zpLHBzl6Z6lIFWabGT2G3b6KQ0yadE3Wmw4FK\/zG0i6bC4qRnA2I0V75CJCIboxOTiN2QodhITZAQvQFfn5AzOUk0bdwJP5XeNKXl1eAWaDzBzo1vNELlF0m3WH9bEXLHzE6Q1IoJmZX\/XY9e7TemwSIIRl0NvZ3Vi\/LrNpYzCQJ8UrcBQzjevuvWuBU4k8xyVIHo9CGF5Y10BeWi1dC2Ws5OpXK9hfni92RWYRVcaBgQjFAJ\/TpE\/ynL33FD\/KTGVwp6wSE6CCedWFdsxk3ushckU53yoUqHAwvRB76Kk3\/TrkWFj3Oph1cTdgPnHz\/0\/6VaphOuNkMtebeAzDtrV55IKqzQZ+6GxRgr5sFDVcy2djrUYGOPemCvYIuXyVhpRq1dbTfJ0lsJ+fiVmnXxmb3hNXskT87QW+TVDFxqfTD7h5RXVFY7IFdXTd5bwq6EyAfvJtuZqu7fX\/Ft1Y3MCPEFbQw9QLyz6CbSZJmG1CafgrIS+R2PlBrJM8LlhId\/BebkwfxXX4tGwI4vAVdw2XmPjeAjZzXiTEpneSz5tFp\/BHoolFnmJkCm+z+F\/zrZbf0K4qtrOiqEdKs+qNWww6h7sEFPPvaXyYBdNGm23F+Rb2N2ZZnUs8tHPaltsI4LSwi+7gNgRnVdI3Ljpp85xbRfEI0OZ7ZJIeS5w3nBWUOu3\/pOBpEsCtwSkpTr9scHwiCC5YDSdYvFSAzZEgIDNVxT28AHFhoQJBcG7JVPIa52mtACYExqoRdbTlQ4DJqMG2WxtnhV\/AHzYqahPD+\/RHmV9yC23vddgMykDrzSQHVxOXzODpioGYSZIZrYIBCGaxIEa7F4wTp7XjR60jvWtUkInr0RontbE7lYMnMTIxttgMqQto2WDxKWMqroMEWHnUnMbfIL5B+7+UpoWN7Zp09z8p\/znXrMn25\/EmcXTcmGXBdP9\/Qh5CBa0v5XUafVvHrTDBoZCDE7RaHnRcou5RBTWIgqDNO+yfKc08PDpw0UJ0QdtxLrKgzkgbIXYvHS+ksGPq5wubp1IrkjYuUwjKKgSaWAuEyB5r4Oa54jdz8KNuJskMLSxqndz0TV2qSBGbB7YdyxR8KyGpG6hfqqudkGmYUkl+nTdeppTGnry67ya9B8iYcijMXBLvk\/3DKVDisv8K4r9kSxvPIfzd5MO\/ui8pRXA+FCRThBxqSS\/lThpcyEIj90VVYxmVyDaA47XE255\/0kEqQm4v67HIwGqIVsK7AfjJkQUAKlVzraKyJB441pDiNParlI5ln0gbpeSnc9iH92zRXfwKZCyF1\/009\/JVbrKIcfNEvDpTUaexwcjetYqc8aLLSEdatqa515Nxhp6nPvHdV1sj1it81Fl+\/nCGrTZ\/YMcYoPoA9Sv7pZU3RMDbCA\/dREsSPGQ0+mCNcwHHe2ZTuki5DIB+nsAsV16g\/OH0vxDr25BLqXuLjhx8Jc9Rlr6jg+32HOJXrRuoSAsFCzyCmtfcwxU0v4+IM\/6cq8\/qdlK8D2zvxMZPL7SUje3\/VB77PHDkceof+DvtmJ\/pUlmXHdSApdXbfWjlfP3DWWYmCZhHlO3pg0TqPKufVSZWFU7AfRuIimNgRs4s\/2ixNs9p9DZbzno6bb5YLh7ql3TqsL2xeYXBTQWpSkGH\/+0BvEOkXGjxbNxdbsjYBxvOp2bVjwCX0omL3C8K84IVnQDZSMEftrLtIlbBmnabN7g5qKzUtNtfN1hB4EFqq7LVvGgkOUCZCZx2dWwmgHqeUEvtKhdlvFuPNEVtd5co0JieFhFJtGqVbGkjMBg22x0uMclaGhma2xYAcgEjiBdg1QVdHq+1gnYyowKG4YGCpXrshC3e4\/ujk+iUmPGkYGYrweglZAnVm+U15OXoJn0lzjBO29jhfZi8d\/DirG5b4lT0C\/6S\/UhG5bGrNcirQsvr5SIbQfoSaLXlJ2SCKuUw51wlF9khENW3o2GPn3L2tlFOaMf5mbuHi4P8eJDhWY2KA6uTEWddjbfVILSPhhKcpWdBu01CxIP\/2gQS5Ou1hjcqyN1jCSxIFVANYkr+IpKooers03bPVmTrLGYalmU+CI8FBYgkZnL4P+OuSqivMxvcscLN25xJ7kz+i2eRz98HoNsOA1Dx+lZEfeailmJh8F\/a\/agUCVWufAem0vC6mZMWjj8QKORpOwh+0wgV2rmWJK6DTPA6lH1ivnqdrE\/JE8V4wOTCE+NeeBb7VIRe0Q4q2\/ORMVPK\/YyrKwARYhmC1L3FuEsFmeimhMweiIzAc7AWeykRY8xS0Laujy7VRzal9y3C6TUSUSeriApPhfhpQmNO8jjYHR22vaA530W4SKOiNEAYdFOolUyoLrUwgADvoH2MWv+Z4JpO4GBGTKUCUbnqn32gMpDgNmc3FzXtpyASF0=:F\/kH8kq9g0PjEEuQmL8eBnoN6eI0oz8AM9UJr\/ADTHoTrqDnbUY4joUQw2dyeNRYxsw1Oj0nGBUxg90dnDhs79X5C\/D2tsgns03tADJUe5I\/hnS8O+njyDcYVODUoS5eC5pHTXzhLmeKNZjhKuVr+XGlKdhnv72gn8JvwjEiwNh52T6RnD10pWwbxLJaMdkBKps7sFDfXxJofCsU8u\/aYmAAolMkViKsTJ1tHcaEz8eG0R4jhB3Lm8m8Cdtx+nGX57pJk+ptmEv+Px\/5rANfDxnNJ6iB+zg9xBFJk+YbJlQ6Cf6dQFye0gAlT3KWi26jA+Ozp+EoGHMm2TCRKnpemw==:q5DxA6tYE2A2AHWz", + "reference": "tQdQEgDTckuE\/Q02KeQJxxDYKAsn6mdAW6EYhtI4l9vN7RhVMFyXMYVDnH2Ujnj\/zWlV8lRtUfl3FWBpnBWM8YINff6Un3zTM9jEf0YYftj0HQzd8dnbM0u2ZxCftRblqk0dAiekjA05Vo\/ma6Y+mreGJpj\/awMP8OvgYWSu03F01PfklUOEmxSUrIMk6\/A8HiAZ\/PH9ayDvjOSZHZ05zahndayzqBSecdt0w+bejNFUM4SRWWC+njStxZGz4IfMHkkiGJoYyg0D5ezRHwINcw3u+1IaEzKJ8hqta7qXh+cx4Jig6rqB0cUQbUqY2LoCsEB9POKdlfBihVxKLKFss+TuLZW6DxtUNbAAaImSMBc=:F\/kH8kq9g0PjEEuQmL8eBnoN6eI0oz8AM9UJr\/ADTHoTrqDnbUY4joUQw2dyeNRYxsw1Oj0nGBUxg90dnDhs79X5C\/D2tsgns03tADJUe5I\/hnS8O+njyDcYVODUoS5eC5pHTXzhLmeKNZjhKuVr+XGlKdhnv72gn8JvwjEiwNh52T6RnD10pWwbxLJaMdkBKps7sFDfXxJofCsU8u\/aYmAAolMkViKsTJ1tHcaEz8eG0R4jhB3Lm8m8Cdtx+nGX57pJk+ptmEv+Px\/5rANfDxnNJ6iB+zg9xBFJk+YbJlQ6Cf6dQFye0gAlT3KWi26jA+Ozp+EoGHMm2TCRKnpemw==:giXb4rEQl9ofTh2T" +} \ No newline at end of file diff --git a/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain_v3.2fas b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain_v3.2fas new file mode 100644 index 00000000..2c0db774 --- /dev/null +++ b/app/src/test/resources/com/beemdevelopment/aegis/importers/2fas_authenticator_plain_v3.2fas @@ -0,0 +1,157 @@ +{ + "services": [ + { + "name": "Deno", + "secret": "4SJHB4GSD43FZBAI7C2HLRJGPQ", + "updatedAt": 1668540633217, + "otp": { + "label": "Deno:Mason", + "account": "Mason", + "issuer": "Deno", + "digits": 6, + "period": 30, + "algorithm": "SHA1", + "tokenType": "TOTP", + "source": "Link" + }, + "order": { + "position": 0 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + }, + { + "name": "SPDX", + "secret": "5OM4WOOGPLQEF6UGN3CPEOOLWU", + "updatedAt": 1668540644738, + "otp": { + "label": "SPDX:James", + "account": "James", + "issuer": "SPDX", + "digits": 7, + "period": 20, + "algorithm": "SHA256", + "tokenType": "TOTP", + "source": "Link" + }, + "order": { + "position": 1 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + }, + { + "name": "Airbnb", + "secret": "7ELGJSGXNCCTV3O6LKJWYFV2RA", + "updatedAt": 1668540650005, + "otp": { + "label": "Airbnb:Elijah", + "account": "Elijah", + "issuer": "Airbnb", + "digits": 8, + "period": 50, + "algorithm": "SHA512", + "tokenType": "TOTP", + "source": "Link" + }, + "order": { + "position": 2 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + }, + { + "name": "Issuu", + "secret": "YOOMIXWS5GN6RTBPUFFWKTW5M4", + "updatedAt": 1668540656157, + "otp": { + "label": "Issuu:James", + "account": "James", + "issuer": "Issuu", + "digits": 6, + "period": 30, + "algorithm": "SHA1", + "counter": 1, + "tokenType": "HOTP", + "source": "Link" + }, + "order": { + "position": 3 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + }, + { + "name": "Air Canada", + "secret": "KUVJJOM753IHTNDSZVCNKL7GII", + "updatedAt": 1668540661393, + "otp": { + "label": "Air Canada:Benjamin", + "account": "Benjamin", + "issuer": "Air Canada", + "digits": 7, + "period": 30, + "algorithm": "SHA256", + "counter": 50, + "tokenType": "HOTP", + "source": "Link" + }, + "order": { + "position": 4 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + }, + { + "name": "WWE", + "secret": "5VAML3X35THCEBVRLV24CGBKOY", + "updatedAt": 1668540666422, + "otp": { + "label": "WWE:Mason", + "account": "Mason", + "issuer": "WWE", + "digits": 8, + "period": 30, + "algorithm": "SHA512", + "counter": 10300, + "tokenType": "HOTP", + "source": "Link" + }, + "order": { + "position": 5 + }, + "icon": { + "selected": "IconCollection", + "iconCollection": { + "id": "a5b3fb65-4ec5-43e6-8ec1-49e24ca9e7ad" + } + } + } + ], + "updatedAt": 1668540786699, + "schemaVersion": 3, + "appVersionCode": 4000004, + "appVersionName": "4.0.0", + "appOrigin": "android", + "groups": [] +} \ No newline at end of file