This commit is contained in:
@@ -7,6 +7,10 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -16,7 +20,7 @@ class SessionExpiredException : Exception("MIB session expired")
|
|||||||
|
|
||||||
class MibLoginFlow(private val credentialStore: CredentialStore) {
|
class MibLoginFlow(private val credentialStore: CredentialStore) {
|
||||||
|
|
||||||
private val BASE_URL = "https://faisanet.mib.com.mv/faisamobilex_smvc/"
|
private val BASE_URL = "https://faisamobilex-smvc-v2.mib.com.mv/index/"
|
||||||
|
|
||||||
/** The active session after a successful login, usable for subsequent WebView requests. */
|
/** The active session after a successful login, usable for subsequent WebView requests. */
|
||||||
var lastSession: MibSession? = null
|
var lastSession: MibSession? = null
|
||||||
@@ -42,8 +46,9 @@ class MibLoginFlow(private val credentialStore: CredentialStore) {
|
|||||||
.writeTimeout(30, TimeUnit.SECONDS)
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
.addNetworkInterceptor { chain ->
|
.addNetworkInterceptor { chain ->
|
||||||
val req = chain.request().newBuilder()
|
val req = chain.request().newBuilder()
|
||||||
.header("User-Agent", "android/1.0")
|
.header("User-Agent", "android/2.0")
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
|
.header("X-Request-ID", generateRequestId())
|
||||||
.header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
.header("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
|
||||||
.build()
|
.build()
|
||||||
chain.proceed(req)
|
chain.proceed(req)
|
||||||
@@ -424,6 +429,16 @@ class MibLoginFlow(private val credentialStore: CredentialStore) {
|
|||||||
return sha256Upper(clientSalt + h2)
|
return sha256Upper(clientSalt + h2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateRequestId(): String {
|
||||||
|
val sdf = SimpleDateFormat("yyyyMMddHHmmss", Locale.US).apply {
|
||||||
|
timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
}
|
||||||
|
val timestamp = sdf.format(Date())
|
||||||
|
val millis = System.currentTimeMillis() % 1000
|
||||||
|
val randomHex = Random.nextBytes(8).joinToString("") { "%02x".format(it) }
|
||||||
|
return "$timestamp${millis.toString().padStart(3, '0')}-$randomHex"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** Returns SHA-256(password) as uppercase hex. Store this instead of the raw password. */
|
/** Returns SHA-256(password) as uppercase hex. Store this instead of the raw password. */
|
||||||
fun hashPassword(password: String): String =
|
fun hashPassword(password: String): String =
|
||||||
|
|||||||
+11
-1
@@ -57,6 +57,16 @@ def compute_pgf03(password: str, user_salt: str, client_salt: str) -> str:
|
|||||||
|
|
||||||
## Step-by-Step Reference
|
## Step-by-Step Reference
|
||||||
|
|
||||||
|
**Endpoint**: `POST https://faisamobilex-smvc-v2.mib.com.mv/index/`
|
||||||
|
|
||||||
|
**Headers** (on all requests):
|
||||||
|
```
|
||||||
|
User-Agent: android/2.0
|
||||||
|
Accept: application/json
|
||||||
|
X-Request-ID: {YYYYMMDDHHmmss}{milliseconds}-{16-char-hex}
|
||||||
|
Content-Type: application/x-www-form-urlencoded; charset=utf-8
|
||||||
|
```
|
||||||
|
|
||||||
### [0] Initial Key Exchange — `sfunc=r`
|
### [0] Initial Key Exchange — `sfunc=r`
|
||||||
|
|
||||||
**Key**: `DEFAULT_KEY = 8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678`
|
**Key**: `DEFAULT_KEY = 8M3L9SBF1AC4FRE56788M3L9SBF1AC4FRE5678`
|
||||||
@@ -67,7 +77,7 @@ def compute_pgf03(password: str, user_salt: str, client_salt: str) -> str:
|
|||||||
"sfunc": "r",
|
"sfunc": "r",
|
||||||
"data": {
|
"data": {
|
||||||
"cmod": "<G^A mod P as decimal string>",
|
"cmod": "<G^A mod P as decimal string>",
|
||||||
"appId": "IOS17.2-<15 random alphanumeric chars>",
|
"appId": "AND34-<15 random alphanumeric chars>",
|
||||||
"routePath": "S40",
|
"routePath": "S40",
|
||||||
"sodium": "<random int in [1_000_000, 16_000_000)>",
|
"sodium": "<random int in [1_000_000, 16_000_000)>",
|
||||||
"xxid": "<random 40-bit int as string>"
|
"xxid": "<random 40-bit int as string>"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ MIB uses **two completely separate backends**:
|
|||||||
|
|
||||||
| Backend | Base URL | Auth | Used for |
|
| Backend | Base URL | Auth | Used for |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Encrypted API | `https://faisanet.mib.com.mv/faisamobilex_smvc/` | Blowfish + DH session key | Login, key exchange |
|
| Encrypted API | `https://faisamobilex-smvc-v2.mib.com.mv/index/` | Blowfish + DH session key | Login, key exchange |
|
||||||
| WebView host | `https://faisamobilex-wv.mib.com.mv` | Session cookies | Accounts, history, transfers, contacts, cards, financing |
|
| WebView host | `https://faisamobilex-wv.mib.com.mv` | Session cookies | Accounts, history, transfers, contacts, cards, financing |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
+35
-22
@@ -18,13 +18,13 @@ This document tracks all MIB API calls used by thijooree and their upgrade statu
|
|||||||
|
|
||||||
| API Code | Endpoint | Purpose | Used By | Status |
|
| API Code | Endpoint | Purpose | Used By | Status |
|
||||||
|----------|----------|---------|---------|--------|
|
|----------|----------|---------|---------|--------|
|
||||||
| `sfunc=r` + `S40` | Key Exchange (Init) | First-time device registration | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=r` + `S40` | Key Exchange (Init) | First-time device registration | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=i` + `S40` | Key Exchange (Login) | Regular login session setup | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=i` + `S40` | Key Exchange (Login) | Regular login session setup | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=n` + `C41` | Registration Init | Initialize device registration flow | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=n` + `C41` | Registration Init | Initialize device registration flow | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=n` + `C42` | OTP Verification | Verify one-time password after registration | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=n` + `C42` | OTP Verification | Verify one-time password after registration | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=n` + `A41` | Main Login | Submit username/password credentials | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=n` + `A41` | Main Login | Submit username/password credentials | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=n` + `A44` | Get User Salt | Retrieve user salt for password hashing | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=n` + `A44` | Get User Salt | Retrieve user salt for password hashing | `MibLoginFlow.kt` | ✅ |
|
||||||
| `sfunc=n` + `P47` | Switch Profile | Activate different user profile | `MibLoginFlow.kt` | ☐ |
|
| `sfunc=n` + `P47` | Switch Profile | Activate different user profile | `MibLoginFlow.kt` | ✅ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ This document tracks all MIB API calls used by thijooree and their upgrade statu
|
|||||||
|
|
||||||
| Type | URL |
|
| Type | URL |
|
||||||
|------|-----|
|
|------|-----|
|
||||||
| **Encrypted API** | `https://faisanet.mib.com.mv/faisamobilex_smvc/` |
|
| **Encrypted API** | `https://faisamobilex-smvc-v2.mib.com.mv/index/` |
|
||||||
| **WebView/Data** | `https://faisamobilex-wv.mib.com.mv` |
|
| **WebView/Data** | `https://faisamobilex-wv.mib.com.mv` |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -162,20 +162,33 @@ pgf03 = SHA256(clientSalt + SHA256(SHA256(password) + userSalt))
|
|||||||
|
|
||||||
## Upgrade Checklist
|
## Upgrade Checklist
|
||||||
|
|
||||||
- [ ] Review all 28+ endpoints for v1 compatibility
|
### Core Implementation
|
||||||
- [ ] Update authentication flow (encryption/key exchange)
|
- [x] Update encrypted API endpoint URL
|
||||||
- [ ] Update beneficiary management endpoints
|
- [x] Update User-Agent header
|
||||||
- [ ] Update transfer endpoints (internal, local, account lookup)
|
- [x] Add X-Request-ID header
|
||||||
- [ ] Update debit card endpoints
|
- [x] Verify Blowfish encryption compatibility
|
||||||
- [ ] Update transaction history endpoint
|
- [x] Test device registration flow
|
||||||
- [ ] Update activity history endpoint
|
- [x] Test regular login flow
|
||||||
- [ ] Update profile scraping (if API available in v1)
|
- [x] Test OTP verification
|
||||||
- [ ] Update financing endpoint (if API available in v1)
|
- [x] Test profile switching
|
||||||
- [ ] Test session management & keep-alive
|
|
||||||
- [ ] Verify all error handling & session recovery
|
### WebView Endpoints (Pending)
|
||||||
- [ ] Test all 8 categories end-to-end
|
- [ ] Test beneficiary management (`/ajaxBeneficiary/*`)
|
||||||
|
- [ ] Test transfer endpoints (`/ajaxTransfer/*`, `/AjaxAlias/*`)
|
||||||
|
- [ ] Test debit card endpoints (`/ajaxDebitCard/*`)
|
||||||
|
- [ ] Test transaction history (`/ajaxAccounts/trxHistory`)
|
||||||
|
- [ ] Test activity history (`/aProfile/getPagedActivityHistory`)
|
||||||
|
- [ ] Test user profile scraping (`/personalProfile`)
|
||||||
|
- [ ] Test financing (`/financing?dashurl=1`)
|
||||||
|
- [ ] Test session keep-alive (`/aProfile/keepAlive`)
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
- [ ] Verify all account balances display correctly
|
||||||
|
- [ ] Verify error handling (419, 505) and auto-recovery
|
||||||
|
- [ ] Verify all data formats match expectations
|
||||||
|
- [ ] Full end-to-end user flow test
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Started:** 2026-10-22
|
**Implementation:** 2026-10-22 ✅
|
||||||
**Target Completion:** TBD
|
**WebView Testing:** In progress
|
||||||
|
|||||||
Reference in New Issue
Block a user