Revert SharedCredentialStore.java to simple starting point (#771)

This commit is contained in:
Leendert de Borst
2025-04-10 11:56:33 +02:00
parent 4b828002ec
commit c87a1cc37a
6 changed files with 62 additions and 692 deletions

View File

@@ -1,25 +0,0 @@
package com.aliasvault;
public class Credential {
private String username;
private String password;
private String service;
public Credential(String username, String password, String service) {
this.username = username;
this.password = password;
this.service = service;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getService() {
return service;
}
}

View File

@@ -1,75 +0,0 @@
package com.aliasvault;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import java.util.List;
public class CredentialManagerModule extends ReactContextBaseJavaModule {
private static final String TAG = "CredentialManagerModule";
private final ReactApplicationContext reactContext;
public CredentialManagerModule(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
}
@Override
public String getName() {
return "CredentialManager";
}
@ReactMethod
public void addCredential(String username, String password, String service, Promise promise) {
try {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
Credential credential = new Credential(username, password, service);
store.addCredential(credential);
promise.resolve(true);
} catch (Exception e) {
Log.e(TAG, "Error adding credential", e);
promise.reject("ERR_ADD_CREDENTIAL", "Failed to add credential: " + e.getMessage(), e);
}
}
@ReactMethod
public void getCredentials(Promise promise) {
try {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
List<Credential> credentials = store.getAllCredentials();
WritableArray credentialsArray = Arguments.createArray();
for (Credential credential : credentials) {
WritableMap credentialMap = Arguments.createMap();
credentialMap.putString("username", credential.getUsername());
credentialMap.putString("password", credential.getPassword());
credentialMap.putString("service", credential.getService());
credentialsArray.pushMap(credentialMap);
}
promise.resolve(credentialsArray);
} catch (Exception e) {
Log.e(TAG, "Error getting credentials", e);
promise.reject("ERR_GET_CREDENTIALS", "Failed to get credentials: " + e.getMessage(), e);
}
}
@ReactMethod
public void clearCredentials(Promise promise) {
try {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
store.clearAllCredentials();
promise.resolve(true);
} catch (Exception e) {
Log.e(TAG, "Error clearing credentials", e);
promise.reject("ERR_CLEAR_CREDENTIALS", "Failed to clear credentials: " + e.getMessage(), e);
}
}
}

View File

@@ -1,24 +0,0 @@
package com.aliasvault;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CredentialManagerPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new CredentialManagerModule(reactContext));
return modules;
}
}

View File

@@ -1,220 +0,0 @@
package com.aliasvault;
import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
public class SharedCredentialStore {
private static final String TAG = "SharedCredentialStore";
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
private static final String ENCRYPTION_KEY_ALIAS = "aliasvault_encryption_key";
private static final String SHARED_PREFS_NAME = "net.aliasvault.autofill";
private static final String CREDENTIALS_KEY = "storedCredentials";
private static final String IV_SUFFIX = "_iv";
private static SharedCredentialStore instance;
private final Context appContext;
private SecretKey cachedEncryptionKey;
private SharedCredentialStore(Context context) {
this.appContext = context.getApplicationContext();
}
public static synchronized SharedCredentialStore getInstance(Context context) {
if (instance == null) {
instance = new SharedCredentialStore(context);
}
return instance;
}
private SecretKey getOrCreateEncryptionKey() throws Exception {
if (cachedEncryptionKey != null) {
return cachedEncryptionKey;
}
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
// Check if the key exists
if (keyStore.containsAlias(ENCRYPTION_KEY_ALIAS)) {
// Key exists, retrieve it
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(
ENCRYPTION_KEY_ALIAS, null);
cachedEncryptionKey = secretKeyEntry.getSecretKey();
return cachedEncryptionKey;
} else {
// Key doesn't exist, create it
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
ENCRYPTION_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
.build();
keyGenerator.init(keyGenParameterSpec);
cachedEncryptionKey = keyGenerator.generateKey();
return cachedEncryptionKey;
}
} catch (Exception e) {
Log.e(TAG, "Error getting or creating encryption key", e);
throw e;
}
}
private Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
}
private String encrypt(String data) throws Exception {
SecretKey key = getOrCreateEncryptionKey();
Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// Store IV in SharedPreferences
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(CREDENTIALS_KEY + IV_SUFFIX, Base64.encodeToString(iv, Base64.DEFAULT)).apply();
return Base64.encodeToString(encryptedBytes, Base64.DEFAULT);
}
private String decrypt(String encryptedData) throws Exception {
SecretKey key = getOrCreateEncryptionKey();
Cipher cipher = getCipher();
// Get IV from SharedPreferences
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
String ivString = prefs.getString(CREDENTIALS_KEY + IV_SUFFIX, null);
if (ivString == null) {
throw new Exception("IV not found for decryption");
}
byte[] iv = Base64.decode(ivString, Base64.DEFAULT);
byte[] encryptedBytes = Base64.decode(encryptedData, Base64.DEFAULT);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public List<Credential> getAllCredentials() throws Exception {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
String encryptedData = prefs.getString(CREDENTIALS_KEY, null);
if (encryptedData == null) {
return new ArrayList<>();
}
String decryptedData = decrypt(encryptedData);
return parseCredentialsFromJson(decryptedData);
}
public void addCredential(Credential credential) throws Exception {
List<Credential> credentials = getAllCredentials();
credentials.add(credential);
String jsonData = credentialsToJson(credentials);
String encryptedData = encrypt(jsonData);
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(CREDENTIALS_KEY, encryptedData).apply();
}
public void clearAllCredentials() {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit()
.remove(CREDENTIALS_KEY)
.remove(CREDENTIALS_KEY + IV_SUFFIX)
.apply();
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
if (keyStore.containsAlias(ENCRYPTION_KEY_ALIAS)) {
keyStore.deleteEntry(ENCRYPTION_KEY_ALIAS);
}
cachedEncryptionKey = null;
} catch (Exception e) {
Log.e(TAG, "Error clearing encryption key", e);
}
}
public void clearCache() {
cachedEncryptionKey = null;
}
private List<Credential> parseCredentialsFromJson(String json) throws JSONException {
List<Credential> credentials = new ArrayList<>();
JSONArray jsonArray = new JSONArray(json);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String username = jsonObject.getString("username");
String password = jsonObject.getString("password");
String service = jsonObject.getString("service");
credentials.add(new Credential(username, password, service));
}
return credentials;
}
private String credentialsToJson(List<Credential> credentials) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (Credential credential : credentials) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", credential.getUsername());
jsonObject.put("password", credential.getPassword());
jsonObject.put("service", credential.getService());
jsonArray.put(jsonObject);
}
return jsonArray.toString();
}
}

