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 9 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 = 31

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,10 @@ 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.Log
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 +27,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 @@ -142,17 +146,90 @@ class AuthPreferences(val context: Context) :
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)
val encryptedSeed = App.appCore.getCurrentAuthenticationManager().encryptInBackground(password, seedEncoded)
?: return false
return setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed)
}

fun getSeedPhrase(): String {
return getString(SEED_PHRASE, "")
suspend fun getSeedPhrase(password: String): String {
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved

val seedEncrypted = getString(SEED_PHRASE_ENCRYPTED)
val seedUnencrypted = getString(SEED_PHRASE)

if(seedEncrypted != null && seedUnencrypted == null){
//Seed is encrypted and the old seed is deleted, decrypt and return the result
return App.appCore.getCurrentAuthenticationManager().decryptInBackground(password, seedEncrypted)
?: return ""
}

if(seedEncrypted != null && seedUnencrypted != null){
//Booth encrypted and decrypted seeds present, delete the old seed
setString(SEED_PHRASE, null)
return seedUnencrypted
}
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved

if(seedUnencrypted != null){
//Only the unencrypted seed is present, encrypt it, save it and delete the old seed
val encryptedSeed = App.appCore.getCurrentAuthenticationManager().encryptInBackground(password, seedUnencrypted) ?: return ""
setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed).let { saveSuccess ->
if (saveSuccess) {
Log.d("ENCRYPTED SEED UPDATED")
setString(SEED_PHRASE, null)
return seedUnencrypted
}
}
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}
return ""
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}

suspend fun getSeedPhraseDecrypted(decryptKey: SecretKey): String? {

sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
getString(SEED_PHRASE_ENCRYPTED)?.let {seedEncrypted ->
return App.appCore.getOriginalAuthenticationManager()
.decryptInBackground(decryptKey, seedEncrypted)
}
return getString(SEED_PHRASE)
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}

suspend fun checkAndTryToEncryptSeed(password: String): Boolean {
bisgardo marked this conversation as resolved.
Show resolved Hide resolved

val seedEncrypted = getString(SEED_PHRASE_ENCRYPTED)
val seedUnencrypted = getString(SEED_PHRASE)

if(seedEncrypted != null && seedUnencrypted == null){
//Encrypted seed is stored, unencrypted is erased
return true
}

if(seedEncrypted != null && seedUnencrypted != null){
//Booth encrypted and decrypted seeds present, delete the old seed
return setStringWithResult(SEED_PHRASE, null)
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}

if(seedUnencrypted != null){
//Only the 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 ->
return saveSuccess && setStringWithResult(SEED_PHRASE, null)
}
}

return false
}

fun setSeedPhraseEncrypted(encryptedSeed: String): Boolean {
setStringWithResult(SEED_PHRASE_ENCRYPTED, encryptedSeed).let {saveSuccess ->
if(saveSuccess){
setString(SEED_PHRASE, null)
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
}
return saveSuccess
}
}

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 @@ -10,6 +10,8 @@ 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 com.concordium.wallet.util.Log
import kotlinx.coroutines.launch
import javax.crypto.Cipher

Expand Down Expand Up @@ -60,7 +62,14 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio
_waitingLiveData.value = true
val res = App.appCore.getCurrentAuthenticationManager().checkPasswordInBackground(password)
if (res) {

val seedEncryptionCheck = AuthPreferences(getApplication()).checkAndTryToEncryptSeed(password)
sasho-axellero marked this conversation as resolved.
Show resolved Hide resolved
bisgardo marked this conversation as resolved.
Show resolved Hide resolved
if(seedEncryptionCheck){
Log.d("Seed is Encrypted")
bisgardo marked this conversation as resolved.
Show resolved Hide resolved
}

loginSuccess()

} 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,7 +81,14 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio
_waitingLiveData.value = true
val password = App.appCore.getCurrentAuthenticationManager().checkPasswordInBackground(cipher)
if (password != null) {

val seedEncryptionCheck = AuthPreferences(getApplication()).checkAndTryToEncryptSeed(password)
if(seedEncryptionCheck){
Log.d("Seed is Encrypted")
}

loginSuccess()

} else {
_passwordErrorLiveData.value =
Event(if (App.appCore.getCurrentAuthenticationManager().usePasscode()) R.string.auth_login_passcode_error else R.string.auth_login_password_error)
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()).getSeedPhraseDecrypted(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()).setSeedPhraseEncrypted(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 @@ -4,13 +4,15 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import com.concordium.wallet.BuildConfig
import com.concordium.wallet.data.preferences.AuthPreferences
import com.concordium.wallet.ui.passphrase.common.WordsPickedBaseListAdapter
import com.concordium.wallet.util.Log
import com.concordium.wallet.util.toHex
import kotlinx.coroutines.launch
import java.util.*

class PassPhraseRecoverViewModel(application: Application) : AndroidViewModel(application) {
Expand All @@ -21,10 +23,18 @@ class PassPhraseRecoverViewModel(application: Application) : AndroidViewModel(ap
val WORD_COUNT: Int = Mnemonics.WordCount.COUNT_24.count
}

private val _saveSeedLiveData = MutableLiveData<Boolean>()
val saveSeed: LiveData<Boolean>
get() = _saveSeedLiveData

private val _validateLiveData = MutableLiveData<Boolean>()
val validate: LiveData<Boolean>
get() = _validateLiveData

private val _seedLiveData = MutableLiveData<String>()
val seed: LiveData<String>
get() = _seedLiveData

fun loadAllWords() {
allWords = Mnemonics.getCachedWords(Locale.ENGLISH.language)
}
Expand All @@ -33,11 +43,16 @@ class PassPhraseRecoverViewModel(application: Application) : AndroidViewModel(ap
wordsPicked = arrayOfNulls(wordsPicked.size)
}

fun hack() {
fun setPredefinedPhraseForTesting(password: String) = viewModelScope.launch {
if (BuildConfig.DEBUG) {
//AuthPreferences(getApplication()).setSeedPhrase("health smoke abandon middle outer method meadow sorry whale random cupboard thank album exclude idle month exit quarter shell portion eternal legal rent tonight") // testnet CBW-320
AuthPreferences(getApplication()).setSeedPhrase("nothing ill myself guitar antique demise awake twelve fall victory grow segment bus puppy iron vicious skate piece tobacco stable police plunge coin fee") // testnet
_validateLiveData.value = true
val saveSuccess = AuthPreferences(getApplication()).tryToSetEncryptedSeedPhrase(
"nothing ill myself guitar antique demise awake twelve fall victory grow segment bus puppy iron vicious skate piece tobacco stable police plunge coin fee",
password
)// testnet
_saveSeedLiveData.value = saveSuccess
_validateLiveData.value = saveSuccess

}
}

Expand All @@ -52,12 +67,19 @@ class PassPhraseRecoverViewModel(application: Application) : AndroidViewModel(ap
val result = Mnemonics.MnemonicCode(enteredPhrase).toSeed()
success = result.isNotEmpty() && result.size == 64 && result.toHex().length == 128
if (success) {
AuthPreferences(getApplication()).setSeedPhrase(enteredPhrase)
_seedLiveData.value = enteredPhrase
}
} catch (ex: Exception) {
Log.d(ex.message ?: "")
}
}
_validateLiveData.value = success
}

fun setSeedPhrase(seed: String, password: String) = viewModelScope.launch {
_saveSeedLiveData.value = AuthPreferences(getApplication()).tryToSetEncryptedSeedPhrase(
seed,
password
)
}
}
Loading