add app lock secruity and support for saving mib credentials

This commit is contained in:
2026-05-14 05:24:13 +05:00
parent 7209e9dca0
commit 81a2be150f
17 changed files with 1259 additions and 35 deletions
@@ -35,9 +35,10 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
* Full login flow. Automatically handles first-time device registration
* vs. subsequent logins using stored key1/key2.
*
* @param passwordHash SHA-256(password) uppercase hex — use [hashPassword] to compute.
* Returns list of accounts from all profiles on success.
*/
fun login(username: String, password: String, otpSeed: String): List<MibAccount> {
fun login(username: String, passwordHash: String, otpSeed: String): List<MibAccount> {
val appId = getOrCreateAppId()
Log.d(TAG, "login: appId=$appId")
val key1 = prefs.getString("mib_key1_$username", null)
@@ -46,17 +47,17 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
return if (key1 != null && key2 != null) {
Log.d(TAG, "login: taking regular login path")
regularLogin(username, password, appId, key1, key2)
regularLogin(username, passwordHash, appId, key1, key2)
} else {
Log.d(TAG, "login: taking first-time registration path")
firstTimeRegistration(username, password, otpSeed, appId)
firstTimeRegistration(username, passwordHash, otpSeed, appId)
}
}
// ─── First-time registration ──────────────────────────────────────────────
private fun firstTimeRegistration(
username: String, password: String, otpSeed: String, appId: String
username: String, passwordHash: String, otpSeed: String, appId: String
): List<MibAccount> {
Log.d(TAG, "[reg] step 0: key exchange (sfunc=r)")
val (session1, _) = initialKeyExchange(appId, MibCrypto.DEFAULT_KEY, "r")
@@ -67,9 +68,9 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
Log.d(TAG, "[reg] step 1 done: userSalt length=${userSalt.length}")
Log.d(TAG, "[reg] step 2: registration init (C41)")
Log.d(TAG, "[reg] username='$username' password='$password' userSalt='$userSalt'")
Log.d(TAG, "[reg] username='$username' userSalt='$userSalt'")
val clientSalt = randomAlpha(32)
val pgf03 = computePgf03(password, userSalt, clientSalt)
val pgf03 = computePgf03(passwordHash, userSalt, clientSalt)
Log.d(TAG, "[reg] pgf03=$pgf03")
val regInitPayload = baseData(session1, "C41").apply {
put("uname", username)
@@ -102,13 +103,13 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
Log.d(TAG, "[reg] stored key1/key2 for user=$username")
prefs.edit().putString("mib_key1_$username", key1).putString("mib_key2_$username", key2).apply()
return regularLogin(username, password, appId, key1, key2)
return regularLogin(username, passwordHash, appId, key1, key2)
}
// ─── Regular login ────────────────────────────────────────────────────────
private fun regularLogin(
username: String, password: String,
username: String, passwordHash: String,
appId: String, key1: String, key2: String
): List<MibAccount> {
Log.d(TAG, "[login] step 4: key exchange (sfunc=i)")
@@ -121,7 +122,7 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
Log.d(TAG, "[login] step 6: login init (A41)")
val clientSalt = randomAlpha(32)
val pgf03 = computePgf03(password, userSalt, clientSalt)
val pgf03 = computePgf03(passwordHash, userSalt, clientSalt)
Log.d(TAG, "[login] pgf03 length=${pgf03.length}")
val loginPayload = baseData(session2, "A41").apply {
put("uname", username)
@@ -273,16 +274,24 @@ class MibLoginFlow(private val prefs: android.content.SharedPreferences) {
return response.body?.string() ?: throw IllegalStateException("Empty response body")
}
private fun computePgf03(password: String, userSalt: String, clientSalt: String): String {
fun sha256Upper(input: String) = MessageDigest.getInstance("SHA-256")
.digest(input.toByteArray())
.joinToString("") { "%02X".format(it) }
val h1 = sha256Upper(password)
/** @param h1 SHA-256(password) uppercase hex, as returned by [hashPassword] */
private fun computePgf03(h1: String, userSalt: String, clientSalt: String): String {
val h2 = sha256Upper(h1 + userSalt)
return sha256Upper(clientSalt + h2)
}
companion object {
/** Returns SHA-256(password) as uppercase hex. Store this instead of the raw password. */
fun hashPassword(password: String): String =
MessageDigest.getInstance("SHA-256")
.digest(password.toByteArray())
.joinToString("") { "%02X".format(it) }
}
private fun sha256Upper(input: String) = MessageDigest.getInstance("SHA-256")
.digest(input.toByteArray())
.joinToString("") { "%02X".format(it) }
private fun generateOtp(seed: String): String = Totp.generate(seed)
private fun getOrCreateAppId(): String {