Skip to content

Commit

Permalink
exact password
Browse files Browse the repository at this point in the history
  • Loading branch information
lucky committed Jul 27, 2022
1 parent 70cf070 commit 5690004
Show file tree
Hide file tree
Showing 19 changed files with 178 additions and 123 deletions.
29 changes: 0 additions & 29 deletions .github/workflows/super-linter.yml

This file was deleted.

19 changes: 5 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,11 @@ Duress password trigger.
Tiny app to listen for a duress password on the lockscreen.
When found, it can send a broadcast message or wipe the device.

## Wasted
## Tested

You have to set:

* action: `me.lucky.wasted.action.TRIGGER`
* receiver: `me.lucky.wasted/.TriggerReceiver`
* authentication code: the code from Wasted
* password length: your actual password len plus at least two!

Do not forget to activate `Broadcast` trigger in Wasted.
* Emulator, Android 12
* Google Pixel 4a/5a, Android 12
* Samsung Tab S8, Android 12

## Permissions

Expand All @@ -43,9 +38,5 @@ Do not forget to activate `Broadcast` trigger in Wasted.
* [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)

This application is Free Software: You can use, study share and improve it at your will.
Specifically you can redistribute and/or modify it under the terms of the
[GNU General Public License v3](https://www.gnu.org/licenses/gpl.html) as published by the Free
Software Foundation.
[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html)
12 changes: 0 additions & 12 deletions SECURITY.md

This file was deleted.

4 changes: 2 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 8
versionName "1.0.7"
versionCode 9
versionName "1.0.8"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
167 changes: 136 additions & 31 deletions app/src/main/java/me/lucky/duress/AccessibilityService.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.lucky.duress

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
Expand All @@ -13,17 +14,23 @@ import me.lucky.duress.admin.DeviceAdminManager

class AccessibilityService : AccessibilityService() {
companion object {
private const val MIN_PASSWORD_LEN = 6
private const val MIN_KEYGUARD_LEN = 4
private const val MIN_LEN = MIN_KEYGUARD_LEN + 2
private const val KEY = "code"
private const val BUTTON_DELETE_TEXT = "DELETE"
private const val BUTTON_OK_TEXT = "OK"
private const val BUTTON_DELETE_DESC = "delete"
private const val BUTTON_OK_DESC = "ok"
private const val BUTTON_ENTER_DESC = "enter"
private const val WRONG_TEXT = "wrong"
private const val INCORRECT_TEXT = "incorrect"
private const val IGNORE_CHAR = ''
}

private lateinit var prefs: Preferences
private val admin by lazy { DeviceAdminManager(this) }
private val lockReceiver = LockReceiver(WeakReference(this))
private var keyguardManager: KeyguardManager? = null
private var enteredPwLen = 0
private var pos = 0
private var counter = mutableListOf<Boolean>()

override fun onCreate() {
super.onCreate()
Expand Down Expand Up @@ -68,38 +75,138 @@ class AccessibilityService : AccessibilityService() {

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
}
val kg = prefs.keyguardType
if (kg == KeyguardType.A.value)
serviceInfo = serviceInfo.apply {
eventTypes = AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED or
AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS
}
else if (kg == KeyguardType.B.value)
serviceInfo = serviceInfo.apply {
eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or
AccessibilityEvent.TYPE_VIEW_LONG_CLICKED or
AccessibilityEvent.TYPE_ANNOUNCEMENT
}
}

private fun checkKeyguardTypeA(event: AccessibilityEvent): Boolean {
if (event.eventType != AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED ||
!event.isPassword) return false
val passwordLen = prefs.passwordLen
if (passwordLen < MIN_PASSWORD_LEN ||
event.text.size != 1 ||
event.text[0].length < passwordLen) return false
return true
if (event.eventType != AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED &&
event.eventType != AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) return false
val passwordOrLen = prefs.passwordOrLen
return when (passwordOrLen.length < MIN_KEYGUARD_LEN) {
true -> checkKeyguardTypeAbyLen(event, passwordOrLen.toIntOrNull() ?: return false)
false -> checkKeyguardTypeAbyPassword(event, passwordOrLen)
}
}

private fun checkKeyguardTypeAbyLen(event: AccessibilityEvent, len: Int) =
len >= MIN_LEN && event.text.size == 1 && event.text[0].length >= len

private fun checkKeyguardTypeAbyPassword(event: AccessibilityEvent, pw: String): Boolean {
if (event.text.isEmpty()) {
reset()
return false
}
val text = event.text[0]
if (pos > text.length) {
if (pos > 0) {
pos--
counter.removeAt(pos)
}
return false
}
val c = text.elementAtOrNull(pos) ?: return false
if (c == IGNORE_CHAR) return false
var ok = false
counter.add(pos < pw.length && pw[pos] == c)
pos++
if (pos == pw.length) ok = counter.all { it }
return ok
}

private fun checkKeyguardTypeB(event: AccessibilityEvent): Boolean {
if (event.eventType != AccessibilityEvent.TYPE_VIEW_CLICKED &&
event.eventType != AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) return false
val passwordLen = prefs.passwordLen
if (passwordLen < MIN_PASSWORD_LEN || event.text.size != 1) return false
when (event.contentDescription.toString()) {
BUTTON_DELETE_TEXT -> {
if (event.eventType == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) enteredPwLen = 0
else if (enteredPwLen > 0) enteredPwLen -= 1
event.eventType != AccessibilityEvent.TYPE_VIEW_LONG_CLICKED &&
event.eventType != AccessibilityEvent.TYPE_ANNOUNCEMENT) return false
val passwordOrLen = prefs.passwordOrLen
return when (passwordOrLen.length < MIN_KEYGUARD_LEN) {
true -> checkKeyguardTypeBbyLen(event, passwordOrLen.toIntOrNull() ?: return false)
false -> checkKeyguardTypeBbyPassword(event, passwordOrLen)
}
}

private fun checkKeyguardTypeBbyLen(event: AccessibilityEvent, len: Int): Boolean {
if (len < MIN_LEN) return false
var ok = false
if (event.eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT) {
if (event.text.size != 1) return false
val text = event.text[0]
if (text.startsWith(WRONG_TEXT, true) ||
text.startsWith(INCORRECT_TEXT, true))
{
ok = pos >= len
pos = 0
}
return ok
}
when (event.contentDescription?.toString()?.lowercase()) {
BUTTON_DELETE_DESC -> {
if (event.eventType == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) pos = 0
else if (pos > 0) pos--
}
BUTTON_OK_DESC, BUTTON_ENTER_DESC -> {
ok = pos >= len
pos = 0
}
null -> pos = 0
else -> {
pos++
ok = pos >= len
}
}
return ok
}

private fun checkKeyguardTypeBbyPassword(event: AccessibilityEvent, pw: String): Boolean {
var ok = false
if (event.eventType == AccessibilityEvent.TYPE_ANNOUNCEMENT) {
if (event.text.size != 1) return false
val text = event.text[0]
if (text.startsWith(WRONG_TEXT, true) ||
text.startsWith(INCORRECT_TEXT, true))
{
if (pos == pw.length) ok = counter.all { it }
reset()
}
BUTTON_OK_TEXT -> enteredPwLen = 0
else -> enteredPwLen += 1
return ok
}
if (enteredPwLen < passwordLen) return false
return true
when (event.contentDescription?.toString()?.lowercase()) {
BUTTON_DELETE_DESC -> {
if (event.eventType == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
reset()
} else if (pos > 0) {
pos--
counter.removeAt(pos)
}
}
BUTTON_OK_DESC, BUTTON_ENTER_DESC -> {
if (pos == pw.length) ok = counter.all { it }
reset()
}
null -> reset()
else -> {
counter.add(pos < pw.length && pw[pos] == event.contentDescription.firstOrNull())
pos++
if (pos == pw.length) ok = counter.all { it }
}
}
return ok
}

private fun reset() {
pos = 0
counter.clear()
}

private fun sendNotification() = NotificationManager(this).send()
Expand All @@ -124,17 +231,15 @@ class AccessibilityService : AccessibilityService() {
})
}

private fun wipeData() {
try { admin.wipeData() } catch (exc: SecurityException) {}
}
private fun wipeData() = try { admin.wipeData() } catch (exc: SecurityException) {}

private class LockReceiver(
private val service: WeakReference<me.lucky.duress.AccessibilityService>,
) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != Intent.ACTION_USER_PRESENT &&
intent?.action != Intent.ACTION_SCREEN_OFF) return
service.get()?.enteredPwLen = 0
service.get()?.reset()
}
}
}
7 changes: 3 additions & 4 deletions app/src/main/java/me/lucky/duress/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class MainActivity : AppCompatActivity() {
action.editText?.setText(prefs.action)
receiver.editText?.setText(prefs.receiver)
secret.editText?.setText(prefs.secret)
passwordLen.editText?.setText(prefs.passwordLen.toString())
passwordOrLen.editText?.setText(prefs.passwordOrLen)
keyguardType.check(when (prefs.keyguardType) {
KeyguardType.A.value -> R.id.keyguardTypeA
KeyguardType.B.value -> R.id.keyguardTypeB
Expand Down Expand Up @@ -142,9 +142,8 @@ class MainActivity : AppCompatActivity() {
secret.editText?.doAfterTextChanged {
prefs.secret = it?.toString()?.trim() ?: ""
}
passwordLen.editText?.doAfterTextChanged {
try { prefs.passwordLen = it?.toString()?.toInt() ?: return@doAfterTextChanged }
catch (exc: NumberFormatException) {}
passwordOrLen.editText?.doAfterTextChanged {
prefs.passwordOrLen = it?.toString()?.trim() ?: ""
}
keyguardType.setOnCheckedChangeListener { _, checkedId ->
prefs.keyguardType = when (checkedId) {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/me/lucky/duress/NotificationManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class NotificationManager(private val ctx: Context) {
manager.createNotificationChannel(
NotificationChannelCompat.Builder(
CHANNEL_DEFAULT_ID,
NotificationManagerCompat.IMPORTANCE_LOW,
NotificationManagerCompat.IMPORTANCE_HIGH,
).setName(ctx.getString(R.string.notification_channel_default_name)).build())
}

Expand All @@ -29,7 +29,7 @@ class NotificationManager(private val ctx: Context) {
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle(ctx.getString(R.string.notification_title))
.setContentText(ctx.getString(android.R.string.ok))
.setPriority(NotificationCompat.PRIORITY_LOW)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_STATUS)
.setShowWhen(true)
.setAutoCancel(true)
Expand Down
14 changes: 6 additions & 8 deletions app/src/main/java/me/lucky/duress/Preferences.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
private const val ACTION = "action"
private const val RECEIVER = "receiver"
private const val SECRET = "secret"
private const val PASSWORD_LEN = "password_len"
private const val PASSWORD_OR_LEN = "password_or_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"
private const val PASSWORD_LEN = "password_len"

fun new(ctx: Context) = Preferences(
ctx,
Expand Down Expand Up @@ -64,15 +65,12 @@ class Preferences(ctx: Context, encrypted: Boolean = true) {
set(value) = prefs.edit { putString(RECEIVER, value) }

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

var passwordLen: Int
get() = prefs.getInt(PASSWORD_LEN, 0)
set(value) = prefs.edit { putInt(PASSWORD_LEN, value) }
var passwordOrLen: String
get() = prefs.getString(PASSWORD_OR_LEN, prefs.getString(PASSWORD_LEN, "")) ?: ""
set(value) = prefs.edit { putString(PASSWORD_OR_LEN, value) }

var keyguardType: Int
get() = prefs.getInt(KEYGUARD_TYPE, KeyguardType.A.value)
Expand Down
Loading

0 comments on commit 5690004

Please sign in to comment.