Skip to content

Commit

Permalink
biometric
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky committed Jul 10, 2022
1 parent 8ea32fc commit f2bd587
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 85 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ Do not forget to activate `Broadcast` trigger in Wasted.
* ACCESSIBILITY - listen for a duress password on the lockscreen
* DEVICE_ADMIN - wipe the device (optional)

## Related

* [pam_panic](https://github.com/pampanic/pam_panic)
* [pam-party](https://github.com/x13a/pam-party)
* [lockup](https://github.com/nekohasekai/lockup)
* [pam-duress](https://github.com/nuvious/pam-duress)

## License
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)

Expand Down
6 changes: 4 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ android {
applicationId "me.lucky.duress"
minSdk 23
targetSdk 32
versionCode 5
versionName "1.0.4"
versionCode 6
versionName "1.0.5"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -42,10 +42,12 @@ dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.4.2'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

implementation 'androidx.security:security-crypto:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.biometric:biometric:1.1.0'
}
15 changes: 12 additions & 3 deletions app/src/main/java/me/lucky/duress/AccessibilityService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ class AccessibilityService : AccessibilityService() {

override fun onInterrupt() {}

override fun onServiceConnected() {
super.onServiceConnected()
if (prefs.keyguardType != KeyguardType.B.value) return
serviceInfo = serviceInfo.apply {
eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
}
}

private fun checkKeyguardTypeA(event: AccessibilityEvent): Boolean {
if (event.eventType != AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED ||
!event.isPassword) return false
Expand Down Expand Up @@ -106,8 +115,8 @@ class AccessibilityService : AccessibilityService() {
)
}
addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
val code = prefs.authenticationCode
if (code.isNotEmpty()) putExtra(KEY, code)
val secret = prefs.secret
if (secret.isNotEmpty()) putExtra(KEY, secret)
})
}

Expand All @@ -124,4 +133,4 @@ class AccessibilityService : AccessibilityService() {
service.get()?.enteredPwLen = 0
}
}
}
}
3 changes: 1 addition & 2 deletions app/src/main/java/me/lucky/duress/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package me.lucky.duress
import android.app.Application
import com.google.android.material.color.DynamicColors

@Suppress("unused")
class Application : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
}
}
}
2 changes: 1 addition & 1 deletion app/src/main/java/me/lucky/duress/DeviceAdminManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ class DeviceAdminManager(private val ctx: Context) {
fun makeRequestIntent() =
Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN)
.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, deviceAdmin)
}
}
12 changes: 1 addition & 11 deletions app/src/main/java/me/lucky/duress/DeviceAdminReceiver.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
package me.lucky.duress

import android.app.admin.DeviceAdminReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class DeviceAdminReceiver : DeviceAdminReceiver() {
override fun onDisabled(context: Context, intent: Intent) {
super.onDisabled(context, intent)
val prefs = Preferences(context)
if (prefs.isEnabled && prefs.mode == Mode.WIPE.value)
Toast.makeText(context, R.string.service_unavailable_popup, Toast.LENGTH_SHORT).show()
}
}
class DeviceAdminReceiver : DeviceAdminReceiver() {}
134 changes: 85 additions & 49 deletions app/src/main/java/me/lucky/duress/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import android.provider.Settings
import android.view.View
import android.view.accessibility.AccessibilityManager
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.core.widget.doAfterTextChanged
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
Expand All @@ -31,6 +34,7 @@ class MainActivity : AppCompatActivity() {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
init()
if (initBiometric()) return
setup()
if (prefs.isShowProminentDisclosure) showProminentDisclosure()
}
Expand All @@ -51,11 +55,12 @@ class MainActivity : AppCompatActivity() {
prefsdb = Preferences(this, encrypted = false)
prefs.copyTo(prefsdb)
accessibilityManager = getSystemService(AccessibilityManager::class.java)
selectInterface()
binding.apply {
tabs.selectTab(tabs.getTabAt(prefs.mode))
action.editText?.setText(prefs.action)
receiver.editText?.setText(prefs.receiver)
authenticationCode.editText?.setText(prefs.authenticationCode)
secret.editText?.setText(prefs.secret)
passwordLen.editText?.setText(prefs.passwordLen.toString())
keyguardType.check(when (prefs.keyguardType) {
KeyguardType.A.value -> R.id.keyguardTypeA
Expand All @@ -64,59 +69,91 @@ class MainActivity : AppCompatActivity() {
})
toggle.isChecked = prefs.isEnabled
}
selectInterface()
}

private fun setup() {
binding.apply {
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab == null) return
setOff()
for (m in Mode.values()) {
if (m.value == tab.position) {
prefs.mode = m.value
break
}
}
selectInterface()
private fun initBiometric(): Boolean {
val authenticators = BiometricManager.Authenticators.BIOMETRIC_STRONG or
BiometricManager.Authenticators.DEVICE_CREDENTIAL
when (BiometricManager
.from(this)
.canAuthenticate(authenticators))
{
BiometricManager.BIOMETRIC_SUCCESS -> {}
else -> return false
}
val executor = ContextCompat.getMainExecutor(this)
val prompt = BiometricPrompt(
this,
executor,
object : BiometricPrompt.AuthenticationCallback()
{
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
finishAndRemoveTask()
}

override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
setup()
if (prefs.isShowProminentDisclosure) showProminentDisclosure()
}
})
action.editText?.doAfterTextChanged {
prefs.action = it?.toString() ?: ""
}
receiver.editText?.doAfterTextChanged {
prefs.receiver = it?.toString() ?: ""
}
authenticationCode.editText?.doAfterTextChanged {
prefs.authenticationCode = it?.toString() ?: ""
}
passwordLen.editText?.doAfterTextChanged {
try {
prefs.passwordLen = it?.toString()?.toInt() ?: return@doAfterTextChanged
} catch (exc: NumberFormatException) {}
}
keyguardType.setOnCheckedChangeListener { _, checkedId ->
prefs.keyguardType = when (checkedId) {
R.id.keyguardTypeA -> KeyguardType.A.value
R.id.keyguardTypeB -> KeyguardType.B.value
else -> return@setOnCheckedChangeListener
prompt.authenticate(BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.authentication))
.setConfirmationRequired(false)
.setAllowedAuthenticators(authenticators)
.build())
return true
}

