diff --git a/app/src/main/java/app/passwordstore/data/crypto/GPGPassphraseCache.kt b/app/src/main/java/app/passwordstore/data/crypto/GPGPassphraseCache.kt new file mode 100644 index 00000000..d9e66f74 --- /dev/null +++ b/app/src/main/java/app/passwordstore/data/crypto/GPGPassphraseCache.kt @@ -0,0 +1,67 @@ +package app.passwordstore.data.crypto + +import android.content.Context +import androidx.core.content.edit +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import app.passwordstore.crypto.GpgIdentifier +import app.passwordstore.util.coroutines.DispatcherProvider +import app.passwordstore.util.extensions.getString +import javax.inject.Inject +import kotlinx.coroutines.withContext + +/** Implements a rudimentary [EncryptedSharedPreferences]-backed cache for GPG passphrases. */ +@Suppress("Unused") // Soon +class GPGPassphraseCache +@Inject +constructor( + private val dispatcherProvider: DispatcherProvider, +) { + + private suspend fun cachePassphrase( + context: Context, + identifier: GpgIdentifier, + passphrase: String, + ) { + withContext(dispatcherProvider.io()) { + getPreferences(context).edit { putString(identifier.toString(), passphrase) } + } + } + + private suspend fun retrieveCachedPassphrase( + context: Context, + identifier: GpgIdentifier, + ): String? { + return withContext(dispatcherProvider.io()) { + getPreferences(context).getString(identifier.toString()) + } + } + + private suspend fun getPreferences(context: Context) = + withContext(dispatcherProvider.io()) { + EncryptedSharedPreferences.create( + context, + ANDROIDX_SECURITY_KEYSET_PREF_NAME, + getOrCreateWrappingMasterKey(context), + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) + } + + private suspend fun getOrCreateWrappingMasterKey(context: Context) = + withContext(dispatcherProvider.io()) { + MasterKey.Builder(context, "passphrase") + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .setRequestStrongBoxBacked(true) + .setUserAuthenticationRequired( + /* authenticationRequired = */ true, + /* userAuthenticationValidityDurationSeconds = */ 60, + ) + .build() + } + + private companion object { + + private const val ANDROIDX_SECURITY_KEYSET_PREF_NAME = "androidx_passphrase_keyset_prefs" + } +}