Merge remote-tracking branch 'upstream/master'

pull/326/head
Andrey Saksonov 6 years ago
commit f543802f9e
No known key found for this signature in database
GPG Key ID: DE720735D1E82B84

@ -19,8 +19,9 @@ android {
applicationId "com.beemdevelopment.aegis"
minSdkVersion 19
targetSdkVersion 29
versionCode 28
versionCode 29
versionName "1.1.4"
multiDexEnabled true
buildConfigField "String", "GIT_HASH", "\"${getGitHash()}\""
buildConfigField "String", "GIT_BRANCH", "\"${getGitBranch()}\""
}
@ -72,6 +73,7 @@ dependencies {
implementation 'androidx.preference:preference:1.1.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.guava:guava:28.2-android'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'com.github.apl-devs:appintro:5.1.0'
implementation 'com.github.avito-tech:krop:0.44'
@ -91,5 +93,6 @@ dependencies {
annotationProcessor 'androidx.annotation:annotation:1.1.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.5.2'
testImplementation 'com.google.guava:guava:28.2-jre"'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.5.2'
}

@ -1,7 +1,7 @@
package com.beemdevelopment.aegis.crypto;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.encoding.HexException;
import org.json.JSONException;
import org.json.JSONObject;
@ -30,7 +30,7 @@ public class CryptParameters implements Serializable {
return obj;
}
public static CryptParameters fromJson(JSONObject obj) throws JSONException, HexException {
public static CryptParameters fromJson(JSONObject obj) throws JSONException, EncodingException {
byte[] nonce = Hex.decode(obj.getString("nonce"));
byte[] tag = Hex.decode(obj.getString("tag"));
return new CryptParameters(nonce, tag);

@ -1,155 +1,23 @@
package com.beemdevelopment.aegis.encoding;
// modified for use in Aegis
import com.google.common.io.BaseEncoding;
/* (PD) 2001 The Bitzi Corporation
* Please see http://bitzi.com/publicdomain for more info.
*
* As modified by Patrick Woodworth:
*
* Copyright 2011 Patrick Woodworth
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.Arrays;
/**
* Base32 - encodes and decodes RFC3548 Base32
* (see http://www.faqs.org/rfcs/rfc3548.html )
*
* @author Robert Kaye
* @author Gordon Mohr
*/
public class Base32 {
private static final String base32Chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static final int[] base32Lookup =
{ 0xFF,0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
};
/**
* Encodes byte array to Base32 String.
*
* @param bytes Bytes to encode.
* @return Encoded byte array <code>bytes</code> as a String.
*
*/
public static char[] encode(final byte[] bytes) {
int i = 0, index = 0, digit = 0, j = 0;
int currByte, nextByte;
char[] base32 = new char[(bytes.length + 7) * 8 / 5];
while (i < bytes.length) {
currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256); // unsign
private static final BaseEncoding _encoding = BaseEncoding.base32().omitPadding();
/* Is the current digit going to span a byte boundary? */
if (index > 3) {
if ((i + 1) < bytes.length) {
nextByte =
(bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256);
} else {
nextByte = 0;
}
private Base32() {
digit = currByte & (0xFF >> index);
index = (index + 5) % 8;
digit <<= index;
digit |= nextByte >> (8 - index);
i++;
} else {
digit = (currByte >> (8 - (index + 5))) & 0x1F;
index = (index + 5) % 8;
if (index == 0)
i++;
}
base32[j++] = base32Chars.charAt(digit);
}
return Arrays.copyOf(base32, j);
}
/**
* Decodes the given Base32 String to a raw byte array.
*
* @param base32
* @return Decoded <code>base32</code> String as a raw byte array.
*/
public static byte[] decode(final char[] base32) throws Base32Exception {
int i, index, lookup, offset, digit;
byte[] bytes = new byte[base32.length * 5 / 8];
for (i = 0, index = 0, offset = 0; i < base32.length; i++) {
// stop decoding when a padding char is encountered
if (base32[i] == '=') {
// make sure the rest is also padding, but don't bother verifying the length
for (int j = i + 1; j < base32.length; j++) {
if (base32[j] != '=') {
throw new Base32Exception("bad padding");
}
}
break;
}
lookup = base32[i] - '0';
digit = decodeDigit(lookup);
if (index <= 3) {
index = (index + 5) % 8;
if (index == 0) {
bytes[offset] |= digit;
offset++;
if (offset >= bytes.length)
break;
} else {
bytes[offset] |= digit << (8 - index);
}
} else {
index = (index + 5) % 8;
bytes[offset] |= (digit >>> index);
offset++;
if (offset >= bytes.length) {
break;
}
bytes[offset] |= digit << (8 - index);
}
public static byte[] decode(String s) throws EncodingException {
try {
return _encoding.decode(s.toUpperCase());
} catch (IllegalArgumentException e) {
throw new EncodingException(e);
}
return bytes;
}
private static int decodeDigit(int c) throws Base32Exception {
/* Skip chars outside the lookup table */
if (c < 0 || c >= base32Lookup.length) {
throw new Base32Exception("char not found in base32 lookup table");
}
int digit = base32Lookup[c];
/* If this digit is not in the table, ignore it */
if (digit == 0xFF) {
throw new Base32Exception("char not found in base32 lookup table");
}
return digit;
public static String encode(byte[] data) {
return _encoding.encode(data);
}
}

@ -1,7 +0,0 @@
package com.beemdevelopment.aegis.encoding;
public class Base32Exception extends Exception {
public Base32Exception(String message) {
super(message);
}
}

@ -1,24 +1,27 @@
package com.beemdevelopment.aegis.encoding;
import com.google.common.io.BaseEncoding;
import java.nio.charset.StandardCharsets;
public class Base64 {
private static final int _flags = android.util.Base64.NO_WRAP;
private Base64() {
}
public static byte[] decode(String s) throws Base64Exception {
public static byte[] decode(String s) throws EncodingException {
try {
return android.util.Base64.decode(s, _flags);
return BaseEncoding.base64().decode(s);
} catch (IllegalArgumentException e) {
throw new Base64Exception(e);
throw new EncodingException(e);
}
}
public static byte[] decode(byte[] s) throws EncodingException {
return decode(new String(s, StandardCharsets.UTF_8));
}
public static String encode(byte[] data) {
byte[] encoded = android.util.Base64.encode(data, _flags);
return new String(encoded, StandardCharsets.UTF_8);
return BaseEncoding.base64().encode(data);
}
}

@ -1,7 +0,0 @@
package com.beemdevelopment.aegis.encoding;
public class Base64Exception extends Exception {
public Base64Exception(Throwable cause) {
super(cause);
}
}

@ -0,0 +1,9 @@
package com.beemdevelopment.aegis.encoding;
import java.io.IOException;
public class EncodingException extends IOException {
public EncodingException(Throwable cause) {
super(cause);
}
}

@ -1,46 +1,21 @@
package com.beemdevelopment.aegis.encoding;
// The hexadecimal utility functions in this file were taken and modified from: http://www.docjar.com/html/api/com/sun/xml/internal/bind/DatatypeConverterImpl.java.html
// It is licensed under GPLv2 with a classpath exception.
import com.google.common.io.BaseEncoding;
public class Hex {
private Hex() {
}
private static int hexToBin(char ch) {
if ('0' <= ch && ch <= '9') return ch - '0';
if ('A' <= ch && ch <= 'F') return ch - 'A' + 10;
if ('a' <= ch && ch <= 'f') return ch - 'a' + 10;
return -1;
}
private static final char[] hexCode = "0123456789abcdef".toCharArray();
public static byte[] decode(String s) throws HexException {
final int len = s.length();
if (len % 2 != 0)
throw new HexException("hexBinary needs to be even-length: " + s);
byte[] out = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
int h = hexToBin(s.charAt(i));
int l = hexToBin(s.charAt(i + 1));
if (h == -1 || l == -1)
throw new HexException("contains illegal character for hexBinary: " + s);
out[i / 2] = (byte) (h * 16 + l);
public static byte[] decode(String s) throws EncodingException {
try {
return BaseEncoding.base16().decode(s.toUpperCase());
} catch (IllegalArgumentException e) {
throw new EncodingException(e);
}
return out;
}
public static String encode(byte[] data) {
StringBuilder r = new StringBuilder(data.length * 2);
for (byte b : data) {
r.append(hexCode[(b >> 4) & 0xF]);
r.append(hexCode[(b & 0xF)]);
}
return r.toString();
return BaseEncoding.base16().lowerCase().encode(data);
}
}

@ -1,7 +0,0 @@
package com.beemdevelopment.aegis.encoding;
public class HexException extends Exception {
public HexException(String message) {
super(message);
}
}

@ -2,13 +2,13 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.vault.VaultFile;
import com.beemdevelopment.aegis.vault.VaultFileCredentials;
import com.beemdevelopment.aegis.vault.VaultFileException;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import org.json.JSONArray;
import org.json.JSONException;
@ -102,7 +102,7 @@ public class AegisImporter extends DatabaseImporter {
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
try {
return VaultEntry.fromJson(obj);
} catch (JSONException | OtpInfoException | Base64Exception e) {
} catch (JSONException | OtpInfoException | EncodingException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}

@ -10,7 +10,7 @@ import com.beemdevelopment.aegis.crypto.CryptResult;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
@ -201,7 +201,7 @@ public class AndOtpImporter extends DatabaseImporter {
String type = obj.getString("type").toLowerCase();
String algo = obj.getString("algorithm");
int digits = obj.getInt("digits");
byte[] secret = Base32.decode(obj.getString("secret").toCharArray());
byte[] secret = Base32.decode(obj.getString("secret"));
OtpInfo info;
switch (type) {
@ -230,7 +230,7 @@ public class AndOtpImporter extends DatabaseImporter {
}
return new VaultEntry(info, name, issuer);
} catch (DatabaseImporterException | Base32Exception | OtpInfoException | JSONException e) {
} catch (DatabaseImporterException | EncodingException | OtpInfoException | JSONException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}

@ -3,13 +3,16 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.util.Xml;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
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.ui.Dialogs;
import com.beemdevelopment.aegis.util.PreferenceParser;
import com.beemdevelopment.aegis.vault.VaultEntry;
import org.json.JSONArray;
import org.json.JSONException;
@ -18,11 +21,33 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
public class AuthyImporter extends DatabaseImporter {
private static final String _subPath = "shared_prefs/com.authy.storage.tokens.authenticator.xml";
private static final String _pkgName = "com.authy.authy";
private static final int ITERATIONS = 1000;
private static final int KEY_SIZE = 256;
private static final byte[] IV = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
public AuthyImporter(Context context) {
super(context);
}
@ -52,18 +77,76 @@ public class AuthyImporter extends DatabaseImporter {
}
}
return new State(array);
for (int i = 0; i < array.length(); i++) {
if (!array.getJSONObject(i).has("decryptedSecret")) {
return new EncryptedState(array);
}
}
return new DecryptedState(array);
} catch (XmlPullParserException | JSONException | IOException e) {
throw new DatabaseImporterException(e);
}
}
public static class State extends DatabaseImporter.State {
private JSONArray _obj;
public static class EncryptedState extends DatabaseImporter.State {
private JSONArray _array;
private EncryptedState(JSONArray array) {
super(true);
_array = array;
}
@Override
public void decrypt(Context context, DecryptListener listener) {
Dialogs.showPasswordInputDialog(context, R.string.enter_password_authy_message, password -> {
try {
for (int i = 0; i < _array.length(); i++) {
JSONObject obj = _array.getJSONObject(i);
String secretString = obj.optString("encryptedSecret", null);
if (secretString == null) {
continue;
}
byte[] encryptedSecret = Base64.decode(secretString);
byte[] salt = obj.getString("salt").getBytes(StandardCharsets.UTF_8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS, KEY_SIZE);
SecretKey key = factory.generateSecret(spec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
IvParameterSpec ivSpec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] secret = cipher.doFinal(encryptedSecret);
obj.remove("encryptedSecret");
obj.remove("salt");
obj.put("decryptedSecret", new String(secret, StandardCharsets.UTF_8));
}
DecryptedState state = new DecryptedState(_array);
listener.onStateDecrypted(state);
} catch (JSONException
| EncodingException
| NoSuchAlgorithmException
| InvalidKeySpecException
| InvalidAlgorithmParameterException
| InvalidKeyException
| NoSuchPaddingException
| BadPaddingException
| IllegalBlockSizeException e) {
listener.onError(e);
}
});
}
}
public static class DecryptedState extends DatabaseImporter.State {
private JSONArray _array;
private State(JSONArray obj) {
private DecryptedState(JSONArray array) {
super(false);
_obj = obj;
_array = array;
}
@Override
@ -71,8 +154,8 @@ public class AuthyImporter extends DatabaseImporter {
Result result = new Result();
try {
for (int i = 0; i < _obj.length(); i++) {
JSONObject entryObj = _obj.getJSONObject(i);
for (int i = 0; i < _array.length(); i++) {
JSONObject entryObj = _array.getJSONObject(i);
try {
VaultEntry entry = convertEntry(entryObj);
result.addEntry(entry);
@ -90,42 +173,46 @@ public class AuthyImporter extends DatabaseImporter {
private static VaultEntry convertEntry(JSONObject entry) throws DatabaseImporterEntryException {
try {
AuthyEntryInfo authyEntryInfo = new AuthyEntryInfo();
authyEntryInfo.OriginalName = entry.getString("originalName");
authyEntryInfo.OriginalName = entry.optString("originalName", null);
authyEntryInfo.OriginalIssuer = entry.optString("originalIssuer", null);
authyEntryInfo.AccountType = entry.getString("accountType");
authyEntryInfo.Name = entry.optString("name");
sanitizeEntryInfo(authyEntryInfo);
int digits = entry.getInt("digits");
byte[] secret = Base32.decode(entry.getString("decryptedSecret").toCharArray());
byte[] secret = Base32.decode(entry.getString("decryptedSecret"));
OtpInfo info = new TotpInfo(secret, "SHA1", digits, 30);
return new VaultEntry(info, authyEntryInfo.Name, authyEntryInfo.Issuer);
} catch (OtpInfoException | JSONException | Base32Exception e) {
} catch (OtpInfoException | JSONException | EncodingException e) {
throw new DatabaseImporterEntryException(e, entry.toString());
}
}
private static void sanitizeEntryInfo(AuthyEntryInfo info) {
String seperator = "";
String separator = "";
if (info.OriginalName.contains(":")) {
if (info.OriginalIssuer != null) {
info.Issuer = info.OriginalIssuer;
} else if (info.OriginalName != null && info.OriginalName.contains(":")) {
info.Issuer = info.OriginalName.substring(0, info.OriginalName.indexOf(":"));
seperator = ":";
separator = ":";
} else if (info.Name.contains(" - ")) {
info.Issuer = info.Name.substring(0, info.Name.indexOf(" - "));
seperator = " - ";
separator = " - ";
} else {
info.Issuer = info.AccountType.substring(0, 1).toUpperCase() + info.AccountType.substring(1);
}
info.Name = info.Name.replace(info.Issuer + seperator, "");
info.Name = info.Name.replace(info.Issuer + separator, "");
}
}
private static class AuthyEntryInfo {
String OriginalName;
String OriginalIssuer;
String AccountType;
String Issuer;
String Name;

@ -32,7 +32,9 @@ 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("TOTP Authenticator", TotpAuthenticatorImporter.class);
_importers.put("WinAuth", WinAuthImporter.class);
_appImporters = new LinkedHashMap<>();
@ -40,7 +42,9 @@ 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);
_appImporters.put("TOTP Authenticator", TotpAuthenticatorImporter.class);
}
public DatabaseImporter(Context context) {

@ -3,6 +3,11 @@ package com.beemdevelopment.aegis.importers;
public class DatabaseImporterEntryException extends Exception {
private String _text;
public DatabaseImporterEntryException(String message, String text) {
super(message);
_text = text;
}
public DatabaseImporterEntryException(Throwable cause, String text) {
super(cause);
_text = text;

@ -2,26 +2,17 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
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;
import com.topjohnwu.superuser.ShellUtils;
import com.beemdevelopment.aegis.vault.VaultEntry;
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 GoogleAuthImporter extends DatabaseImporter {
private static final int TYPE_TOTP = 0;
private static final int TYPE_HOTP = 1;
@ -45,37 +36,9 @@ public class GoogleAuthImporter extends DatabaseImporter {
@Override
public State read(FileReader reader) 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 (FileOutputStream out = new FileOutputStream(file)) {
ShellUtils.pump(reader.getStream(), 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)) {
List<Entry> entries = new ArrayList<>();
if (cursor.moveToFirst()) {
do {
Entry entry = new Entry(cursor);
entries.add(entry);
} while(cursor.moveToNext());
}
return new State(entries);
}
} catch (SQLiteException e) {
throw new DatabaseImporterException(e);
} finally {
// always delete the temporary file
file.delete();
}
SqlImporterHelper helper = new SqlImporterHelper(getContext());
List<Entry> entries = helper.read(Entry.class, reader.getStream(), "accounts");
return new State(entries);
}
public static class State extends DatabaseImporter.State {
@ -104,7 +67,7 @@ public class GoogleAuthImporter extends DatabaseImporter {
private static VaultEntry convertEntry(Entry entry) throws DatabaseImporterEntryException {
try {
byte[] secret = Base32.decode(entry.getSecret().toCharArray());
byte[] secret = Base32.decode(entry.getSecret());
OtpInfo info;
switch (entry.getType()) {
@ -125,33 +88,13 @@ public class GoogleAuthImporter extends DatabaseImporter {
}
return new VaultEntry(info, name, entry.getIssuer());
} catch (Base32Exception | OtpInfoException | DatabaseImporterException e) {
} catch (EncodingException | OtpInfoException | DatabaseImporterException e) {
throw new DatabaseImporterEntryException(e, entry.toString());
}
}
}
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));
}
private static long getLong(Cursor cursor, String columnName) {
return cursor.getLong(cursor.getColumnIndex(columnName));
}
private static class Entry {
private static class Entry extends SqlImporterHelper.Entry {
private int _type;
private String _secret;
private String _email;
@ -159,11 +102,12 @@ public class GoogleAuthImporter extends DatabaseImporter {
private long _counter;
public Entry(Cursor cursor) {
_type = getInt(cursor, "type");
_secret = getString(cursor, "secret");
_email = getString(cursor, "email", "");
_issuer = getString(cursor, "issuer", "");
_counter = getLong(cursor, "counter");
super(cursor);
_type = SqlImporterHelper.getInt(cursor, "type");
_secret = SqlImporterHelper.getString(cursor, "secret");
_email = SqlImporterHelper.getString(cursor, "email", "");
_issuer = SqlImporterHelper.getString(cursor, "issuer", "");
_counter = SqlImporterHelper.getLong(cursor, "counter");
}
public int getType() {

@ -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<Entry> entries = helper.read(Entry.class, reader.getStream(), "accounts");
return new State(entries);
}
public static class State extends DatabaseImporter.State {
private List<Entry> _entries;
private State(List<Entry> 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;
}
}
}

@ -0,0 +1,89 @@
package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import com.topjohnwu.superuser.ShellUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import static android.database.sqlite.SQLiteDatabase.OPEN_READONLY;
public class SqlImporterHelper {
private Context _context;
public SqlImporterHelper(Context context) {
_context = context;
}
public <T extends Entry> List<T> read(Class<T> type, InputStream inStream, String table) throws DatabaseImporterException {
File file;
try {
// create a temporary copy of the database so that SQLiteDatabase can open it
file = File.createTempFile("db-import-", "", _context.getCacheDir());
try (FileOutputStream out = new FileOutputStream(file)) {
ShellUtils.pump(inStream, out);
}
} catch (IOException e) {
throw new DatabaseImporterException(e);
}
try (SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, OPEN_READONLY, null)) {
try (Cursor cursor = db.rawQuery(String.format("SELECT * FROM %s", table), null)) {
List<T> entries = new ArrayList<>();
if (cursor.moveToFirst()) {
do {
T entry = type.getDeclaredConstructor(Cursor.class).newInstance(cursor);
entries.add(entry);
} while (cursor.moveToNext());
}
return entries;
} catch (InstantiationException | IllegalAccessException
| NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
} catch (SQLiteException e) {
throw new DatabaseImporterException(e);
} finally {
// always delete the temporary file
file.delete();
}
}
public static String getString(Cursor cursor, String columnName) {
return cursor.getString(cursor.getColumnIndex(columnName));
}
public static String getString(Cursor cursor, String columnName, String def) {
String res = cursor.getString(cursor.getColumnIndex(columnName));
if (res == null) {
return def;
}
return res;
}
public static int getInt(Cursor cursor, String columnName) {
return cursor.getInt(cursor.getColumnIndex(columnName));
}
public static long getLong(Cursor cursor, String columnName) {
return cursor.getLong(cursor.getColumnIndex(columnName));
}
public static abstract class Entry {
public Entry (Cursor cursor) {
}
}
}

@ -3,9 +3,9 @@ package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.content.pm.PackageManager;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.util.ByteInputStream;
@ -82,7 +82,7 @@ public class SteamImporter extends DatabaseImporter {
String account = obj.getString("account_name");
return new VaultEntry(info, account, "Steam");
} catch (JSONException | Base64Exception | OtpInfoException e) {
} catch (JSONException | EncodingException | OtpInfoException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}

@ -0,0 +1,235 @@
package com.beemdevelopment.aegis.importers;
import android.content.Context;
import android.util.Xml;
import androidx.appcompat.app.AlertDialog;
import com.beemdevelopment.aegis.R;
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.encoding.Hex;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.ui.Dialogs;
import com.beemdevelopment.aegis.util.PreferenceParser;
import com.beemdevelopment.aegis.vault.VaultEntry;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class TotpAuthenticatorImporter extends DatabaseImporter {
private static final String _subPath = "shared_prefs/TOTP_Authenticator_Preferences.xml";
private static final String _pkgName = "com.authenticator.authservice2";
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this is a hardcoded password and nonce, used solely to decrypt TOTP Authenticator backups
private static final char[] PASSWORD = "TotpAuthenticator".toCharArray();
private static final byte[] IV = new byte[]{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
private static final String PREF_KEY = "STATIC_TOTP_CODES_LIST";
public TotpAuthenticatorImporter(Context context) {
super(context);
}
@Override
protected String getAppPkgName() {
return _pkgName;
}
@Override
protected String getAppSubPath() {
return _subPath;
}
@Override
public State read(FileReader reader) throws DatabaseImporterException {
try {
if (reader.isInternal()) {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(reader.getStream(), null);
parser.nextTag();
String data = null;
for (PreferenceParser.XmlEntry entry : PreferenceParser.parse(parser)) {
if (entry.Name.equals(PREF_KEY)) {
data = entry.Value;
}
}
if (data == null) {
throw new DatabaseImporterException(String.format("Key %s not found in shared preference file", PREF_KEY));
}
List<JSONObject> entries = parse(data);
return new DecryptedState(entries);
} else {
byte[] base64 = reader.readAll();
byte[] cipherText = Base64.decode(base64);
return new EncryptedState(cipherText);
}
} catch (IOException | XmlPullParserException | JSONException e) {
throw new DatabaseImporterException(e);
}
}
private static List<JSONObject> parse(String data) throws JSONException {
JSONArray array = new JSONArray(data);
List<JSONObject> entries = new ArrayList<>();
for (int i = 0; i < array.length(); ++i) {
String s = array.getString(i);
entries.add(new JSONObject(s));
}
return entries;
}
public static class EncryptedState extends DatabaseImporter.State {
private byte[] _data;
public EncryptedState(byte[] data) {
super(true);
_data = data;
}
private DecryptedState decrypt(char[] password) throws DatabaseImporterException {
try {
// WARNING: DON'T DO THIS IN YOUR OWN CODE
// this is not a secure way to derive a key from a password
MessageDigest hash = MessageDigest.getInstance("SHA-256");
byte[] keyBytes = hash.digest(CryptoUtils.toBytes(password));
SecretKey key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
IvParameterSpec spec = new IvParameterSpec(IV);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = cipher.doFinal(_data);
JSONObject obj = new JSONObject(new String(bytes, StandardCharsets.UTF_8));
JSONArray keys = obj.names();
List<JSONObject> entries = new ArrayList<>();
if (keys != null && keys.length() > 0) {
entries = parse((String) keys.get(0));
}
return new DecryptedState(entries);
} catch (NoSuchAlgorithmException
| NoSuchPaddingException
| InvalidAlgorithmParameterException
| InvalidKeyException
| BadPaddingException
| IllegalBlockSizeException
| JSONException e) {
throw new DatabaseImporterException(e);
}
}
@Override
public void decrypt(Context context, DecryptListener listener) {
Dialogs.showSecureDialog(new AlertDialog.Builder(context)
.setMessage(R.string.choose_totpauth_importer)
.setPositiveButton(R.string.yes, (dialog, which) -> {
Dialogs.showPasswordInputDialog(context, password -> {
decrypt(password, listener);
});
})
.setNegativeButton(R.string.no, (dialog, which) -> {
decrypt(PASSWORD, listener);
})
.create());
}
private void decrypt(char[] password, DecryptListener listener) {
try {
DecryptedState state = decrypt(password);
listener.onStateDecrypted(state);
} catch (DatabaseImporterException e) {
listener.onError(e);
}
}
}
public static class DecryptedState extends DatabaseImporter.State {
private List<JSONObject> _objs;
private DecryptedState(List<JSONObject> objs) {
super(false);
_objs = objs;
}
@Override
public Result convert() {
Result result = new Result();
for (JSONObject obj : _objs) {
try {
VaultEntry entry = convertEntry(obj);
result.addEntry(entry);
} catch (DatabaseImporterEntryException e) {
result.addError(e);
}
}
return result;
}
private static VaultEntry convertEntry(JSONObject obj) throws DatabaseImporterEntryException {
try {
int base = obj.getInt("base");
String secretString = obj.getString("key");
byte[] secret;
switch (base) {
case 16:
secret = Hex.decode(secretString);
break;
case 32:
secret = Base32.decode(secretString);
break;
case 64:
secret = Base64.decode(secretString);
break;
default:
throw new DatabaseImporterEntryException(String.format("Unsupported secret encoding: base %d", base), obj.toString());
}
TotpInfo info = new TotpInfo(secret, "SHA1", 6, 30);
String name = obj.optString("name");
String issuer = obj.optString("issuer");
return new VaultEntry(info, name, issuer);
} catch (JSONException | OtpInfoException | EncodingException e) {
throw new DatabaseImporterEntryException(e, obj.toString());
}
}
}
}

@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.otp;
import android.net.Uri;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
public class GoogleAuthInfo {
private OtpInfo _info;
@ -71,8 +71,8 @@ public class GoogleAuthInfo {
// decode secret
byte[] secret;
try {
secret = Base32.decode(encodedSecret.toCharArray());
} catch (Base32Exception e) {
secret = Base32.decode(encodedSecret);
} catch (EncodingException e) {
throw new GoogleAuthInfoException("bad secret", e);
}
@ -101,7 +101,7 @@ public class GoogleAuthInfo {
default:
throw new GoogleAuthInfoException(String.format("unsupported otp type: %s", type));
}
} catch (OtpInfoException e) {
} catch (OtpInfoException | NumberFormatException e) {
throw new GoogleAuthInfoException(e);
}
@ -141,7 +141,7 @@ public class GoogleAuthInfo {
if (digits != null) {
info.setDigits(Integer.parseInt(digits));
}
} catch (OtpInfoException e) {
} catch (OtpInfoException | NumberFormatException e) {
throw new GoogleAuthInfoException(e);
}

@ -1,7 +1,7 @@
package com.beemdevelopment.aegis.otp;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
import org.json.JSONException;
import org.json.JSONObject;
@ -93,7 +93,7 @@ public abstract class OtpInfo implements Serializable {
OtpInfo info;
try {
byte[] secret = Base32.decode(obj.getString("secret").toCharArray());
byte[] secret = Base32.decode(obj.getString("secret"));
String algo = obj.getString("algo");
int digits = obj.getInt("digits");
@ -110,7 +110,7 @@ public abstract class OtpInfo implements Serializable {
default:
throw new OtpInfoException("unsupported otp type: " + type);
}
} catch (Base32Exception | JSONException e) {
} catch (EncodingException | JSONException e) {
throw new OtpInfoException(e);
}

@ -156,7 +156,7 @@ public class Dialogs {
showSecureDialog(dialog);
}
private static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener, boolean isSecret) {
private static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int messageId, @StringRes int hintId, TextInputListener listener, boolean isSecret) {
View view = LayoutInflater.from(context).inflate(R.layout.dialog_text_input, null);
EditText input = view.findViewById(R.id.text_input);
if (isSecret) {
@ -164,18 +164,25 @@ public class Dialogs {
}
input.setHint(hintId);
AlertDialog dialog = new AlertDialog.Builder(context)
AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle(titleId)
.setView(view)
.setPositiveButton(android.R.string.ok, (dialog1, which) -> {
char[] text = EditTextHelper.getEditTextChars(input);
listener.onTextInputResult(text);
})
.create();
char[] text = EditTextHelper.getEditTextChars(input);
listener.onTextInputResult(text);
});
if (messageId != 0) {
builder.setMessage(messageId);
}
AlertDialog dialog = builder.create();
showSecureDialog(dialog);
}
private static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener, boolean isSecret) {
showTextInputDialog(context, titleId, 0, hintId, listener, isSecret);
}
public static void showTextInputDialog(Context context, @StringRes int titleId, @StringRes int hintId, TextInputListener listener) {
showTextInputDialog(context, titleId, hintId, listener, false);
}
@ -184,6 +191,10 @@ public class Dialogs {
showTextInputDialog(context, R.string.set_password, R.string.password, listener, true);
}
public static void showPasswordInputDialog(Context context, @StringRes int messageId, TextInputListener listener) {
showTextInputDialog(context, R.string.set_password, messageId, R.string.password, listener, true);
}
public static void showNumberPickerDialog(Activity activity, NumberInputListener listener) {
View view = activity.getLayoutInflater().inflate(R.layout.dialog_number_picker, null);
NumberPicker numberPicker = view.findViewById(R.id.numberPicker);

@ -22,12 +22,18 @@ import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TableRow;
import androidx.annotation.ArrayRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import com.amulyakhare.textdrawable.TextDrawable;
import com.avito.android.krop.KropView;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.vault.VaultEntry;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.encoding.Base32Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.helpers.EditTextHelper;
import com.beemdevelopment.aegis.helpers.SpinnerHelper;
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
@ -36,11 +42,11 @@ import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.otp.SteamInfo;
import com.beemdevelopment.aegis.otp.TotpInfo;
import com.beemdevelopment.aegis.util.Cloner;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
import com.beemdevelopment.aegis.util.Cloner;
import java.io.ByteArrayOutputStream;
import java.text.Collator;
@ -49,11 +55,6 @@ import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import androidx.annotation.ArrayRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import de.hdodenhof.circleimageview.CircleImageView;
public class EditEntryActivity extends AegisActivity {
@ -163,8 +164,8 @@ public class EditEntryActivity extends AegisActivity {
byte[] secretBytes = _origEntry.getInfo().getSecret();
if (secretBytes != null) {
char[] secretChars = Base32.encode(secretBytes);
_textSecret.setText(secretChars, 0, secretChars.length);
String secretString = Base32.encode(secretBytes);
_textSecret.setText(secretString);
}
String type = _origEntry.getInfo().getType();
@ -458,8 +459,11 @@ public class EditEntryActivity extends AegisActivity {
byte[] secret;
try {
secret = Base32.decode(EditTextHelper.getEditTextChars(_textSecret, true));
} catch (Base32Exception e) {
secret = Base32.decode(new String(EditTextHelper.getEditTextChars(_textSecret, true)));
if (secret.length == 0) {
throw new ParseException("Secret cannot be empty");
}
} catch (EncodingException e) {
throw new ParseException("Secret is not valid base32.");
}

@ -108,7 +108,7 @@ public class ScannerActivity extends AegisActivity implements ZXingScannerView.R
public void handleResult(Result rawResult) {
try {
// parse google auth uri
GoogleAuthInfo info = GoogleAuthInfo.parseUri(rawResult.getText());
GoogleAuthInfo info = GoogleAuthInfo.parseUri(rawResult.getText().trim());
VaultEntry entry = new VaultEntry(info);
Intent intent = new Intent();

@ -1,6 +1,6 @@
package com.beemdevelopment.aegis.vault;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.OtpInfoException;
import com.beemdevelopment.aegis.util.UUIDMap;
@ -43,7 +43,7 @@ public class Vault {
VaultEntry entry = VaultEntry.fromJson(array.getJSONObject(i));
entries.add(entry);
}
} catch (Base64Exception | OtpInfoException | JSONException e) {
} catch (EncodingException | OtpInfoException | JSONException e) {
throw new VaultException(e);
}

@ -1,7 +1,7 @@
package com.beemdevelopment.aegis.vault;
import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.otp.OtpInfoException;
@ -60,7 +60,7 @@ public class VaultEntry extends UUIDMap.Value {
return obj;
}
public static VaultEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, Base64Exception {
public static VaultEntry fromJson(JSONObject obj) throws JSONException, OtpInfoException, EncodingException {
// if there is no uuid, generate a new one
UUID uuid;
if (!obj.has("uuid")) {

@ -3,11 +3,10 @@ package com.beemdevelopment.aegis.vault;
import com.beemdevelopment.aegis.crypto.CryptParameters;
import com.beemdevelopment.aegis.crypto.CryptResult;
import com.beemdevelopment.aegis.crypto.MasterKeyException;
import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.vault.slots.SlotList;
import com.beemdevelopment.aegis.vault.slots.SlotListException;
import com.beemdevelopment.aegis.encoding.Base64;
import com.beemdevelopment.aegis.encoding.Base64Exception;
import com.beemdevelopment.aegis.encoding.HexException;
import org.json.JSONException;
import org.json.JSONObject;
@ -95,7 +94,7 @@ public class VaultFile {
byte[] bytes = Base64.decode((String) _content);
CryptResult result = creds.decrypt(bytes, _header.getParams());
return new JSONObject(new String(result.getData(), StandardCharsets.UTF_8));
} catch (MasterKeyException | JSONException | Base64Exception e) {
} catch (MasterKeyException | JSONException | EncodingException e) {
throw new VaultFileException(e);
}
}
@ -136,7 +135,7 @@ public class VaultFile {
SlotList slots = SlotList.fromJson(obj.getJSONArray("slots"));
CryptParameters params = CryptParameters.fromJson(obj.getJSONObject("params"));
return new Header(slots, params);
} catch (SlotListException | JSONException | HexException e) {
} catch (SlotListException | JSONException | EncodingException e) {
throw new VaultFileException(e);
}
}

@ -3,13 +3,13 @@ package com.beemdevelopment.aegis.vault;
import android.content.Context;
import android.content.Intent;
import androidx.core.util.AtomicFile;
import com.beemdevelopment.aegis.services.NotificationService;
import org.json.JSONObject;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@ -41,12 +41,9 @@ public class VaultManager {
public void load() throws VaultManagerException {
assertState(true, false);
try (FileInputStream file = _context.openFileInput(FILENAME)) {
byte[] fileBytes = new byte[(int) file.getChannel().size()];
DataInputStream stream = new DataInputStream(file);
stream.readFully(fileBytes);
stream.close();
AtomicFile file = new AtomicFile(new File(_context.getFilesDir(), FILENAME));
try {
byte[] fileBytes = file.readFully();
_file = VaultFile.fromBytes(fileBytes);
_encrypt = _file.isEncrypted();
if (!isEncryptionEnabled()) {
@ -77,11 +74,19 @@ public class VaultManager {
}
}
public static void save(Context context, VaultFile file) throws VaultManagerException {
byte[] bytes = file.toBytes();
try (FileOutputStream stream = context.openFileOutput(FILENAME, Context.MODE_PRIVATE)) {
public static void save(Context context, VaultFile vaultFile) throws VaultManagerException {
byte[] bytes = vaultFile.toBytes();
AtomicFile file = new AtomicFile(new File(context.getFilesDir(), FILENAME));
FileOutputStream stream = null;
try {
stream = file.startWrite();
stream.write(bytes);
file.finishWrite(stream);
} catch (IOException e) {
if (stream != null) {
file.failWrite(stream);
}
throw new VaultManagerException(e);
}
}

@ -5,8 +5,8 @@ import com.beemdevelopment.aegis.crypto.CryptResult;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.MasterKey;
import com.beemdevelopment.aegis.crypto.SCryptParameters;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.encoding.HexException;
import com.beemdevelopment.aegis.util.UUIDMap;
import org.json.JSONException;
@ -144,7 +144,7 @@ public abstract class Slot extends UUIDMap.Value {
default:
throw new SlotException("unrecognized slot type");
}
} catch (JSONException | HexException e) {
} catch (JSONException | EncodingException e) {
throw new SlotException(e);
}

@ -66,7 +66,10 @@
<string name="set_password_confirm">Please confirm the password</string>
<string name="invalidated_biometrics">A change in your device\'s security settings has been detected. Please go to \"Aegis -> Settings -> Biometrics\" and re-enable biometric unlock.</string>
<string name="password_reminder">It\'s been a while since you\'ve entered your password. Do you still remember it?</string>
<string name="enter_password_authy_message">It looks like your Authy tokens are encrypted. Please close Aegis, open Authy and unlock the tokens with your password. Instead, Aegis can also attempt to decrypt your Authy tokens for you, if you enter your password below.</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="unlock">Unlock</string>
<string name="biometrics">Biometrics</string>
<string name="advanced">Advanced</string>
@ -122,6 +125,7 @@
<string name="andotp_new_format">New format (v0.6.3 or newer) </string>
<string name="andotp_old_format">Old format (v0.6.2 or older) </string>
<string name="choose_andotp_importer">Which format does the andOTP backup file have?</string>
<string name="choose_totpauth_importer">Is this TOTP Authenticator backup encrypted with a password?</string>
<string name="choose_application">Select the application you\'d like to import from</string>
<string name="choose_theme">Select your desired theme</string>
<string name="choose_view_mode">Select your desired view mode</string>
@ -175,7 +179,7 @@
<string name="selected">Selected</string>
<string name="dark_theme_title">Dark theme</string>
<string name="light_theme_title">Light theme</string>
<string name="amoled_theme_title">Amoled theme</string>
<string name="amoled_theme_title">AMOLED theme</string>
<string name="normal_viewmode_title">Normal</string>
<string name="compact_mode_title">Compact</string>
<string name="small_mode_title">Small</string>

@ -2,8 +2,8 @@ package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.crypto.CryptoUtils;
import com.beemdevelopment.aegis.crypto.SCryptParameters;
import com.beemdevelopment.aegis.encoding.EncodingException;
import com.beemdevelopment.aegis.encoding.Hex;
import com.beemdevelopment.aegis.encoding.HexException;
import org.junit.jupiter.api.Test;
@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.*;
public class SCryptTest {
@Test
public void testTrailingNullCollision() throws HexException {
public void testTrailingNullCollision() throws EncodingException {
byte[] salt = new byte[0];
SCryptParameters params = new SCryptParameters(
CryptoUtils.CRYPTO_SCRYPT_N,

@ -2,14 +2,14 @@ package com.beemdevelopment.aegis;
import com.beemdevelopment.aegis.crypto.otp.OTP;
import com.beemdevelopment.aegis.crypto.otp.TOTP;
import com.beemdevelopment.aegis.encoding.HexException;
import org.junit.jupiter.api.Test;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public class TOTPTest {
private static class Vector {
@ -66,7 +66,7 @@ public class TOTPTest {
};
@Test
public void vectorsMatch() throws NoSuchAlgorithmException, InvalidKeyException, HexException {
public void vectorsMatch() throws NoSuchAlgorithmException, InvalidKeyException {
for (Vector vector : _vectors) {
byte[] seed;

Loading…
Cancel
Save