Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypt seed #192

Merged
merged 14 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: "androidx.navigation.safeargs.kotlin"

def versionBuildNumberSequential = 30
def versionBuildNumberSequential = 32

def versionBuildNumberMajor = 1
def versionBuildNumberMinor = 1
def versionBuildNumberPatch = 4
def versionBuildNumberPatch = 5

//def LOCAL_IP = getLocalIPv4()
def LOCAL_IP = "10.0.2.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.concordium.wallet.data.preferences
import android.content.Context
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import com.concordium.wallet.App
import com.concordium.wallet.util.toHex
import javax.crypto.SecretKey

class AuthPreferences(val context: Context) :
Preferences(context, SharedPreferencesKeys.PREF_FILE_AUTH, Context.MODE_PRIVATE) {
Expand All @@ -24,6 +26,7 @@ class AuthPreferences(val context: Context) :
const val PREFKEY_SHIELDED_WARNING_DISMISSED_ = "PREFKEY_SHIELDED_WARNING_DISMISSED_"
const val PREFKEY_IDENTITY_PENDING_ACKNOWLEDGED = "PREFKEY_IDENTITY_PENDING_ACKNOWLEDGED_"
const val SEED_PHRASE = "SEED_PHRASE"
const val SEED_PHRASE_ENCRYPTED = "SEED_PHRASE_ENCRYPTED"
}

fun setHasSetupUser(value: Boolean) {
Expand Down Expand Up @@ -138,21 +141,64 @@ class AuthPreferences(val context: Context) :
return getBoolean(PREFKEY_IDENTITY_PENDING_ACKNOWLEDGED+id, false)
}

fun resetSeedPhrase() {
setString(SEED_PHRASE, "")
}

fun setSeedPhrase(value: String) {
suspend fun tryToSetEncryptedSeedPhrase(value: String, password: String): Boolean {
val seed = Mnemonics.MnemonicCode(value).toSeed()
val seedEncoded = seed.toHex()
setString(SEED_PHRASE, seedEncoded)
}

fun getSeedPhrase(): String {
return getString(SEED_PHRASE, "")
val encryptedSeed = App.appCore.getCurrentAuthenticationManager().encryptInBackground(password, seedEncoded)
?: return false
return setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed)
}

suspend fun getSeedPhrase(password: String): String {
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
getString(SEED_PHRASE_ENCRYPTED)?.let {seedEncrypted ->
return App.appCore.getOriginalAuthenticationManager()
.decryptInBackground(password, seedEncrypted)?: return ""
}
return ""
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}

suspend fun getSeedPhrase(decryptKey: SecretKey): String? {
getString(SEED_PHRASE_ENCRYPTED)?.let {seedEncrypted ->
return App.appCore.getOriginalAuthenticationManager()
.decryptInBackground(decryptKey, seedEncrypted)
}
return null
}
/**
* Check if the old unencrypted seed is present
*
* If the old unencrypted seed is present, encrypt it, save the new encrypted seed.
*
* Then get the encrypted seed from SharedPreference, decrypt it and compare it to the unencrypted seed. If they match delete the old seed.
*
* @param password the password required to encrypt the seed
* @return Returns [Boolean]
*
* **true** if there is no unencrypted seed saved. This can happen when new user has setup password and biometrics and exited the app before saving the seed phrase.
* Next time when he starts the app he will be presented with the Authentication screen and after successful password the user needs to be able to continue with the seed-phrase setup.
*
* **true** if the seed is successfully encrypted, the encrypted seed is successfully saved,
* the decrypted and unencrypted seeds match and the old seed is successfully deleted.
*/
suspend fun checkAndTryToEncryptSeed(password: String): Boolean {
bisgardo marked this conversation as resolved.
Show resolved Hide resolved
val seedUnencrypted = getString(SEED_PHRASE) ?: return true

//Unencrypted seed is present, encrypt it, save it and delete the old.
val encryptedSeed = App.appCore.getCurrentAuthenticationManager().encryptInBackground(password, seedUnencrypted) ?: return false
setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed).let { saveSuccess ->
if(saveSuccess) {
val decryptedSeed = getSeedPhrase(password)
return decryptedSeed == seedUnencrypted && setStringWithResult(SEED_PHRASE, null)
}
}
return false
}

fun updateEncryptedSeedPhrase(encryptedSeed: String): Boolean {
return setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed)
}

