From df9d648988bbe91ef89d586c856933b39cc63aad Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Tue, 6 Jan 2026 06:50:31 +0200 Subject: [PATCH] key storage spotless --- .../commons/account/AccountManager.kt | 10 +++--- .../vitorpamplona/amethyst/desktop/Main.kt | 2 +- .../nip01Core/crypto/SecureKeyStorage.kt | 12 +++++-- .../nip01Core/crypto/SecureKeyStorage.kt | 10 ++++-- .../nip01Core/crypto/SecureKeyStorage.kt | 34 +++++++++++++------ 5 files changed, 48 insertions(+), 20 deletions(-) diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/account/AccountManager.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/account/AccountManager.kt index 5e334b976..1b9321077 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/account/AccountManager.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/account/AccountManager.kt @@ -63,8 +63,9 @@ class AccountManager( // and use SecureKeyStorage to retrieve the private key val lastNpub = getLastNpub() ?: return Result.failure(Exception("No saved account")) - val privKeyHex = secureStorage.getPrivateKey(lastNpub) - ?: return Result.failure(Exception("Private key not found for $lastNpub")) + val privKeyHex = + secureStorage.getPrivateKey(lastNpub) + ?: return Result.failure(Exception("Private key not found for $lastNpub")) val keyPair = KeyPair(privKey = privKeyHex.hexToByteArray()) val signer = NostrSignerInternal(keyPair) @@ -94,8 +95,9 @@ class AccountManager( } return try { - val privKeyHex = decodePrivateKeyAsHexOrNull(current.nsec) - ?: return Result.failure(Exception("Invalid nsec format")) + val privKeyHex = + decodePrivateKeyAsHexOrNull(current.nsec) + ?: return Result.failure(Exception("Invalid nsec format")) secureStorage.savePrivateKey(current.npub, privKeyHex) saveLastNpub(current.npub) diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt index 2954046a1..c442390d3 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/Main.kt @@ -390,7 +390,7 @@ fun ProfileScreen( accountManager: AccountManager, ) { val scope = rememberCoroutineScope() - + Column { Text( "Profile", diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt index 2f76f72e6..f6a8754aa 100644 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt @@ -30,14 +30,17 @@ import kotlinx.coroutines.withContext * Android implementation of SecureKeyStorage using EncryptedSharedPreferences * 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 { private const val PREFS_NAME = "amethyst_secure_keys" private const val KEY_PREFIX = "privkey_" } 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) .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) { try { encryptedPrefs.edit().putString(KEY_PREFIX + npub, privKeyHex).apply() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt index 188dd07c0..b7cd8018b 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt @@ -34,7 +34,10 @@ expect class SecureKeyStorage { * @param privKeyHex The private key in hexadecimal format * @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. @@ -66,4 +69,7 @@ expect class SecureKeyStorage { /** * 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) diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt index b0dc7e6d1..a8a7537f9 100644 --- a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/SecureKeyStorage.kt @@ -58,7 +58,10 @@ actual class SecureKeyStorage { private var keyringAvailable: Boolean = true private var fallbackPassword: String? = null - actual suspend fun savePrivateKey(npub: String, privKeyHex: String) { + actual suspend fun savePrivateKey( + npub: String, + privKeyHex: String, + ) { withContext(Dispatchers.IO) { try { if (keyringAvailable) { @@ -111,11 +114,13 @@ actual class SecureKeyStorage { } } - actual suspend fun hasPrivateKey(npub: String): Boolean = - getPrivateKey(npub) != null + actual suspend fun hasPrivateKey(npub: String): Boolean = getPrivateKey(npub) != null // Keyring-based storage - private fun saveToKeyring(npub: String, privKeyHex: String) { + private fun saveToKeyring( + npub: String, + privKeyHex: String, + ) { val keyring = Keyring.create() keyring.setPassword(SERVICE_NAME, npub, privKeyHex) } @@ -138,7 +143,10 @@ actual class SecureKeyStorage { } // Fallback encrypted file storage - private fun saveToFallback(npub: String, privKeyHex: String) { + private fun saveToFallback( + npub: String, + privKeyHex: String, + ) { val password = getFallbackPassword() val encrypted = encryptData(privKeyHex, password) @@ -184,12 +192,12 @@ actual class SecureKeyStorage { val fallbackFile = getFallbackFile() if (!fallbackFile.exists()) return emptyMap() - return fallbackFile.readLines() + return fallbackFile + .readLines() .mapNotNull { line -> val parts = line.split(":", limit = 2) if (parts.size == 2) parts[0] to parts[1] else null - } - .toMap() + }.toMap() } private fun getFallbackFile(): File { @@ -206,7 +214,10 @@ actual class SecureKeyStorage { 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 iv = ByteArray(IV_LENGTH).apply { SecureRandom().nextBytes(this) } @@ -222,7 +233,10 @@ actual class SecureKeyStorage { 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 salt = combined.copyOfRange(0, 16)