key storage spotless
This commit is contained in:
+6
-4
@@ -63,8 +63,9 @@ class AccountManager(
|
|||||||
// and use SecureKeyStorage to retrieve the private key
|
// and use SecureKeyStorage to retrieve the private key
|
||||||
val lastNpub = getLastNpub() ?: return Result.failure(Exception("No saved account"))
|
val lastNpub = getLastNpub() ?: return Result.failure(Exception("No saved account"))
|
||||||
|
|
||||||
val privKeyHex = secureStorage.getPrivateKey(lastNpub)
|
val privKeyHex =
|
||||||
?: return Result.failure(Exception("Private key not found for $lastNpub"))
|
secureStorage.getPrivateKey(lastNpub)
|
||||||
|
?: return Result.failure(Exception("Private key not found for $lastNpub"))
|
||||||
|
|
||||||
val keyPair = KeyPair(privKey = privKeyHex.hexToByteArray())
|
val keyPair = KeyPair(privKey = privKeyHex.hexToByteArray())
|
||||||
val signer = NostrSignerInternal(keyPair)
|
val signer = NostrSignerInternal(keyPair)
|
||||||
@@ -94,8 +95,9 @@ class AccountManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val privKeyHex = decodePrivateKeyAsHexOrNull(current.nsec)
|
val privKeyHex =
|
||||||
?: return Result.failure(Exception("Invalid nsec format"))
|
decodePrivateKeyAsHexOrNull(current.nsec)
|
||||||
|
?: return Result.failure(Exception("Invalid nsec format"))
|
||||||
|
|
||||||
secureStorage.savePrivateKey(current.npub, privKeyHex)
|
secureStorage.savePrivateKey(current.npub, privKeyHex)
|
||||||
saveLastNpub(current.npub)
|
saveLastNpub(current.npub)
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ fun ProfileScreen(
|
|||||||
accountManager: AccountManager,
|
accountManager: AccountManager,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
"Profile",
|
"Profile",
|
||||||
|
|||||||
+9
-3
@@ -30,14 +30,17 @@ import kotlinx.coroutines.withContext
|
|||||||
* Android implementation of SecureKeyStorage using EncryptedSharedPreferences
|
* Android implementation of SecureKeyStorage using EncryptedSharedPreferences
|
||||||
* backed by Android Keystore (AES-256-GCM, hardware-backed when available).
|
* backed by Android Keystore (AES-256-GCM, hardware-backed when available).
|
||||||
*/
|
*/
|
||||||
actual class SecureKeyStorage(private val context: Context) {
|
actual class SecureKeyStorage(
|
||||||
|
private val context: Context,
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
private const val PREFS_NAME = "amethyst_secure_keys"
|
private const val PREFS_NAME = "amethyst_secure_keys"
|
||||||
private const val KEY_PREFIX = "privkey_"
|
private const val KEY_PREFIX = "privkey_"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val masterKey: MasterKey by lazy {
|
private val masterKey: MasterKey by lazy {
|
||||||
MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
|
MasterKey
|
||||||
|
.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
|
||||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,10 @@ actual class SecureKeyStorage(private val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
actual suspend fun savePrivateKey(npub: String, privKeyHex: String) {
|
actual suspend fun savePrivateKey(
|
||||||
|
npub: String,
|
||||||
|
privKeyHex: String,
|
||||||
|
) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
encryptedPrefs.edit().putString(KEY_PREFIX + npub, privKeyHex).apply()
|
encryptedPrefs.edit().putString(KEY_PREFIX + npub, privKeyHex).apply()
|
||||||
|
|||||||
+8
-2
@@ -34,7 +34,10 @@ expect class SecureKeyStorage {
|
|||||||
* @param privKeyHex The private key in hexadecimal format
|
* @param privKeyHex The private key in hexadecimal format
|
||||||
* @throws SecureStorageException if storage operation fails
|
* @throws SecureStorageException if storage operation fails
|
||||||
*/
|
*/
|
||||||
suspend fun savePrivateKey(npub: String, privKeyHex: String)
|
suspend fun savePrivateKey(
|
||||||
|
npub: String,
|
||||||
|
privKeyHex: String,
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a private key for the given npub.
|
* Retrieves a private key for the given npub.
|
||||||
@@ -66,4 +69,7 @@ expect class SecureKeyStorage {
|
|||||||
/**
|
/**
|
||||||
* Exception thrown when secure storage operations fail.
|
* Exception thrown when secure storage operations fail.
|
||||||
*/
|
*/
|
||||||
class SecureStorageException(message: String, cause: Throwable? = null) : Exception(message, cause)
|
class SecureStorageException(
|
||||||
|
message: String,
|
||||||
|
cause: Throwable? = null,
|
||||||
|
) : Exception(message, cause)
|
||||||
|
|||||||
+24
-10
@@ -58,7 +58,10 @@ actual class SecureKeyStorage {
|
|||||||
private var keyringAvailable: Boolean = true
|
private var keyringAvailable: Boolean = true
|
||||||
private var fallbackPassword: String? = null
|
private var fallbackPassword: String? = null
|
||||||
|
|
||||||
actual suspend fun savePrivateKey(npub: String, privKeyHex: String) {
|
actual suspend fun savePrivateKey(
|
||||||
|
npub: String,
|
||||||
|
privKeyHex: String,
|
||||||
|
) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
if (keyringAvailable) {
|
if (keyringAvailable) {
|
||||||
@@ -111,11 +114,13 @@ actual class SecureKeyStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual suspend fun hasPrivateKey(npub: String): Boolean =
|
actual suspend fun hasPrivateKey(npub: String): Boolean = getPrivateKey(npub) != null
|
||||||
getPrivateKey(npub) != null
|
|
||||||
|
|
||||||
// Keyring-based storage
|
// Keyring-based storage
|
||||||
private fun saveToKeyring(npub: String, privKeyHex: String) {
|
private fun saveToKeyring(
|
||||||
|
npub: String,
|
||||||
|
privKeyHex: String,
|
||||||
|
) {
|
||||||
val keyring = Keyring.create()
|
val keyring = Keyring.create()
|
||||||
keyring.setPassword(SERVICE_NAME, npub, privKeyHex)
|
keyring.setPassword(SERVICE_NAME, npub, privKeyHex)
|
||||||
}
|
}
|
||||||
@@ -138,7 +143,10 @@ actual class SecureKeyStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback encrypted file storage
|
// Fallback encrypted file storage
|
||||||
private fun saveToFallback(npub: String, privKeyHex: String) {
|
private fun saveToFallback(
|
||||||
|
npub: String,
|
||||||
|
privKeyHex: String,
|
||||||
|
) {
|
||||||
val password = getFallbackPassword()
|
val password = getFallbackPassword()
|
||||||
val encrypted = encryptData(privKeyHex, password)
|
val encrypted = encryptData(privKeyHex, password)
|
||||||
|
|
||||||
@@ -184,12 +192,12 @@ actual class SecureKeyStorage {
|
|||||||
val fallbackFile = getFallbackFile()
|
val fallbackFile = getFallbackFile()
|
||||||
if (!fallbackFile.exists()) return emptyMap()
|
if (!fallbackFile.exists()) return emptyMap()
|
||||||
|
|
||||||
return fallbackFile.readLines()
|
return fallbackFile
|
||||||
|
.readLines()
|
||||||
.mapNotNull { line ->
|
.mapNotNull { line ->
|
||||||
val parts = line.split(":", limit = 2)
|
val parts = line.split(":", limit = 2)
|
||||||
if (parts.size == 2) parts[0] to parts[1] else null
|
if (parts.size == 2) parts[0] to parts[1] else null
|
||||||
}
|
}.toMap()
|
||||||
.toMap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFallbackFile(): File {
|
private fun getFallbackFile(): File {
|
||||||
@@ -206,7 +214,10 @@ actual class SecureKeyStorage {
|
|||||||
return fallbackPassword!!
|
return fallbackPassword!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encryptData(plaintext: String, password: String): String {
|
private fun encryptData(
|
||||||
|
plaintext: String,
|
||||||
|
password: String,
|
||||||
|
): String {
|
||||||
val salt = ByteArray(16).apply { SecureRandom().nextBytes(this) }
|
val salt = ByteArray(16).apply { SecureRandom().nextBytes(this) }
|
||||||
val iv = ByteArray(IV_LENGTH).apply { SecureRandom().nextBytes(this) }
|
val iv = ByteArray(IV_LENGTH).apply { SecureRandom().nextBytes(this) }
|
||||||
|
|
||||||
@@ -222,7 +233,10 @@ actual class SecureKeyStorage {
|
|||||||
return Base64.getEncoder().encodeToString(combined)
|
return Base64.getEncoder().encodeToString(combined)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptData(ciphertext: String, password: String): String {
|
private fun decryptData(
|
||||||
|
ciphertext: String,
|
||||||
|
password: String,
|
||||||
|
): String {
|
||||||
val combined = Base64.getDecoder().decode(ciphertext)
|
val combined = Base64.getDecoder().decode(ciphertext)
|
||||||
|
|
||||||
val salt = combined.copyOfRange(0, 16)
|
val salt = combined.copyOfRange(0, 16)
|
||||||
|
|||||||
Reference in New Issue
Block a user