fun hasSeedPhrase(): Boolean {
return getSeedPhrase() != ""
return getString(SEED_PHRASE_ENCRYPTED) != null || getString(SEED_PHRASE) != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ open class Preferences {
triggerChangeEvent(key)
}

protected fun setStringWithResult(key: String, value: String?): Boolean {
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
val editor = editor
bisgardo marked this conversation as resolved.
Show resolved Hide resolved
if (value == null) {
editor.remove(key)
} else {
editor.putString(key, value)
}
return editor.commit()
}

protected fun getString(key: String, def: String): String {
val result = sharedPreferences.getString(key, def)
result ?: return def
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ open class NewAccountViewModel(application: Application) : AndroidViewModel(appl
}

val net = AppConfig.net
val seed = AuthPreferences(getApplication()).getSeedPhrase()
val seed = AuthPreferences(getApplication()).getSeedPhrase(password)

val credentialInput = CreateCredentialInputV1(
idProviderInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ class AuthLoginActivity : BaseActivity() {
showPasswordError(value)
}
})
viewModel.errorSeedLiveData.observe(this) { error ->
if (error) {
KeyboardUtil.hideKeyboard(this)
binding.errorSeedTextview.visibility = View.VISIBLE
} else {
binding.errorSeedTextview.visibility = View.GONE
}
}
}

