security: encrypt credentials, caches, and harden lock screen
Auto Tag on Version Change / check-version (push) Successful in 6s
Auto Tag on Version Change / check-version (push) Successful in 6s
This commit is contained in:
@@ -56,6 +56,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
private val autolockHandler = Handler(Looper.getMainLooper())
|
||||
private var warningDialog: AlertDialog? = null
|
||||
private var countdownTimer: CountDownTimer? = null
|
||||
private var pauseTime = 0L
|
||||
|
||||
private val warningRunnable = Runnable { showAutolockWarning() }
|
||||
|
||||
@@ -194,11 +195,24 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// If we were away long enough to have hit the autolock timeout (e.g. while
|
||||
// QrScannerActivity was in the foreground), lock immediately.
|
||||
if (pauseTime > 0L) {
|
||||
val elapsed = System.currentTimeMillis() - pauseTime
|
||||
val timeout = getSharedPreferences("prefs", MODE_PRIVATE).getLong("autolock_timeout", 60_000L)
|
||||
val securitySet = getSharedPreferences("prefs", MODE_PRIVATE).getString("security_method", null) != null
|
||||
if (timeout > 0L && elapsed >= timeout && securitySet) {
|
||||
startActivity(Intent(this, sh.sar.basedbank.LockActivity::class.java))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
}
|
||||
resetAutolockTimer()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
pauseTime = System.currentTimeMillis()
|
||||
autolockHandler.removeCallbacks(autolockRunnable)
|
||||
autolockHandler.removeCallbacks(warningRunnable)
|
||||
countdownTimer?.cancel(); countdownTimer = null
|
||||
@@ -300,8 +314,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
val mibJob = mibCreds?.let {
|
||||
async(Dispatchers.IO) {
|
||||
try {
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val flow = MibLoginFlow(CredentialStore(this@HomeActivity))
|
||||
val accounts = flow.login(it.username, it.passwordHash, it.otpSeed)
|
||||
val app = application as BasedBankApp
|
||||
app.accounts = accounts
|
||||
@@ -411,8 +424,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
private fun refreshContacts(session: MibSession?, profiles: List<MibProfile>) {
|
||||
if (session == null || profiles.isEmpty()) return
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val flow = MibLoginFlow(CredentialStore(this))
|
||||
val contactsClient = MibContactsClient()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
@@ -465,8 +477,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
val fresh = withContext(Dispatchers.IO) {
|
||||
val sess = app.mibSession ?: return@withContext null
|
||||
val profile = app.mibProfiles.firstOrNull { it.profileId == src.profileId } ?: return@withContext null
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
try { MibLoginFlow(prefs).fetchAllProfiles(sess, listOf(profile), src.loginTag) }
|
||||
try { MibLoginFlow(CredentialStore(this@HomeActivity)).fetchAllProfiles(sess, listOf(profile), src.loginTag) }
|
||||
catch (_: Exception) { null }
|
||||
} ?: return@launch
|
||||
// Replace accounts from this profile only, keep everything else
|
||||
@@ -480,8 +491,7 @@ class HomeActivity : AppCompatActivity() {
|
||||
|
||||
private fun refreshFinancing(session: MibSession?, profiles: List<MibProfile>) {
|
||||
if (session == null || profiles.isEmpty()) return
|
||||
val prefs = getSharedPreferences("mib_prefs", MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val flow = MibLoginFlow(CredentialStore(this))
|
||||
val client = MibFinancingClient()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
|
||||
@@ -111,8 +111,7 @@ class CredentialsFragment : Fragment() {
|
||||
binding.btnLogin.isEnabled = false
|
||||
|
||||
val passwordHash = MibLoginFlow.hashPassword(password)
|
||||
val prefs = requireContext().getSharedPreferences("mib_prefs", android.content.Context.MODE_PRIVATE)
|
||||
val flow = MibLoginFlow(prefs)
|
||||
val flow = MibLoginFlow(CredentialStore(requireContext()))
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
try {
|
||||
|
||||
@@ -12,8 +12,10 @@ import androidx.fragment.app.Fragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import sh.sar.basedbank.R
|
||||
import sh.sar.basedbank.databinding.FragmentSecuritySetupBinding
|
||||
import java.security.MessageDigest
|
||||
import sh.sar.basedbank.util.CredentialStore
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.SecretKeyFactory
|
||||
import javax.crypto.spec.PBEKeySpec
|
||||
|
||||
class SecuritySetupFragment : Fragment() {
|
||||
|
||||
@@ -231,16 +233,25 @@ class SecuritySetupFragment : Fragment() {
|
||||
private fun saveCredential(method: String, input: String) {
|
||||
val salt = ByteArray(16).also { SecureRandom().nextBytes(it) }
|
||||
val saltB64 = Base64.encodeToString(salt, Base64.NO_WRAP)
|
||||
val hash = sha256(saltB64 + input)
|
||||
val hash = pbkdf2(input, salt)
|
||||
requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE).edit()
|
||||
.putString("security_method", method)
|
||||
.putString("security_salt", saltB64)
|
||||
.putString("security_hash", hash)
|
||||
// Remove legacy plaintext fields if they exist from an old install
|
||||
.remove("security_salt")
|
||||
.remove("security_hash")
|
||||
.apply()
|
||||
CredentialStore(requireContext()).saveSecurityHash(saltB64, hash)
|
||||
}
|
||||
|
||||
private fun sha256(input: String) = MessageDigest.getInstance("SHA-256")
|
||||
.digest(input.toByteArray()).joinToString("") { "%02x".format(it) }
|
||||
private fun pbkdf2(input: String, salt: ByteArray): String {
|
||||
val spec = PBEKeySpec(input.toCharArray(), salt, 100_000, 256)
|
||||
return try {
|
||||
val hash = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(spec).encoded
|
||||
Base64.encodeToString(hash, Base64.NO_WRAP)
|
||||
} finally {
|
||||
spec.clearPassword()
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishSetup() {
|
||||
val cb = activity as? Callback
|
||||
|
||||
Reference in New Issue
Block a user