Implement backups (WIP), Move add OTP to menu
This commit is contained in:
parent
ff50e68e90
commit
2045326d04
@ -144,9 +144,9 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
//binding.fabMenu.setOnClickListener(view -> NavigationUtil.navigate(this, MenuFragment.class, null)); TODO: remove old menu
|
||||
binding.fabMenu.setOnClickListener(view -> NavigationUtil.openMenu(this, null));
|
||||
binding.fabScan.setOnClickListener(view -> scanCode());
|
||||
binding.fabScanImage.setOnClickListener(view -> scanCodeFromImage());
|
||||
binding.fabInput.setOnClickListener(view -> inputCode());
|
||||
binding.fabScan.setOnClickListener(view -> scanCode(null));
|
||||
binding.fabScanImage.setOnClickListener(view -> scanCodeFromImage(null));
|
||||
binding.fabInput.setOnClickListener(view -> inputCode(null));
|
||||
|
||||
Fragment fragment = NavigationUtil.getCurrentFragment(this);
|
||||
if(fragment instanceof NamedFragment) {
|
||||
@ -210,18 +210,18 @@ public class MainActivity extends BaseActivity {
|
||||
NavigationUtil.navigate(this, AboutFragment.class, null);
|
||||
}
|
||||
|
||||
public void scanCode() {
|
||||
public void scanCode(MenuItem item) {
|
||||
lockOnPause = false;
|
||||
startQRCodeScan.launch(null);
|
||||
}
|
||||
|
||||
public void scanCodeFromImage() {
|
||||
public void scanCodeFromImage(MenuItem item) {
|
||||
pickQRCodeImage.launch(new PickVisualMediaRequest.Builder()
|
||||
.setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE)
|
||||
.build());
|
||||
}
|
||||
|
||||
public void inputCode() {
|
||||
public void inputCode(MenuItem item) {
|
||||
DialogInputCodeChoiceBinding binding = DialogInputCodeChoiceBinding.inflate(getLayoutInflater());
|
||||
|
||||
String[] options = new String[2];
|
||||
|
@ -63,7 +63,7 @@ public class GroupFragment extends NamedFragment {
|
||||
|
||||
groupID = requireArguments().getString(GroupFragment.BUNDLE_GROUP);
|
||||
|
||||
FabUtil.showFabs(requireActivity());
|
||||
//FabUtil.showFabs(requireActivity());
|
||||
|
||||
otpListAdapter = new OTPListAdapter(requireContext(), binding.itemList);
|
||||
binding.itemList.setAdapter(otpListAdapter);
|
||||
|
@ -9,12 +9,16 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator.R;
|
||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||
import com.cringe_studios.cringe_authenticator.databinding.FragmentMenuDrawerBinding;
|
||||
import com.cringe_studios.cringe_authenticator.grouplist.GroupListAdapter;
|
||||
import com.cringe_studios.cringe_authenticator.grouplist.GroupListItem;
|
||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||
import com.cringe_studios.cringe_authenticator.util.DialogUtil;
|
||||
import com.cringe_studios.cringe_authenticator.util.FabUtil;
|
||||
import com.cringe_studios.cringe_authenticator.util.NavigationUtil;
|
||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabase;
|
||||
import com.cringe_studios.cringe_authenticator.util.OTPDatabaseException;
|
||||
import com.cringe_studios.cringe_authenticator.util.SettingsUtil;
|
||||
import com.cringe_studios.cringe_authenticator.util.StyledDialogBuilder;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
@ -104,8 +108,16 @@ public class MenuDrawerFragment extends BottomSheetDialogFragment {
|
||||
}
|
||||
|
||||
public void removeGroup(String group) {
|
||||
OTPDatabase.promptLoadDatabase(requireActivity(), () -> {
|
||||
try {
|
||||
OTPDatabase.getLoadedDatabase().removeOTPs(group);
|
||||
OTPDatabase.saveDatabase(requireContext(), SettingsUtil.getCryptoParameters(requireContext()));
|
||||
SettingsUtil.removeGroup(requireContext(), group);
|
||||
groupListAdapter.remove(group);
|
||||
} catch (OTPDatabaseException | CryptoException e) {
|
||||
DialogUtil.showErrorDialog(requireContext(), e.toString());
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
|
||||
public void renameGroup(String group, String newName) {
|
||||
|
@ -0,0 +1,18 @@
|
||||
package com.cringe_studios.cringe_authenticator.util;
|
||||
|
||||
public class BackupException extends Exception {
|
||||
public BackupException() {
|
||||
}
|
||||
|
||||
public BackupException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public BackupException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public BackupException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.cringe_studios.cringe_authenticator.util;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import com.cringe_studios.cringe_authenticator.crypto.Crypto;
|
||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoException;
|
||||
import com.cringe_studios.cringe_authenticator.crypto.CryptoParameters;
|
||||
import com.cringe_studios.cringe_authenticator.model.OTPData;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import org.bouncycastle.jcajce.provider.symmetric.ARC4;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class BackupUtil {
|
||||
|
||||
public static void saveBackup(File backupFile, SecretKey key, CryptoParameters parameters) throws BackupException, CryptoException {
|
||||
if(!OTPDatabase.isDatabaseLoaded()) throw new BackupException("Database is not loaded");
|
||||
|
||||
if(!backupFile.exists()) {
|
||||
File parent = backupFile.getParentFile();
|
||||
if(parent != null && !parent.exists()) parent.mkdirs();
|
||||
try {
|
||||
backupFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw new BackupException(e);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] dbBytes = OTPDatabase.convertToEncryptedBytes(OTPDatabase.getLoadedDatabase(), key, parameters);
|
||||
JsonObject object = new JsonObject();
|
||||
object.add("parameters", SettingsUtil.GSON.toJsonTree(parameters));
|
||||
object.addProperty("database", Base64.encodeToString(dbBytes, Base64.DEFAULT));
|
||||
try(FileOutputStream fOut = new FileOutputStream(backupFile)) {
|
||||
fOut.write(object.toString().getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
throw new BackupException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static CryptoParameters loadParametersFromBackup(File backupFile) throws BackupException {
|
||||
try {
|
||||
byte[] backupBytes = IOUtil.readBytes(backupFile);
|
||||
JsonObject object = SettingsUtil.GSON.fromJson(new String(backupBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
return SettingsUtil.GSON.fromJson(object.get("parameters"), CryptoParameters.class);
|
||||
} catch (IOException e) {
|
||||
throw new BackupException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static OTPDatabase loadBackup(File backupFile, SecretKey key, CryptoParameters parameters) throws BackupException, OTPDatabaseException, CryptoException {
|
||||
try {
|
||||
byte[] backupBytes = IOUtil.readBytes(backupFile);
|
||||
JsonObject object = SettingsUtil.GSON.fromJson(new String(backupBytes, StandardCharsets.UTF_8), JsonObject.class);
|
||||
return OTPDatabase.loadFromEncryptedBytes(Base64.decode(object.get("database").getAsString(), Base64.DEFAULT), key, parameters);
|
||||
} catch (IOException e) {
|
||||
throw new BackupException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.cringe_studios.cringe_authenticator.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public class IOUtil {
|
||||
|
||||
public static byte[] readBytes(File file) throws IOException {
|
||||
try(FileInputStream fIn = new FileInputStream(file)) {
|
||||
ByteBuffer fileBuffer = ByteBuffer.allocate((int) file.length());
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = fIn.read(buffer)) > 0) {
|
||||
fileBuffer.put(buffer, 0, len);
|
||||
}
|
||||
|
||||
return fileBuffer.array();
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeBytes(File file, byte[] bytes) throws IOException {
|
||||
try(FileOutputStream fOut = new FileOutputStream(file)) {
|
||||
fOut.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -102,13 +102,7 @@ public class OTPDatabase {
|
||||
fileBuffer.put(buffer, 0, len);
|
||||
}
|
||||
|
||||
if(key == null) {
|
||||
loadedDatabase = loadFromBytes(fileBuffer.array());
|
||||
loadedKey = null;
|
||||
return loadedDatabase;
|
||||
}
|
||||
|
||||
loadedDatabase = loadFromBytes(Crypto.decrypt(SettingsUtil.getCryptoParameters(context), fileBuffer.array(), key));
|
||||
loadedDatabase = loadFromEncryptedBytes(fileBuffer.array(), key, SettingsUtil.getCryptoParameters(context));
|
||||
loadedKey = key;
|
||||
return loadedDatabase;
|
||||
}catch(IOException e) {
|
||||
@ -134,6 +128,14 @@ public class OTPDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public static OTPDatabase loadFromEncryptedBytes(byte[] bytes, SecretKey key, CryptoParameters parameters) throws CryptoException, OTPDatabaseException {
|
||||
if(key != null) {
|
||||
bytes = Crypto.decrypt(parameters, bytes, key);
|
||||
}
|
||||
|
||||
return loadFromBytes(bytes);
|
||||
}
|
||||
|
||||
private static byte[] convertToBytes(OTPDatabase db) {
|
||||
JsonObject object = new JsonObject();
|
||||
for(Map.Entry<String, List<OTPData>> en : db.otps.entrySet()) {
|
||||
@ -142,16 +144,22 @@ public class OTPDatabase {
|
||||
return object.toString().getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static void saveDatabase(Context ctx, CryptoParameters parameters) throws OTPDatabaseException, CryptoException {
|
||||
if(!isDatabaseLoaded()) throw new IllegalStateException("Database is not loaded");
|
||||
File file = new File(ctx.getFilesDir(), DB_FILE_NAME);
|
||||
|
||||
public static byte[] convertToEncryptedBytes(OTPDatabase db, SecretKey key, CryptoParameters parameters) throws CryptoException {
|
||||
byte[] dbBytes = convertToBytes(loadedDatabase);
|
||||
|
||||
if(loadedKey != null) {
|
||||
dbBytes = Crypto.encrypt(parameters, dbBytes, loadedKey);
|
||||
if(key != null) {
|
||||
dbBytes = Crypto.encrypt(parameters, dbBytes, key);
|
||||
}
|
||||
|
||||
return dbBytes;
|
||||
}
|
||||
|
||||
public static void saveDatabase(Context ctx, CryptoParameters parameters) throws OTPDatabaseException, CryptoException {
|
||||
if(!isDatabaseLoaded()) throw new OTPDatabaseException("Database is not loaded");
|
||||
File file = new File(ctx.getFilesDir(), DB_FILE_NAME);
|
||||
|
||||
byte[] dbBytes = convertToEncryptedBytes(loadedDatabase, loadedKey, parameters);
|
||||
|
||||
try(FileOutputStream fOut = new FileOutputStream(file)) {
|
||||
fOut.write(dbBytes);
|
||||
} catch (IOException e) {
|
||||
@ -172,13 +180,13 @@ public class OTPDatabase {
|
||||
}
|
||||
|
||||
public static void encrypt(Context ctx, SecretKey key, CryptoParameters parameters) throws OTPDatabaseException, CryptoException {
|
||||
if(!isDatabaseLoaded()) throw new IllegalStateException("Database is not loaded");
|
||||
if(!isDatabaseLoaded()) throw new OTPDatabaseException("Database is not loaded");
|
||||
loadedKey = key;
|
||||
saveDatabase(ctx, parameters);
|
||||
}
|
||||
|
||||
public static void decrypt(Context ctx) throws OTPDatabaseException, CryptoException {
|
||||
if(!isDatabaseLoaded()) throw new IllegalStateException("Database is not loaded");
|
||||
if(!isDatabaseLoaded()) throw new OTPDatabaseException("Database is not loaded");
|
||||
loadedKey = null;
|
||||
saveDatabase(ctx, null);
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/cringeauth_white"
|
||||
app:tint="?android:attr/textColor" />
|
||||
app:srcCompat="@drawable/cringeauth_white" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -25,8 +25,7 @@
|
||||
android:paddingLeft="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:scaleType="centerInside"
|
||||
app:srcCompat="@drawable/cringeauth_white"
|
||||
tools:srcCompat="@drawable/cringeauth_white" />
|
||||
app:srcCompat="@drawable/cringeauth_white" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
|
@ -2,6 +2,12 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.cringe_studios.cringe_authenticator.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_lock"
|
||||
android:orderInCategory="100"
|
||||
android:title="Lock"
|
||||
app:showAsAction="never"
|
||||
android:onClick="lockApp" />
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
@ -14,10 +20,4 @@
|
||||
android:title="@string/action_about"
|
||||
app:showAsAction="never"
|
||||
android:onClick="openAbout" />
|
||||
<item
|
||||
android:id="@+id/action_lock"
|
||||
android:orderInCategory="100"
|
||||
android:title="Lock"
|
||||
app:showAsAction="never"
|
||||
android:onClick="lockApp" />
|
||||
</menu>
|
@ -6,8 +6,31 @@
|
||||
android:orderInCategory="100"
|
||||
android:icon="@drawable/baseline_add_24"
|
||||
android:title="@string/action_new_group"
|
||||
android:onClick="addOTP"
|
||||
app:showAsAction="ifRoom" />
|
||||
app:showAsAction="ifRoom">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/action_otp_input"
|
||||
android:orderInCategory="100"
|
||||
android:icon="@drawable/baseline_edit_24"
|
||||
android:title="Input manually"
|
||||
android:onClick="inputCode"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/action_otp_scan"
|
||||
android:orderInCategory="100"
|
||||
android:icon="@drawable/baseline_qr_code_scanner_24"
|
||||
android:title="Scan QR code"
|
||||
android:onClick="scanCode"
|
||||
app:showAsAction="never"/>
|
||||
<item
|
||||
android:id="@+id/action_otp_scan_image"
|
||||
android:orderInCategory="100"
|
||||
android:icon="@drawable/baseline_compare_24"
|
||||
android:title="Scan image"
|
||||
android:onClick="scanCodeFromImage"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
|
Loading…
Reference in New Issue
Block a user