private fun setup() = binding.apply {
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab == null) return
setOff()
for (m in Mode.values()) {
if (m.value == tab.position) {
prefs.mode = m.value
break
}
}
selectInterface()
}
toggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked && !hasPermissions()) {
toggle.isChecked = false
requestPermissions()
return@setOnCheckedChangeListener
}
prefs.isEnabled = isChecked

override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {}

})
action.editText?.doAfterTextChanged {
prefs.action = it?.toString()?.trim() ?: ""
}
receiver.editText?.doAfterTextChanged {
prefs.receiver = it?.toString()?.trim() ?: ""
}
secret.editText?.doAfterTextChanged {
prefs.secret = it?.toString()?.trim() ?: ""
}
passwordLen.editText?.doAfterTextChanged {
try { prefs.passwordLen = it?.toString()?.toInt() ?: return@doAfterTextChanged }
catch (exc: NumberFormatException) {}
}
keyguardType.setOnCheckedChangeListener { _, checkedId ->
prefs.keyguardType = when (checkedId) {
R.id.keyguardTypeA -> KeyguardType.A.value
R.id.keyguardTypeB -> KeyguardType.B.value
else -> return@setOnCheckedChangeListener
}
}
toggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked && !hasPermissions()) {
toggle.isChecked = false
requestPermissions()
return@setOnCheckedChangeListener
}
prefs.isEnabled = isChecked
}
}

private fun selectInterface() {
val v = when (prefs.mode) {
Mode.BROADCAST.value -> View.VISIBLE
Expand All @@ -126,7 +163,7 @@ class MainActivity : AppCompatActivity() {
binding.apply {
action.visibility = v
receiver.visibility = v
authenticationCode.visibility = v
secret.visibility = v
space1.visibility = v
space2.visibility = v
space3.visibility = v
Expand All @@ -135,8 +172,8 @@ class MainActivity : AppCompatActivity() {

private fun setOff() {
prefs.isEnabled = false
binding.toggle.isChecked = false
try { admin.remove() } catch (exc: SecurityException) {}
binding.toggle.isChecked = false
}

private fun update() {
Expand All @@ -148,7 +185,7 @@ class MainActivity : AppCompatActivity() {
).show()
}

private fun showProminentDisclosure() {
private fun showProminentDisclosure() =
MaterialAlertDialogBuilder(this)
.setTitle(R.string.prominent_disclosure_title)
.setMessage(R.string.prominent_disclosure_message)
Expand All @@ -159,7 +196,6 @@ class MainActivity : AppCompatActivity() {
finishAndRemoveTask()
}
.show()
}

private fun requestPermissions() {
if (!hasAccessibilityPermission()) {
Expand Down Expand Up @@ -191,4 +227,4 @@ class MainActivity : AppCompatActivity() {
}

private fun hasAdminPermission() = admin.isActive()
}
}
14 changes: 9 additions & 5 deletions app/src/main/java/me/lucky/duress/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
private const val MODE = "mode"
private const val ACTION = "action"
private const val RECEIVER = "receiver"
private const val AUTHENTICATION_CODE = "authentication_code"
private const val SECRET = "secret"
private const val PASSWORD_LEN = "password_len"
private const val KEYGUARD_TYPE = "keyguard_type"
private const val SHOW_PROMINENT_DISCLOSURE = "show_prominent_disclosure"

private const val FILE_NAME = "sec_shared_prefs"
// migration
private const val SERVICE_ENABLED = "service_enabled"
private const val AUTHENTICATION_CODE = "authentication_code"

fun new(ctx: Context) = Preferences(
ctx,
Expand Down Expand Up @@ -62,9 +63,12 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
get() = prefs.getString(RECEIVER, "") ?: ""
set(value) = prefs.edit { putString(RECEIVER, value) }

var authenticationCode: String
get() = prefs.getString(AUTHENTICATION_CODE, "") ?: ""
set(value) = prefs.edit { putString(AUTHENTICATION_CODE, value) }
var secret: String
get() = prefs.getString(
SECRET,
prefs.getString(AUTHENTICATION_CODE, "") ?: "",
) ?: ""
set(value) = prefs.edit { putString(SECRET, value) }

var passwordLen: Int
get() = prefs.getInt(PASSWORD_LEN, 0)
Expand Down Expand Up @@ -106,4 +110,4 @@ enum class Mode(val value: Int) {
enum class KeyguardType(val value: Int) {
A(0),
B(1),
}
}
Loading

0 comments on commit f2bd587

Please sign in to comment.