View File

@@ -57,7 +57,7 @@ public class CredentialManagerModule extends ReactContextBaseJavaModule {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
Credential credential = new Credential(username, password, service);
store.addCredentialWithBiometricAuth(activity, credential, new SharedCredentialStore.CryptoOperationCallback() {
store.saveCredential(activity, credential, new SharedCredentialStore.CryptoOperationCallback() {
@Override
public void onSuccess(String result) {
promise.resolve(true);
@@ -91,7 +91,7 @@ public class CredentialManagerModule extends ReactContextBaseJavaModule {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
store.getAllCredentialsWithBiometricAuth(activity, new SharedCredentialStore.CryptoOperationCallback() {
store.getAllCredentials(activity, new SharedCredentialStore.CryptoOperationCallback() {
@Override
public void onSuccess(String jsonString) {
try {
@@ -132,7 +132,7 @@ public class CredentialManagerModule extends ReactContextBaseJavaModule {
public void clearCredentials(final Promise promise) {
try {
SharedCredentialStore store = SharedCredentialStore.getInstance(reactContext);
store.clearAllCredentials();
store.clearAllData();
promise.resolve(true);
} catch (Exception e) {
Log.e(TAG, "Error clearing credentials", e);

View File

@@ -2,415 +2,129 @@ package net.aliasvault.app.credentialmanager;
import android.content.Context;
import android.content.SharedPreferences;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;
import android.util.Log;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
public class SharedCredentialStore {
private static final String TAG = "SharedCredentialStore";
private static final String ANDROID_KEYSTORE = "AndroidKeyStore";
private static final String ENCRYPTION_KEY_ALIAS = "aliasvault_encryption_key";
private static final String SHARED_PREFS_NAME = "net.aliasvault.autofill";
private static final String CREDENTIALS_KEY = "storedCredentials";
private static final String IV_SUFFIX = "_iv";
private static final String SHARED_PREFS_NAME = "net.aliasvault.credentials";
private static final String CREDENTIALS_KEY = "stored_credentials";
private static SharedCredentialStore instance;
private final Context appContext;
private SecretKey cachedEncryptionKey;
// Interface for crypto operations that need biometric auth
// Interface for operations that need callbacks
public interface CryptoOperationCallback {
void onSuccess(String result);
void onError(Exception e);
}
// Interface for crypto key operations that need biometric auth
public interface KeyOperationCallback {
void onKeyReady(SecretKey key);
void onError(Exception e);
}
private SharedCredentialStore(Context context) {
this.appContext = context.getApplicationContext();
}
public static synchronized SharedCredentialStore getInstance(Context context) {
if (instance == null) {
instance = new SharedCredentialStore(context);
}
return instance;
}
private SecretKey getOrCreateEncryptionKey() throws Exception {
if (cachedEncryptionKey != null) {
return cachedEncryptionKey;
}
throw new Exception("Biometric authentication required. Use getEncryptionKeyWithBiometricAuth instead.");
}
private Cipher getCipher() throws Exception {
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_GCM + "/"
+ KeyProperties.ENCRYPTION_PADDING_NONE);
}
/**
* Authenticate user with biometric and get encryption key.
* This method will prompt for authentication even if key is already cached.
* Save a credential to SharedPreferences
*/
public void getEncryptionKeyWithBiometricAuth(final FragmentActivity activity, final KeyOperationCallback callback) {
public void saveCredential(FragmentActivity activity, final Credential credential,
final CryptoOperationCallback callback) {
try {
// First check if the key exists in the keystore
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
SecretKey keyToUse;
boolean createNewKey = false;
if (keyStore.containsAlias(ENCRYPTION_KEY_ALIAS)) {
// Key exists, retrieve it but we'll need authentication to use it
KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(
ENCRYPTION_KEY_ALIAS, null);
keyToUse = secretKeyEntry.getSecretKey();
} else {
// Key doesn't exist, we'll create it after authentication
createNewKey = true;
keyToUse = null;
}
// We'll use the cipher to verify biometric auth works
final Cipher cipher = getCipher();
if (!createNewKey) {
// If we have a key, initialize the cipher with it to verify auth
try {
// Get IV from preferences if available for decryption mode
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
String ivString = prefs.getString(CREDENTIALS_KEY + IV_SUFFIX, null);
if (ivString != null) {
// We have IV, use decrypt mode
byte[] iv = Base64.decode(ivString, Base64.DEFAULT);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, keyToUse, gcmParameterSpec);
} else {
// No IV, use encrypt mode
cipher.init(Cipher.ENCRYPT_MODE, keyToUse);
}
} catch (Exception e) {
Log.e(TAG, "Error initializing cipher for auth check", e);
// If we fail to initialize (likely due to auth being required),
// we'll continue and let the biometric prompt handle it
}
}
final boolean finalCreateNewKey = createNewKey;
final SecretKey finalKeyToUse = keyToUse;
// Create biometric prompt
final Executor executor = Executors.newSingleThreadExecutor();
final BiometricPrompt.AuthenticationCallback authCallback = new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
try {
if (finalCreateNewKey) {
// Create a new key since it didn't exist
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYSTORE);
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(
ENCRYPTION_KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.setUserAuthenticationRequired(true)
// This is critical: set validation timeout to prevent requiring auth for each operation
.setUserAuthenticationValidityDurationSeconds(30)
.build();
keyGenerator.init(keyGenParameterSpec);
cachedEncryptionKey = keyGenerator.generateKey();
} else {
// Use existing key
cachedEncryptionKey = finalKeyToUse;
}
callback.onKeyReady(cachedEncryptionKey);
} catch (Exception e) {
callback.onError(e);
}
}
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
callback.onError(new Exception("Authentication error: " + errString));
}
@Override
public void onAuthenticationFailed() {
callback.onError(new Exception("Authentication failed"));
}
};
// Show biometric prompt on main thread
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
BiometricPrompt biometricPrompt = new BiometricPrompt(activity, executor, authCallback);
// Show biometric prompt
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate to Access Secure Storage")
.setSubtitle("Authentication is required to access your credentials")
.setNegativeButtonText("Cancel")
.build();
if (finalCreateNewKey) {
biometricPrompt.authenticate(promptInfo);
} else {
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
}
} catch (Exception e) {
callback.onError(e);
}
}
});
} catch (Exception e) {
callback.onError(e);
}
}
public void encryptWithBiometricAuth(final FragmentActivity activity, final String data, final CryptoOperationCallback callback) {
getEncryptionKeyWithBiometricAuth(activity, new KeyOperationCallback() {
@Override
public void onKeyReady(SecretKey key) {
try {
final Cipher cipher = getCipher();
cipher.init(Cipher.ENCRYPT_MODE, key);
// Perform encryption
byte[] iv = cipher.getIV();
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// Store IV in SharedPreferences
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(CREDENTIALS_KEY + IV_SUFFIX, Base64.encodeToString(iv, Base64.DEFAULT)).apply();
callback.onSuccess(Base64.encodeToString(encryptedBytes, Base64.DEFAULT));
} catch (Exception e) {
callback.onError(e);
}
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
}
public void decryptWithBiometricAuth(final FragmentActivity activity, final String encryptedData, final CryptoOperationCallback callback) {
try {
// Get IV from SharedPreferences
Log.d(TAG, "Saving credential for: " + credential.getService());
// Get current credentials from SharedPreferences
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
String ivString = prefs.getString(CREDENTIALS_KEY + IV_SUFFIX, null);
if (ivString == null) {
callback.onError(new Exception("IV not found for decryption"));
return;
}
final byte[] iv = Base64.decode(ivString, Base64.DEFAULT);
getEncryptionKeyWithBiometricAuth(activity, new KeyOperationCallback() {
@Override
public void onKeyReady(SecretKey key) {
try {
Cipher cipher = getCipher();
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
// Perform decryption
byte[] encryptedBytes = Base64.decode(encryptedData, Base64.DEFAULT);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8);
callback.onSuccess(decryptedData);
} catch (Exception e) {
callback.onError(e);
}
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
String storedCredentialsJson = prefs.getString(CREDENTIALS_KEY, "[]");
// Parse existing credentials
List<Credential> credentials = parseCredentialsFromJson(storedCredentialsJson);
// Add new credential
credentials.add(credential);
// Save updated credentials
String updatedJsonData = credentialsToJson(credentials);
prefs.edit().putString(CREDENTIALS_KEY, updatedJsonData).apply();
callback.onSuccess("Credential saved successfully");
} catch (Exception e) {
callback.onError(e);
}
}
public void getAllCredentialsWithBiometricAuth(final FragmentActivity activity, final CryptoOperationCallback callback) {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
final String encryptedData = prefs.getString(CREDENTIALS_KEY, null);
if (encryptedData == null) {
try {
callback.onSuccess(new JSONArray().toString());
} catch (Exception e) {
callback.onError(e);
}
return;
}
decryptWithBiometricAuth(activity, encryptedData, new CryptoOperationCallback() {
@Override
public void onSuccess(String decryptedData) {
callback.onSuccess(decryptedData);
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
}
public void addCredentialWithBiometricAuth(final FragmentActivity activity, final Credential credential, final CryptoOperationCallback callback) {
getAllCredentialsWithBiometricAuth(activity, new CryptoOperationCallback() {
@Override
public void onSuccess(String result) {
try {
List<Credential> credentials = parseCredentialsFromJson(result);
credentials.add(credential);
String jsonData = credentialsToJson(credentials);
encryptWithBiometricAuth(activity, jsonData, new CryptoOperationCallback() {
@Override
public void onSuccess(String encryptedData) {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(CREDENTIALS_KEY, encryptedData).apply();
callback.onSuccess("Credential added successfully");
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
} catch (Exception e) {
callback.onError(e);
}
}
@Override
public void onError(Exception e) {
// If there's an error getting credentials (might be first use), create a new list
try {
List<Credential> credentials = new ArrayList<>();
credentials.add(credential);
String jsonData = credentialsToJson(credentials);
encryptWithBiometricAuth(activity, jsonData, new CryptoOperationCallback() {
@Override
public void onSuccess(String encryptedData) {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(CREDENTIALS_KEY, encryptedData).apply();
callback.onSuccess("Credential added successfully");
}
@Override
public void onError(Exception e) {
callback.onError(e);
}
});
} catch (Exception ex) {
callback.onError(ex);
}
}
});
}
public void clearAllCredentials() {
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit()
.remove(CREDENTIALS_KEY)
.remove(CREDENTIALS_KEY + IV_SUFFIX)
.apply();
/**
* Get all credentials from SharedPreferences
*/
public void getAllCredentials(FragmentActivity activity, final CryptoOperationCallback callback) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEYSTORE);
keyStore.load(null);
if (keyStore.containsAlias(ENCRYPTION_KEY_ALIAS)) {
keyStore.deleteEntry(ENCRYPTION_KEY_ALIAS);
}
cachedEncryptionKey = null;
Log.d(TAG, "Retrieving credentials from SharedPreferences");
// Get credentials from SharedPreferences
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
String storedCredentialsJson = prefs.getString(CREDENTIALS_KEY, "[]");
callback.onSuccess(storedCredentialsJson);
} catch (Exception e) {
Log.e(TAG, "Error clearing encryption key", e);
callback.onError(e);
}
}
public void clearCache() {
cachedEncryptionKey = null;
/**
* Clear all credentials from SharedPreferences
*/
public void clearAllData() {
Log.d(TAG, "Clearing all credentials from SharedPreferences");
SharedPreferences prefs = appContext.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().remove(CREDENTIALS_KEY).apply();
}
private List<Credential> parseCredentialsFromJson(String json) throws JSONException {
List<Credential> credentials = new ArrayList<>();
if (json == null || json.isEmpty()) {
return credentials;
}
JSONArray jsonArray = new JSONArray(json);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
String username = jsonObject.getString("username");
String password = jsonObject.getString("password");
String service = jsonObject.getString("service");
credentials.add(new Credential(username, password, service));
}
return credentials;
}
private String credentialsToJson(List<Credential> credentials) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (Credential credential : credentials) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("username", credential.getUsername());
jsonObject.put("password", credential.getPassword());
jsonObject.put("service", credential.getService());
jsonArray.put(jsonObject);
}
return jsonArray.toString();
}
}
}