private fun initializeViews() {
Expand All @@ -95,6 +103,7 @@ class AuthLoginActivity : BaseActivity() {
binding.passcodeView.passcodeListener = object : PasscodeView.PasscodeListener {
override fun onInputChanged() {
binding.errorTextview.text = ""
binding.errorSeedTextview.visibility = View.GONE
}

override fun onDone() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.concordium.wallet.R
import com.concordium.wallet.core.arch.Event
import com.concordium.wallet.core.authentication.Session
import com.concordium.wallet.core.security.KeystoreEncryptionException
import com.concordium.wallet.data.preferences.AuthPreferences
import kotlinx.coroutines.launch
import javax.crypto.Cipher

Expand All @@ -33,6 +34,9 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio
val finishScreenLiveData: LiveData<Event<Boolean>>
get() = _finishScreenLiveData

private val _errorSeedLiveData = MutableLiveData<Boolean>()
val errorSeedLiveData: LiveData<Boolean>
get() = _errorSeedLiveData


fun initialize() {
Expand Down Expand Up @@ -60,7 +64,7 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio
_waitingLiveData.value = true
val res = App.appCore.getCurrentAuthenticationManager().checkPasswordInBackground(password)
if (res) {
loginSuccess()
checkSeed(password)
} else {
_passwordErrorLiveData.value =
Event(if (App.appCore.getCurrentAuthenticationManager().usePasscode()) R.string.auth_login_passcode_error else R.string.auth_login_password_error)
Expand All @@ -72,14 +76,29 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio
_waitingLiveData.value = true
val password = App.appCore.getCurrentAuthenticationManager().checkPasswordInBackground(cipher)
if (password != null) {
loginSuccess()
checkSeed(password)
} else {
_passwordErrorLiveData.value =
Event(if (App.appCore.getCurrentAuthenticationManager().usePasscode()) R.string.auth_login_passcode_error else R.string.auth_login_password_error)
_waitingLiveData.value = false
}
}

private suspend fun checkSeed(password: String){
val noUnencryptedSeed = AuthPreferences(getApplication()).checkAndTryToEncryptSeed(password)
if(noUnencryptedSeed){
// The old seed is deleted or a new user resumes the seed-phrase setup process.
loginSuccess()
}else{
//The unencrypted seed is still present.
//Something went wrong when trying to encrypt it and delete it.
_passwordErrorLiveData.value =
Event(R.string.auth_login_seed_error)
_errorSeedLiveData.value = true
_waitingLiveData.value = false
}
}

private fun loginSuccess() {
session.hasLoggedInUser()
_finishScreenLiveData.value = Event(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class AuthDelegateImpl : AuthDelegate {
private fun createPromptInfo(activity: AppCompatActivity): BiometricPrompt.PromptInfo {
return BiometricPrompt.PromptInfo.Builder()
.setTitle(activity.getString(R.string.auth_login_biometrics_dialog_title))
.setSubtitle(activity.getString(R.string.auth_login_biometrics_dialog_subtitle))
.setConfirmationRequired(true)
.setNegativeButtonText(activity.getString(if (App.appCore.getCurrentAuthenticationManager().usePasscode()) R.string.auth_login_biometrics_dialog_cancel_passcode else R.string.auth_login_biometrics_dialog_cancel_password))
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class IdentityProviderListActivity : BaseActivity() {
val dialogFragment = AuthenticationDialogFragment()
dialogFragment.setCallback(object : AuthenticationDialogFragment.Callback {
override fun onCorrectPassword(password: String) {
viewModel.continueWithPassword()
viewModel.continueWithPassword(password)
}

override fun onCancelled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,24 @@ class IdentityProviderListViewModel(application: Application) : AndroidViewModel
}
}

fun continueWithPassword() = viewModelScope.launch {
fun continueWithPassword(password: String) = viewModelScope.launch {
_waitingLiveData.value = true
encryptAndContinue()
encryptAndContinue(password)
}

fun checkLogin(cipher: Cipher) = viewModelScope.launch {
_waitingLiveData.value = true
val password = App.appCore.getCurrentAuthenticationManager().checkPasswordInBackground(cipher)
if (password != null) {
encryptAndContinue()
encryptAndContinue(password)
} else {
_errorLiveData.value = Event(R.string.app_error_encryption)
_waitingLiveData.value = false
}
}

private suspend fun encryptAndContinue() {
val output = createIdRequestAndPrivateDataV1()
private suspend fun encryptAndContinue(password: String) {
val output = createIdRequestAndPrivateDataV1(password)
if (output != null) {
tempData.idObjectRequest = output.idObjectRequest
_gotoIdentityProviderWebView.value = Event(true)
Expand All @@ -151,7 +151,7 @@ class IdentityProviderListViewModel(application: Application) : AndroidViewModel
}
}

private suspend fun createIdRequestAndPrivateDataV1(): IdRequestAndPrivateDataOutputV1? {
private suspend fun createIdRequestAndPrivateDataV1(password: String): IdRequestAndPrivateDataOutputV1? {
val identityProvider = tempData.identityProvider
val global = tempData.globalParams
if (identityProvider == null) {
Expand All @@ -163,7 +163,7 @@ class IdentityProviderListViewModel(application: Application) : AndroidViewModel
val net = AppConfig.net
tempData.identityIndex = identityRepository.nextIdentityIndex(identityProvider.ipInfo.ipIdentity)
tempData.identityName = identityRepository.nextIdentityName(getApplication<Application?>().getString(R.string.view_identity_identity))
val seed = AuthPreferences(getApplication()).getSeedPhrase()
val seed = AuthPreferences(getApplication()).getSeedPhrase(password)

val output = App.appCore.cryptoLibrary.createIdRequestAndPrivateDataV1(identityProvider.ipInfo, identityProvider.arsInfos, global, seed, net, tempData.identityIndex)
return if (output != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.concordium.wallet.core.arch.Event
import com.concordium.wallet.core.security.KeystoreEncryptionException
import com.concordium.wallet.data.AccountRepository
import com.concordium.wallet.data.IdentityRepository
import com.concordium.wallet.data.preferences.AuthPreferences
import com.concordium.wallet.data.room.Account
import com.concordium.wallet.data.room.Identity
import com.concordium.wallet.data.room.WalletDatabase
Expand All @@ -27,6 +28,8 @@ class AlterPasswordViewModel(application: Application) :
private var initialDecryptedAccountsList: ArrayList<Account> = ArrayList()
private var initialDecryptedIdentityList: List<Identity> = emptyList()

private var decryptedSeed: String? = null

private var database: WalletDatabase

private val identityRepository: IdentityRepository
Expand Down Expand Up @@ -163,6 +166,16 @@ class AlterPasswordViewModel(application: Application) :
allSuccess = false
}

try {
decryptedSeed = AuthPreferences(getApplication()).getSeedPhrase(decryptKey)
if(decryptedSeed == null){
allSuccess = false
}
}catch (e: Exception) {
e.printStackTrace()
allSuccess = false
}

if(allSuccess){
App.appCore.startResetAuthFlow()
_doneInitialAuthenticationLiveData.value = Event(true)
Expand All @@ -178,8 +191,25 @@ class AlterPasswordViewModel(application: Application) :
private fun encryptAndFinalize(encryptKey: SecretKey) =
viewModelScope.launch {
_waitingLiveData.value = true

var allSuccess = true

if(decryptedSeed != null){
try {
val seedPhraseEncrypted = App.appCore.getOriginalAuthenticationManager()
.encryptInBackground(encryptKey, decryptedSeed!!)
if (seedPhraseEncrypted == null || !AuthPreferences(getApplication()).updateEncryptedSeedPhrase(seedPhraseEncrypted)) {
allSuccess = false
}
decryptedSeed = null
}catch (e: Exception){
allSuccess = false
}
}else{
allSuccess = false
}

database.withTransaction {
var allSuccess = true
try {
for (account in initialDecryptedAccountsList) {
if (account.encryptedAccountData.isNotEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class PassPhraseRecoverInputFragment : Fragment() {
override fun onResume() {
super.onResume()
(activity as BaseActivity).setActionBarTitle(R.string.pass_phrase_recover_input_title)
val inputMethodManager = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
val inputMethodManager = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
ali-concordium marked this conversation as resolved.
Show resolved Hide resolved
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0)
}

override fun onPause() {
Expand Down
Loading