Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0624288
feat: new security system (phase 1)
HashEngineering Dec 19, 2025
5ab1962
feat: new security system (phase 2)
HashEngineering Dec 20, 2025
4cf7737
feat: add fallback testing
HashEngineering Dec 20, 2025
f1f0591
fix: add propagate context
HashEngineering Dec 21, 2025
a967b50
feat: add add security status and track
HashEngineering Dec 23, 2025
7f30bdb
feat: add add security status and track
HashEngineering Dec 23, 2025
1b20d3d
feat: add add security status and track
HashEngineering Dec 23, 2025
57963fd
feat: add another test failure mode
HashEngineering Dec 23, 2025
c9a3d5f
feat: new security system phase 3, recovery
HashEngineering Dec 23, 2025
a794696
feat: remove obsolete files
HashEngineering Dec 24, 2025
dd2f403
feat: notify user of lost password/PIN
HashEngineering Dec 30, 2025
110be64
feat: other updates
HashEngineering Dec 30, 2025
fd21786
feat: update dialogs with new components
HashEngineering Dec 30, 2025
47403ba
feat: restore migration step
HashEngineering Jan 5, 2026
86efb03
feat: prevent crashes
HashEngineering Jan 6, 2026
9c73707
fixes: minor fixes
HashEngineering Jan 8, 2026
e38b795
fix: minor fixes
HashEngineering Jan 9, 2026
d3b0dd6
docs: add QA test protocol
HashEngineering Jan 9, 2026
8e61189
Merge branch 'master' of https://github.com/dashevo/dash-wallet into …
HashEngineering Jan 9, 2026
61e1f54
fix: remove debug UI elements
HashEngineering Jan 12, 2026
a8168a6
fix: remove password dumps in debug mode
HashEngineering Jan 12, 2026
06c8fe5
fix: only set healthy status if encryption is true
HashEngineering Jan 12, 2026
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ buildscript {
kotlin_version = '2.1.0'
coroutinesVersion = '1.6.4'
ok_http_version = '4.12.0'
dashjVersion = '21.1.13'
dashjVersion = '21.1.14-SNAPSHOT'
dppVersion = "2.0.2"
hiltVersion = '2.53'
hiltCompilerVersion = '1.2.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2025 Dash Core Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.dash.wallet.common.data

enum class SecuritySystemStatus(val value: Int, val isHealthy: Boolean) {
DEAD(0, false),
FALLBACKS(2, false),
HEALTHY(1, true),
HEALTHY_WITH_FALLBACKS(3, true);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ data class BlockchainState(var bestChainDate: Date?,
}

enum class Impediment {
STORAGE, NETWORK
STORAGE, NETWORK, SECURITY
}

fun syncFailed(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
package org.dash.wallet.common.services

import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.flow.Flow
import org.bitcoinj.core.Address
import org.dash.wallet.common.data.SecuritySystemStatus

interface AuthenticationManager {
fun authenticate(activity: FragmentActivity, pinOnly: Boolean = false, callback: (String?) -> Unit)
suspend fun authenticate(activity: FragmentActivity, pinOnly: Boolean = false): String?
suspend fun signMessage(address: Address, message: String): String
fun getHealth(): SecuritySystemStatus
fun observeHealth(): Flow<SecuritySystemStatus>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2025 Dash Core Group.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.dash.wallet.common.util.security

import javax.crypto.SecretKey

/**
* Interface for providing master encryption keys
* Implementations can derive keys from wallet seed, recovery codes, or other sources
*/
interface MasterKeyProvider {
/**
* Get the master encryption key for a specific key alias
* This key is used for encrypting/decrypting sensitive data
*
* @param keyAlias The alias identifying which key to retrieve
* @return SecretKey for encryption/decryption
* @throws GeneralSecurityException if key cannot be derived
*/
fun getMasterKey(keyAlias: String): SecretKey

/**
* Check if the master key provider is available and ready to use
* For seed-based providers, this checks if wallet is initialized
*/
fun isAvailable(): Boolean
}
226 changes: 226 additions & 0 deletions docs/security/ALL_PIN_CHECKS_UPDATED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# All PIN Check Locations Updated with Fallback Recovery ✅

## Summary

All locations in the codebase that check user PINs have been updated to include automatic PIN-based fallback recovery when Android KeyStore fails.

## Updated Files

### 1. ✅ CheckPinLiveData.kt
**Location**: `wallet/src/de/schildbach/wallet/livedata/CheckPinLiveData.kt`
**Method**: `checkPin(pin: String)` (lines 45-89)
**What it does**: Primary PIN checking logic used by most UI components
**Used by**:
- CheckPinDialog
- LockScreenActivity
- SetPinActivity
- SetupPinDuringUpgradeDialog
- Any screen using CheckPinViewModel

**Changes**:
- Try primary KeyStore PIN check
- On failure → Automatic PIN-based fallback recovery
- On success → Self-healing restores KeyStore
- Also calls `ensurePinFallback()` when setting up new PIN

### 2. ✅ DecryptSeedViewModel.kt
**Location**: `wallet/src/de/schildbach/wallet/ui/DecryptSeedViewModel.kt`
**Method**: `decryptSeed(pin: String)` (lines 58-95)
**What it does**: Verifies PIN before decrypting/displaying recovery phrase
**Used by**: Screens that show/verify recovery phrase

**Changes**:
- Try primary KeyStore PIN check
- On failure → Automatic PIN-based fallback recovery
- On success → Self-healing restores KeyStore
- Added analytics events for tracking fallback usage

### 3. ✅ EncryptWalletLiveData.kt
**Location**: `wallet/src/de/schildbach/wallet/livedata/EncryptWalletLiveData.kt`
**Method**: `changePassword(oldPin: String, newPin: String)` (lines 65-101)
**What it does**: Verifies old PIN before allowing PIN change
**Used by**: PIN change/update screens

**Changes**:
- Try primary KeyStore PIN check for old PIN
- On failure → Automatic PIN-based fallback recovery
- On success → Self-healing restores KeyStore
- Calls `ensurePinFallback()` for both old and new PIN

## Flow Diagram

### Direct PIN Check Locations
```
┌─────────────────────────────────────────────────────────────┐
│ User needs to verify their PIN │
└─────────────────────────────────────────────────────────────┘
┌───────────────────┴───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│CheckPinLive │ │DecryptSeedView │ │EncryptWalletLive │
│Data.checkPin │ │Model.decryptSeed │ │Data.changePassword│
└──────────────┘ └──────────────────┘ └──────────────────┘
│ │ │
└───────────────────┴───────────────────────────────┘
┌───────────────────────┐
│ Try KeyStore (Primary)│
└───────────────────────┘
┌───────────┴────────────┐
▼ ▼
Success Failure
│ │
▼ ▼
┌──────────┐ ┌─────────────────┐
│PIN OK ✓ │ │Try PIN Fallback │
└──────────┘ └─────────────────┘
┌───────────┴────────────┐
▼ ▼
Success Failure
│ │
▼ ▼
┌───────────────┐ ┌──────────┐
│Self-heal │ │PIN Wrong │
│KeyStore │ └──────────┘
└───────────────┘
┌───────────────┐
│PIN OK ✓ │
└───────────────┘
```

## UI Components (Indirect Usage)

These UI components call through ViewModels and eventually use the updated methods above:

### Uses CheckPinLiveData (Already Protected ✅)
1. **CheckPinDialog** → CheckPinViewModel → CheckPinLiveData.checkPin()
2. **LockScreenActivity** → CheckPinViewModel → CheckPinLiveData.checkPin()
3. **SetPinActivity** → SetPinViewModel → CheckPinLiveData.checkPin()
4. **SetupPinDuringUpgradeDialog** → CheckPinViewModel → CheckPinLiveData.checkPin()

### Uses DecryptSeedViewModel (Already Protected ✅)
5. **Screens showing recovery phrase** → DecryptSeedViewModel.decryptSeed()

### Uses EncryptWalletLiveData (Already Protected ✅)
6. **PIN change screens** → EncryptWalletLiveData.changePassword()

## Verification

### ✅ All PIN Check Entry Points Covered

```bash
# Search for all .checkPin( calls
grep -r "\.checkPin\(" wallet/src --include="*.kt" --include="*.java"
```

**Results**:
- CheckPinLiveData.kt - ✅ Updated with fallback
- DecryptSeedViewModel.kt - ✅ Updated with fallback
- EncryptWalletLiveData.kt - ✅ Updated with fallback
- All other calls go through ViewModels → Use the above ✅

## Testing Scenarios

### 1. Normal Login (CheckPinDialog/LockScreenActivity)
- User enters PIN
- KeyStore works → PIN accepted
- No fallback needed ✅

### 2. Login with KeyStore Corruption
- User enters PIN
- KeyStore fails → Automatic PIN fallback
- Recovery succeeds → Self-healing restores KeyStore
- PIN accepted ✅

### 3. View Recovery Phrase
- User wants to view recovery phrase
- Enters PIN in DecryptSeedViewModel
- KeyStore fails → Automatic PIN fallback
- Recovery succeeds → Phrase displayed ✅

### 4. Change PIN
- User wants to change PIN
- Enters old PIN in EncryptWalletLiveData
- KeyStore fails → Automatic PIN fallback
- Recovery succeeds → PIN change allowed ✅

## Log Messages

Watch for these log messages to verify fallback is working:

### Success Path
```
Primary PIN check failed: <exception>
Attempting PIN-based fallback recovery
PIN-based fallback recovery succeeded
Primary encryption healed for wallet_password_key
```

### Failure Path
```
Primary PIN check failed: <exception>
Attempting PIN-based fallback recovery
PIN-based fallback recovery also failed
```

## Code Pattern Used

All three locations use the same pattern:

```kotlin
// Try primary PIN check (KeyStore-based) with automatic fallback
val isPinCorrect = try {
securityGuard.checkPin(pin)
} catch (primaryException: Exception) {
log.warn("Primary PIN check failed: ${primaryException.message}")

// Primary failed - try PIN-based fallback recovery
try {
log.info("Attempting PIN-based fallback recovery")
val recoveredPassword = securityGuard.recoverPasswordWithPin(pin)

// PIN-based recovery succeeded!
log.info("PIN-based fallback recovery succeeded")

// Ensure PIN fallback is added if it wasn't already
securityGuard.ensurePinFallback(pin)

true // PIN is correct
} catch (fallbackException: Exception) {
log.error("PIN-based fallback recovery also failed: ${fallbackException.message}")
false // PIN is incorrect
}
}

if (!isPinCorrect) {
// Handle incorrect PIN
throw IllegalArgumentException("wrong pin")
}

// Continue with authenticated operation
```

## Benefits

1. **Transparent Recovery**: User doesn't know KeyStore failed
2. **Self-Healing**: Automatically restores KeyStore after recovery
3. **No UI Changes**: Works with existing dialogs and screens
4. **Analytics Ready**: Can track fallback usage for monitoring
5. **Complete Coverage**: All PIN entry points protected

## Production Ready ✅

All PIN checking code paths now have:
- ✅ Automatic fallback recovery
- ✅ Self-healing capability
- ✅ Error logging
- ✅ Analytics events (where applicable)
- ✅ Backward compatibility

No additional code changes required!
Loading
Loading