forked from hardcoresushi/DroidFS
Switching to androix.biometric
This commit is contained in:
parent
1bde428ace
commit
9a8023fc33
@ -17,6 +17,7 @@ android {
|
||||
versionCode 4
|
||||
versionName "1.1.9"
|
||||
|
||||
android.ndkVersion = "21.2.6472646"
|
||||
ndk {
|
||||
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
@ -48,12 +49,13 @@ dependencies {
|
||||
implementation "androidx.appcompat:appcompat:1.2.0"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.2"
|
||||
|
||||
implementation "androidx.sqlite:sqlite:2.1.0"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.sqlite:sqlite-ktx:2.1.0"
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
|
||||
implementation "com.jaredrummler:cyanea:1.0.2"
|
||||
implementation "com.github.bumptech.glide:glide:4.11.0"
|
||||
implementation "com.google.android.exoplayer:exoplayer-core:2.11.7"
|
||||
implementation "com.google.android.exoplayer:exoplayer-ui:2.11.7"
|
||||
implementation "com.otaliastudios:cameraview:2.6.3"
|
||||
implementation "androidx.biometric:biometric:1.0.1"
|
||||
}
|
||||
|
@ -9,12 +9,18 @@
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-feature android:name="android.hardware.camera.any" android:required="false"/>
|
||||
<uses-feature android:name="android.hardware.fingerprint" android:required="false"/>
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" tools:node="remove"/> <!--removing this permission automatically added by exoplayer-->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera.any"
|
||||
android:required="false" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.fingerprint"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.ACCESS_NETWORK_STATE"
|
||||
tools:node="remove" /> <!--removing this permission automatically added by exoplayer-->
|
||||
|
||||
<application
|
||||
android:name=".ColoredApplication"
|
||||
@ -24,7 +30,9 @@
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".CameraActivity" android:screenOrientation="nosensor"/>
|
||||
<activity
|
||||
android:name=".CameraActivity"
|
||||
android:screenOrientation="nosensor" />
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
|
@ -10,6 +10,7 @@ import sushi.hardcore.droidfs.widgets.ThemeColor
|
||||
|
||||
open class BaseActivity: CyaneaAppCompatActivity() {
|
||||
protected lateinit var sharedPrefs: SharedPreferences
|
||||
protected var isRecreating = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
@ -24,6 +25,7 @@ open class BaseActivity: CyaneaAppCompatActivity() {
|
||||
fun changeThemeColor(themeColor: Int? = null){
|
||||
val accentColor = themeColor ?: ThemeColor.getThemeColor(this)
|
||||
val backgroundColor = ContextCompat.getColor(this, R.color.backgroundColor)
|
||||
isRecreating = true
|
||||
cyanea.edit{
|
||||
accent(accentColor)
|
||||
//accentDark(themeColor)
|
||||
|
@ -11,36 +11,24 @@ import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_change_password.*
|
||||
import kotlinx.android.synthetic.main.activity_change_password.saved_path_listview
|
||||
import kotlinx.android.synthetic.main.checkboxes_section.*
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import kotlinx.android.synthetic.main.volume_path_section.*
|
||||
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.util.*
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class ChangePasswordActivity : BaseActivity() {
|
||||
class ChangePasswordActivity : VolumeActionActivity() {
|
||||
companion object {
|
||||
private const val PICK_DIRECTORY_REQUEST_CODE = 1
|
||||
}
|
||||
private lateinit var savedVolumesAdapter: SavedVolumesAdapter
|
||||
private lateinit var fingerprintPasswordHashSaver: FingerprintPasswordHashSaver
|
||||
private lateinit var rootCipherDir: String
|
||||
private var usf_fingerprint = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_change_password)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
} else {
|
||||
WidgetUtil.hide(checkbox_save_password)
|
||||
}
|
||||
setupActionBar()
|
||||
setupFingerprintStuff()
|
||||
savedVolumesAdapter = SavedVolumesAdapter(this, sharedPrefs)
|
||||
if (savedVolumesAdapter.count > 0){
|
||||
saved_path_listview.adapter = savedVolumesAdapter
|
||||
@ -147,15 +135,15 @@ class ChangePasswordActivity : BaseActivity() {
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
val oldPassword = edit_old_password.text.toString().toCharArray()
|
||||
var returnedHash: ByteArray? = null
|
||||
if (usf_fingerprint && checkbox_save_password.isChecked) {
|
||||
if (checkbox_save_password.isChecked) {
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
}
|
||||
var changePasswordImmediately = true
|
||||
if (givenHash == null) {
|
||||
val cipherText = sharedPrefs.getString(rootCipherDir, null)
|
||||
if (cipherText != null) { //password hash saved
|
||||
if (cipherText != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //password hash saved
|
||||
stopTask {
|
||||
fingerprintPasswordHashSaver.decrypt(cipherText, rootCipherDir, ::changePassword)
|
||||
loadPasswordHash(cipherText, ::changePassword)
|
||||
}
|
||||
changePasswordImmediately = false
|
||||
}
|
||||
@ -177,9 +165,11 @@ class ChangePasswordActivity : BaseActivity() {
|
||||
if (checkbox_remember_path.isChecked) {
|
||||
savedVolumesAdapter.addVolumePath(rootCipherDir)
|
||||
}
|
||||
if (checkbox_save_password.isChecked && returnedHash != null) {
|
||||
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir) { _ ->
|
||||
stopTask { onPasswordChanged() }
|
||||
if (checkbox_save_password.isChecked && returnedHash != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
|
||||
stopTask {
|
||||
savePasswordHash(returnedHash) {
|
||||
onPasswordChanged()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopTask { onPasswordChanged() }
|
||||
@ -213,32 +203,12 @@ class ChangePasswordActivity : BaseActivity() {
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onClickSavePasswordHash(view: View) {
|
||||
if (checkbox_save_password.isChecked){
|
||||
if (!fingerprintPasswordHashSaver.canAuthenticate()){
|
||||
checkbox_save_password.isChecked = false
|
||||
} else {
|
||||
checkbox_remember_path.isChecked = checkbox_remember_path.isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRememberPath(view: View) {
|
||||
if (!checkbox_remember_path.isChecked){
|
||||
checkbox_save_password.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (::fingerprintPasswordHashSaver.isInitialized && fingerprintPasswordHashSaver.isListening){
|
||||
fingerprintPasswordHashSaver.stopListening()
|
||||
if (fingerprintPasswordHashSaver.fingerprintFragment.isAdded){
|
||||
fingerprintPasswordHashSaver.fingerprintFragment.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Wiper.wipeEditText(edit_old_password)
|
||||
|
@ -9,34 +9,26 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_create.*
|
||||
import kotlinx.android.synthetic.main.checkboxes_section.*
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import kotlinx.android.synthetic.main.volume_path_section.*
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.util.*
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.LoadingTask
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class CreateActivity : BaseActivity() {
|
||||
class CreateActivity : VolumeActionActivity() {
|
||||
companion object {
|
||||
private const val PICK_DIRECTORY_REQUEST_CODE = 1
|
||||
}
|
||||
private lateinit var fingerprintPasswordHashSaver: FingerprintPasswordHashSaver
|
||||
private lateinit var rootCipherDir: String
|
||||
private var sessionID = -1
|
||||
private var usf_fingerprint = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_create)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
} else {
|
||||
WidgetUtil.hide(checkbox_save_password)
|
||||
}
|
||||
setupActionBar()
|
||||
setupFingerprintStuff()
|
||||
edit_password_confirm.setOnEditorActionListener { v, _, _ ->
|
||||
onClickCreate(v)
|
||||
true
|
||||
@ -127,7 +119,7 @@ class CreateActivity : BaseActivity() {
|
||||
if (goodDirectory) {
|
||||
if (GocryptfsVolume.createVolume(rootCipherDir, password, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator)) {
|
||||
var returnedHash: ByteArray? = null
|
||||
if (usf_fingerprint && checkbox_save_password.isChecked){
|
||||
if (checkbox_save_password.isChecked){
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
}
|
||||
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
|
||||
@ -146,9 +138,11 @@ class CreateActivity : BaseActivity() {
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
if (checkbox_save_password.isChecked && returnedHash != null){
|
||||
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir){ _ ->
|
||||
stopTask { startExplorer() }
|
||||
if (checkbox_save_password.isChecked && returnedHash != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
|
||||
stopTask {
|
||||
savePasswordHash(returnedHash) {
|
||||
startExplorer()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopTask { startExplorer() }
|
||||
@ -191,32 +185,12 @@ class CreateActivity : BaseActivity() {
|
||||
.show()
|
||||
}
|
||||
|
||||
fun onClickSavePasswordHash(view: View) {
|
||||
if (checkbox_save_password.isChecked){
|
||||
if (!fingerprintPasswordHashSaver.canAuthenticate()){
|
||||
checkbox_save_password.isChecked = false
|
||||
} else {
|
||||
checkbox_remember_path.isChecked = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRememberPath(view: View) {
|
||||
if (!checkbox_remember_path.isChecked){
|
||||
checkbox_save_password.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
if (::fingerprintPasswordHashSaver.isInitialized && fingerprintPasswordHashSaver.isListening){
|
||||
fingerprintPasswordHashSaver.stopListening()
|
||||
if (fingerprintPasswordHashSaver.fingerprintFragment.isAdded){
|
||||
fingerprintPasswordHashSaver.fingerprintFragment.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Wiper.wipeEditText(edit_password)
|
||||
|
@ -22,22 +22,30 @@ class MainActivity : BaseActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
setSupportActionBar(toolbar)
|
||||
val metrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(metrics)
|
||||
image_logo.layoutParams.height = (metrics.heightPixels/2.2).toInt()
|
||||
Glide.with(this).load(R.drawable.logo).into(image_logo)
|
||||
if (!isRecreating){
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) +
|
||||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE), STORAGE_PERMISSIONS_REQUEST)
|
||||
} else {
|
||||
onStoragePermissionGranted()
|
||||
}
|
||||
} else {
|
||||
onStoragePermissionGranted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStoragePermissionGranted(){
|
||||
if (checkStorageAvailability()){
|
||||
checkFirstOpening()
|
||||
}
|
||||
}
|
||||
}
|
||||
val metrics = DisplayMetrics()
|
||||
windowManager.defaultDisplay.getMetrics(metrics)
|
||||
image_logo.layoutParams.height = (metrics.heightPixels/2.2).toInt()
|
||||
Glide.with(this).load(R.drawable.logo).into(image_logo)
|
||||
}
|
||||
|
||||
private fun checkStorageAvailability(): Boolean {
|
||||
val state = Environment.getExternalStorageState()
|
||||
@ -81,8 +89,7 @@ class MainActivity : BaseActivity() {
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> finish() }.show()
|
||||
} else {
|
||||
checkStorageAvailability()
|
||||
checkFirstOpening()
|
||||
onStoragePermissionGranted()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,43 +12,31 @@ import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_open.*
|
||||
import kotlinx.android.synthetic.main.activity_open.saved_path_listview
|
||||
import kotlinx.android.synthetic.main.checkboxes_section.*
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import kotlinx.android.synthetic.main.volume_path_section.*
|
||||
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.util.*
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class OpenActivity : BaseActivity() {
|
||||
class OpenActivity : VolumeActionActivity() {
|
||||
companion object {
|
||||
private const val PICK_DIRECTORY_REQUEST_CODE = 1
|
||||
}
|
||||
private lateinit var savedVolumesAdapter: SavedVolumesAdapter
|
||||
private lateinit var fingerprintPasswordHashSaver: FingerprintPasswordHashSaver
|
||||
private lateinit var rootCipherDir: String
|
||||
private var sessionID = -1
|
||||
private var isStartingActivity = false
|
||||
private var isFinishingIntentionally = false
|
||||
private var usf_fingerprint = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_open)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
} else {
|
||||
WidgetUtil.hide(checkbox_save_password)
|
||||
}
|
||||
setupActionBar()
|
||||
setupFingerprintStuff()
|
||||
savedVolumesAdapter = SavedVolumesAdapter(this, sharedPrefs)
|
||||
if (savedVolumesAdapter.count > 0){
|
||||
saved_path_listview.adapter = savedVolumesAdapter
|
||||
@ -56,8 +44,8 @@ class OpenActivity : BaseActivity() {
|
||||
rootCipherDir = savedVolumesAdapter.getItem(position)
|
||||
edit_volume_path.setText(rootCipherDir)
|
||||
val cipherText = sharedPrefs.getString(rootCipherDir, null)
|
||||
if (cipherText != null){ //password hash saved
|
||||
fingerprintPasswordHashSaver.decrypt(cipherText, rootCipherDir, ::openUsingPasswordHash)
|
||||
if (cipherText != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ //password hash saved
|
||||
loadPasswordHash(cipherText, ::openUsingPasswordHash)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -165,7 +153,7 @@ class OpenActivity : BaseActivity() {
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
val password = edit_password.text.toString().toCharArray()
|
||||
var returnedHash: ByteArray? = null
|
||||
if (usf_fingerprint && checkbox_save_password.isChecked){
|
||||
if (checkbox_save_password.isChecked){
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
}
|
||||
sessionID = GocryptfsVolume.init(rootCipherDir, password, null, returnedHash)
|
||||
@ -173,9 +161,11 @@ class OpenActivity : BaseActivity() {
|
||||
if (checkbox_remember_path.isChecked) {
|
||||
savedVolumesAdapter.addVolumePath(rootCipherDir)
|
||||
}
|
||||
if (checkbox_save_password.isChecked && returnedHash != null){
|
||||
fingerprintPasswordHashSaver.encryptAndSave(returnedHash, rootCipherDir) { _ ->
|
||||
stopTask { startExplorer() }
|
||||
if (checkbox_save_password.isChecked && returnedHash != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
|
||||
stopTask {
|
||||
savePasswordHash(returnedHash) {
|
||||
startExplorer()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stopTask { startExplorer() }
|
||||
@ -238,16 +228,6 @@ class OpenActivity : BaseActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
fun onClickSavePasswordHash(view: View) {
|
||||
if (checkbox_save_password.isChecked){
|
||||
if (!fingerprintPasswordHashSaver.canAuthenticate()){
|
||||
checkbox_save_password.isChecked = false
|
||||
} else {
|
||||
checkbox_remember_path.isChecked = checkbox_remember_path.isEnabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onClickRememberPath(view: View) {
|
||||
if (!checkbox_remember_path.isChecked){
|
||||
checkbox_save_password.isChecked = false
|
||||
@ -268,12 +248,6 @@ class OpenActivity : BaseActivity() {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
if (::fingerprintPasswordHashSaver.isInitialized && fingerprintPasswordHashSaver.isListening){
|
||||
fingerprintPasswordHashSaver.stopListening()
|
||||
if (fingerprintPasswordHashSaver.fingerprintFragment.isAdded){
|
||||
fingerprintPasswordHashSaver.fingerprintFragment.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
253
app/src/main/java/sushi/hardcore/droidfs/VolumeActionActivity.kt
Normal file
253
app/src/main/java/sushi/hardcore/droidfs/VolumeActionActivity.kt
Normal file
@ -0,0 +1,253 @@
|
||||
package sushi.hardcore.droidfs
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.content.ContextCompat
|
||||
import kotlinx.android.synthetic.main.checkboxes_section.*
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
|
||||
open class VolumeActionActivity : BaseActivity() {
|
||||
protected lateinit var rootCipherDir: String
|
||||
private var usf_fingerprint = false
|
||||
private var biometricCanAuthenticateCode: Int = -1
|
||||
private lateinit var biometricManager: BiometricManager
|
||||
private lateinit var biometricPrompt: BiometricPrompt
|
||||
private lateinit var keyStore: KeyStore
|
||||
private lateinit var key: SecretKey
|
||||
private lateinit var cipher: Cipher
|
||||
private var actionMode: Int? = null
|
||||
private lateinit var onAuthenticationResult: (success: Boolean) -> Unit
|
||||
private lateinit var onPasswordDecrypted: (password: ByteArray) -> Unit
|
||||
private lateinit var dataToProcess: ByteArray
|
||||
companion object {
|
||||
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
|
||||
private const val KEY_ALIAS = "Hash Key"
|
||||
private const val KEY_SIZE = 256
|
||||
private const val GCM_TAG_LEN = 128
|
||||
}
|
||||
|
||||
protected fun setupFingerprintStuff(){
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
biometricManager = BiometricManager.from(this)
|
||||
biometricCanAuthenticateCode = canAuthenticate()
|
||||
if (biometricCanAuthenticateCode == 0){
|
||||
val executor = ContextCompat.getMainExecutor(this)
|
||||
val activityContext = this
|
||||
val callback = object: BiometricPrompt.AuthenticationCallback() {
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
Toast.makeText(applicationContext, errString, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
Toast.makeText(applicationContext, R.string.authentication_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
||||
super.onAuthenticationSucceeded(result)
|
||||
val cipherObject = result.cryptoObject?.cipher
|
||||
if (cipherObject != null){
|
||||
try {
|
||||
when (actionMode) {
|
||||
Cipher.ENCRYPT_MODE -> {
|
||||
val cipherText = cipherObject.doFinal(dataToProcess)
|
||||
val encodedCipherText = Base64.encodeToString(cipherText, 0)
|
||||
val encodedIv = Base64.encodeToString(cipherObject.iv, 0)
|
||||
val sharedPrefsEditor = sharedPrefs.edit()
|
||||
sharedPrefsEditor.putString(rootCipherDir, "$encodedIv:$encodedCipherText")
|
||||
sharedPrefsEditor.apply()
|
||||
onAuthenticationResult(true)
|
||||
}
|
||||
Cipher.DECRYPT_MODE -> {
|
||||
try {
|
||||
val plainText = cipherObject.doFinal(dataToProcess)
|
||||
onPasswordDecrypted(plainText)
|
||||
} catch (e: AEADBadTagException){
|
||||
ColoredAlertDialogBuilder(activityContext)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.MAC_verification_failed)
|
||||
.setPositiveButton(R.string.reset_hash_storage) { _, _ ->
|
||||
resetHashStorage()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalBlockSizeException){
|
||||
ColoredAlertDialogBuilder(activityContext)
|
||||
.setTitle(R.string.illegal_block_size_exception)
|
||||
.setMessage(R.string.illegal_block_size_exception_msg)
|
||||
.setPositiveButton(R.string.reset_hash_storage) { _, _ ->
|
||||
resetHashStorage()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(applicationContext, R.string.error_cipher_null, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
biometricPrompt = BiometricPrompt(this, executor, callback)
|
||||
}
|
||||
} else {
|
||||
WidgetUtil.hide(checkbox_save_password)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun setupActionBar(){
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
private fun canAuthenticate(): Int {
|
||||
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
return if (!keyguardManager.isKeyguardSecure) {
|
||||
1
|
||||
} else {
|
||||
when (biometricManager.canAuthenticate()){
|
||||
BiometricManager.BIOMETRIC_SUCCESS -> 0
|
||||
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> 2
|
||||
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> 3
|
||||
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> 4
|
||||
else -> -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun printAuthenticateImpossibleError() {
|
||||
Toast.makeText(this, when (biometricCanAuthenticateCode){
|
||||
1 -> R.string.fingerprint_error_no_fingerprints
|
||||
2 -> R.string.fingerprint_error_hw_not_present
|
||||
3 -> R.string.fingerprint_error_hw_not_available
|
||||
4 -> R.string.fingerprint_error_no_fingerprints
|
||||
else -> R.string.error
|
||||
}, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun onClickSavePasswordHash(view: View) {
|
||||
if (checkbox_save_password.isChecked){
|
||||
if (biometricCanAuthenticateCode == 0){
|
||||
checkbox_remember_path.isChecked = checkbox_remember_path.isEnabled
|
||||
} else {
|
||||
checkbox_save_password.isChecked = false
|
||||
printAuthenticateImpossibleError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun prepareCipher() {
|
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
|
||||
keyStore.load(null)
|
||||
key = if (keyStore.containsAlias(KEY_ALIAS)){
|
||||
keyStore.getKey(KEY_ALIAS, null) as SecretKey
|
||||
} else {
|
||||
val builder = KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
builder.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
builder.setKeySize(KEY_SIZE)
|
||||
builder.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
builder.setUserAuthenticationRequired(true)
|
||||
val keyGenerator = KeyGenerator.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES,
|
||||
ANDROID_KEY_STORE
|
||||
)
|
||||
keyGenerator.init(builder.build())
|
||||
keyGenerator.generateKey()
|
||||
}
|
||||
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES+"/"+KeyProperties.BLOCK_MODE_GCM+"/"+KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
}
|
||||
|
||||
private fun alertKeyPermanentlyInvalidatedException(){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.key_permanently_invalidated_exception)
|
||||
.setMessage(R.string.key_permanently_invalidated_exception_msg)
|
||||
.setPositiveButton(R.string.reset_hash_storage) { _, _ ->
|
||||
resetHashStorage()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
protected fun savePasswordHash(plainText: ByteArray, onAuthenticationResult: (success: Boolean) -> Unit){
|
||||
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(rootCipherDir)
|
||||
.setSubtitle(getString(R.string.encrypt_action_description))
|
||||
.setDescription(getString(R.string.fingerprint_instruction))
|
||||
.setNegativeButtonText(getString(R.string.cancel))
|
||||
.setDeviceCredentialAllowed(false)
|
||||
.setConfirmationRequired(false)
|
||||
.build()
|
||||
if (!::cipher.isInitialized){
|
||||
prepareCipher()
|
||||
}
|
||||
actionMode = Cipher.ENCRYPT_MODE
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
||||
this.onAuthenticationResult = onAuthenticationResult
|
||||
dataToProcess = plainText
|
||||
biometricPrompt.authenticate(biometricPromptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||
} catch (e: KeyPermanentlyInvalidatedException){
|
||||
alertKeyPermanentlyInvalidatedException()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
protected fun loadPasswordHash(cipherText: String, onPasswordDecrypted: (password: ByteArray) -> Unit){
|
||||
val biometricPromptInfo = BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(rootCipherDir)
|
||||
.setSubtitle(getString(R.string.decrypt_action_description))
|
||||
.setDescription(getString(R.string.fingerprint_instruction))
|
||||
.setNegativeButtonText(getString(R.string.cancel))
|
||||
.setDeviceCredentialAllowed(false)
|
||||
.setConfirmationRequired(false)
|
||||
.build()
|
||||
this.onPasswordDecrypted = onPasswordDecrypted
|
||||
actionMode = Cipher.DECRYPT_MODE
|
||||
if (!::cipher.isInitialized){
|
||||
prepareCipher()
|
||||
}
|
||||
val encodedElements = cipherText.split(":")
|
||||
dataToProcess = Base64.decode(encodedElements[1], 0)
|
||||
val iv = Base64.decode(encodedElements[0], 0)
|
||||
val gcmSpec = GCMParameterSpec(GCM_TAG_LEN, iv)
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec)
|
||||
biometricPrompt.authenticate(biometricPromptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||
} catch (e: KeyPermanentlyInvalidatedException){
|
||||
alertKeyPermanentlyInvalidatedException()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetHashStorage() {
|
||||
keyStore.deleteEntry(KEY_ALIAS)
|
||||
val savedVolumePaths = sharedPrefs.getStringSet(ConstValues.saved_volumes_key, HashSet<String>()) as Set<String>
|
||||
val sharedPrefsEditor = sharedPrefs.edit()
|
||||
for (path in savedVolumePaths){
|
||||
val savedHash = sharedPrefs.getString(path, null)
|
||||
if (savedHash != null){
|
||||
sharedPrefsEditor.remove(path)
|
||||
}
|
||||
}
|
||||
sharedPrefsEditor.apply()
|
||||
Toast.makeText(this, R.string.hash_storage_reset, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import android.content.Context
|
||||
import sushi.hardcore.droidfs.R
|
||||
|
||||
class OpenAsDialogAdapter(context: Context, showOpenWithExternalApp: Boolean) : IconTextDialogAdapter(context) {
|
||||
private val openAsItems = mutableListOf(
|
||||
private val openAsItems: MutableList<List<Any>> = mutableListOf(
|
||||
listOf("image", R.string.image, R.drawable.icon_file_image),
|
||||
listOf("video", R.string.video, R.drawable.icon_file_video),
|
||||
listOf("audio", R.string.audio, R.drawable.icon_file_audio),
|
||||
|
@ -1,31 +0,0 @@
|
||||
package sushi.hardcore.droidfs.fingerprint_stuff
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import sushi.hardcore.droidfs.R
|
||||
|
||||
class FingerprintFragment(val volume_path: String, val action_description: String, val callbackOnDismiss: () -> Unit) : DialogFragment() {
|
||||
lateinit var image_fingerprint: ImageView
|
||||
lateinit var text_instruction: TextView
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_fingerprint, container, false)
|
||||
val text_volume = view.findViewById<TextView>(R.id.text_volume)
|
||||
text_volume.text = volume_path
|
||||
image_fingerprint = view.findViewById(R.id.image_fingerprint)
|
||||
val text_action_description = view.findViewById<TextView>(R.id.text_action_description)
|
||||
text_action_description.text = action_description
|
||||
text_instruction = view.findViewById(R.id.text_instruction)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
callbackOnDismiss()
|
||||
}
|
||||
}
|
@ -1,257 +0,0 @@
|
||||
package sushi.hardcore.droidfs.fingerprint_stuff
|
||||
|
||||
import android.Manifest
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.hardware.biometrics.BiometricPrompt
|
||||
import android.hardware.fingerprint.FingerprintManager
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.os.Handler
|
||||
import android.security.keystore.KeyGenParameterSpec
|
||||
import android.security.keystore.KeyProperties
|
||||
import android.util.Base64
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.*
|
||||
import javax.crypto.spec.GCMParameterSpec
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
class FingerprintPasswordHashSaver(private val activityContext: AppCompatActivity, private val shared_prefs: SharedPreferences) {
|
||||
private var isPrepared = false
|
||||
var isListening = false
|
||||
private var authenticationFailed = false
|
||||
private val sharedPrefsEditor: SharedPreferences.Editor = shared_prefs.edit()
|
||||
private val fingerprintManager = activityContext.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager
|
||||
private lateinit var rootCipherDir: String
|
||||
private lateinit var actionDescription: String
|
||||
private lateinit var onAuthenticationResult: (success: Boolean) -> Unit
|
||||
private lateinit var onPasswordDecrypted: (password: ByteArray) -> Unit
|
||||
private lateinit var keyStore: KeyStore
|
||||
private lateinit var key: SecretKey
|
||||
lateinit var fingerprintFragment: FingerprintFragment
|
||||
private val handler = Handler()
|
||||
private lateinit var cancellationSignal: CancellationSignal
|
||||
private var actionMode: Int? = null
|
||||
private lateinit var dataToProcess: ByteArray
|
||||
private lateinit var cipher: Cipher
|
||||
companion object {
|
||||
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
|
||||
private const val KEY_ALIAS = "Hash Key"
|
||||
private const val KEY_SIZE = 256
|
||||
private const val GCM_TAG_LEN = 128
|
||||
private const val CIPHER_TYPE = "AES/GCM/NoPadding"
|
||||
private const val SUCCESS_DISMISS_DIALOG_DELAY: Long = 400
|
||||
private const val FAILED_DISMISS_DIALOG_DELAY: Long = 800
|
||||
}
|
||||
private fun resetHashStorage() {
|
||||
keyStore.deleteEntry(KEY_ALIAS)
|
||||
val savedVolumePaths = shared_prefs.getStringSet(ConstValues.saved_volumes_key, HashSet<String>()) as Set<String>
|
||||
for (path in savedVolumePaths){
|
||||
val savedHash = shared_prefs.getString(path, null)
|
||||
if (savedHash != null){
|
||||
sharedPrefsEditor.remove(path)
|
||||
}
|
||||
}
|
||||
sharedPrefsEditor.apply()
|
||||
Toast.makeText(activityContext, activityContext.getString(R.string.hash_storage_reset), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
fun canAuthenticate(): Boolean{
|
||||
if (ContextCompat.checkSelfPermission(activityContext, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED){
|
||||
Toast.makeText(activityContext, activityContext.getString(R.string.fingerprint_perm_denied), Toast.LENGTH_SHORT).show()
|
||||
} else if (!fingerprintManager.isHardwareDetected){
|
||||
Toast.makeText(activityContext, activityContext.getString(R.string.no_fingerprint_sensor), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
val keyguardManager = activityContext.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
if (!keyguardManager.isKeyguardSecure || !fingerprintManager.hasEnrolledFingerprints()) {
|
||||
Toast.makeText(activityContext, activityContext.getString(R.string.no_fingerprint_configured), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
private fun prepare() {
|
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
|
||||
keyStore.load(null)
|
||||
key = if (keyStore.containsAlias(KEY_ALIAS)){
|
||||
keyStore.getKey(KEY_ALIAS, null) as SecretKey
|
||||
} else {
|
||||
val builder = KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||
builder.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
||||
builder.setKeySize(KEY_SIZE)
|
||||
builder.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
||||
builder.setUserAuthenticationRequired(true)
|
||||
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
|
||||
keyGenerator.init(builder.build())
|
||||
keyGenerator.generateKey()
|
||||
}
|
||||
cipher = Cipher.getInstance(CIPHER_TYPE)
|
||||
fingerprintFragment = FingerprintFragment(rootCipherDir, actionDescription, ::stopListening)
|
||||
isPrepared = true
|
||||
}
|
||||
fun encryptAndSave(plainText: ByteArray, root_cipher_dir: String, onAuthenticationResult: (success: Boolean) -> Unit){
|
||||
this.rootCipherDir = root_cipher_dir
|
||||
this.actionDescription = activityContext.getString(R.string.encrypt_action_description)
|
||||
this.onAuthenticationResult = onAuthenticationResult
|
||||
if (!isPrepared){
|
||||
prepare()
|
||||
}
|
||||
dataToProcess = plainText
|
||||
actionMode = Cipher.ENCRYPT_MODE
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key)
|
||||
startListening()
|
||||
}
|
||||
fun decrypt(cipherText: String, root_cipher_dir: String, onPasswordDecrypted: (password: ByteArray) -> Unit){
|
||||
this.rootCipherDir = root_cipher_dir
|
||||
this.actionDescription = activityContext.getString(R.string.decrypt_action_description)
|
||||
this.onPasswordDecrypted = onPasswordDecrypted
|
||||
if (!isPrepared){
|
||||
prepare()
|
||||
}
|
||||
actionMode = Cipher.DECRYPT_MODE
|
||||
val encodedElements = cipherText.split(":")
|
||||
dataToProcess = Base64.decode(encodedElements[1], 0)
|
||||
val iv = Base64.decode(encodedElements[0], 0)
|
||||
val gcmSpec = GCMParameterSpec(GCM_TAG_LEN, iv)
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec)
|
||||
startListening()
|
||||
}
|
||||
|
||||
private fun startListening(){
|
||||
cancellationSignal = CancellationSignal()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val biometricPrompt = BiometricPrompt.Builder(activityContext)
|
||||
.setTitle(rootCipherDir)
|
||||
.setSubtitle(actionDescription)
|
||||
.setDescription(activityContext.getString(R.string.fingerprint_instruction))
|
||||
.setNegativeButton(activityContext.getString(R.string.cancel), activityContext.mainExecutor, DialogInterface.OnClickListener{_, _ ->
|
||||
cancellationSignal.cancel()
|
||||
callbackOnAuthenticationFailed() //toggle on onAuthenticationResult
|
||||
}).build()
|
||||
biometricPrompt.authenticate(BiometricPrompt.CryptoObject(cipher), cancellationSignal, activityContext.mainExecutor, object: BiometricPrompt.AuthenticationCallback(){
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
||||
callbackOnAuthenticationError()
|
||||
}
|
||||
override fun onAuthenticationFailed() {
|
||||
callbackOnAuthenticationFailed()
|
||||
}
|
||||
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) {
|
||||
callbackOnAuthenticationSucceeded()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
fingerprintFragment.show(activityContext.supportFragmentManager, null)
|
||||
fingerprintManager.authenticate(FingerprintManager.CryptoObject(cipher), cancellationSignal, 0, object: FingerprintManager.AuthenticationCallback(){
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
||||
callbackOnAuthenticationError()
|
||||
}
|
||||
override fun onAuthenticationFailed() {
|
||||
callbackOnAuthenticationFailed()
|
||||
}
|
||||
override fun onAuthenticationSucceeded(result: FingerprintManager.AuthenticationResult?) {
|
||||
callbackOnAuthenticationSucceeded()
|
||||
}
|
||||
}, null)
|
||||
}
|
||||
isListening = true
|
||||
}
|
||||
|
||||
fun stopListening(){
|
||||
cancellationSignal.cancel()
|
||||
isListening = false
|
||||
}
|
||||
|
||||
fun callbackOnAuthenticationError() {
|
||||
if (!authenticationFailed){
|
||||
if (fingerprintFragment.isAdded){
|
||||
fingerprintFragment.image_fingerprint.setColorFilter(ContextCompat.getColor(activityContext, R.color.fingerprint_failed))
|
||||
fingerprintFragment.text_instruction.text = activityContext.getString(R.string.authentication_error)
|
||||
handler.postDelayed({ fingerprintFragment.dismiss() }, 1000)
|
||||
}
|
||||
if (actionMode == Cipher.ENCRYPT_MODE){
|
||||
handler.postDelayed({ onAuthenticationResult(false) }, FAILED_DISMISS_DIALOG_DELAY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun callbackOnAuthenticationFailed() {
|
||||
authenticationFailed = true
|
||||
if (fingerprintFragment.isAdded){
|
||||
fingerprintFragment.image_fingerprint.setColorFilter(ContextCompat.getColor(activityContext, R.color.fingerprint_failed))
|
||||
fingerprintFragment.text_instruction.text = activityContext.getString(R.string.authentication_failed)
|
||||
handler.postDelayed({ fingerprintFragment.dismiss() }, FAILED_DISMISS_DIALOG_DELAY)
|
||||
stopListening()
|
||||
} else {
|
||||
handler.postDelayed({ stopListening() }, FAILED_DISMISS_DIALOG_DELAY)
|
||||
}
|
||||
if (actionMode == Cipher.ENCRYPT_MODE){
|
||||
handler.postDelayed({ onAuthenticationResult(false) }, FAILED_DISMISS_DIALOG_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
fun callbackOnAuthenticationSucceeded() {
|
||||
if (fingerprintFragment.isAdded){
|
||||
fingerprintFragment.image_fingerprint.setColorFilter(ContextCompat.getColor(activityContext, R.color.fingerprint_success))
|
||||
fingerprintFragment.text_instruction.text = activityContext.getString(R.string.authenticated)
|
||||
}
|
||||
try {
|
||||
when (actionMode) {
|
||||
Cipher.ENCRYPT_MODE -> {
|
||||
val cipherText = cipher.doFinal(dataToProcess)
|
||||
val encodedCipherText = Base64.encodeToString(cipherText, 0)
|
||||
val encodedIv = Base64.encodeToString(cipher.iv, 0)
|
||||
sharedPrefsEditor.putString(rootCipherDir, "$encodedIv:$encodedCipherText")
|
||||
sharedPrefsEditor.apply()
|
||||
handler.postDelayed({
|
||||
if (fingerprintFragment.isAdded){
|
||||
fingerprintFragment.dismiss()
|
||||
}
|
||||
onAuthenticationResult(true)
|
||||
}, SUCCESS_DISMISS_DIALOG_DELAY)
|
||||
}
|
||||
Cipher.DECRYPT_MODE -> {
|
||||
try {
|
||||
val plainText = cipher.doFinal(dataToProcess)
|
||||
handler.postDelayed({
|
||||
if (fingerprintFragment.isAdded){
|
||||
fingerprintFragment.dismiss()
|
||||
}
|
||||
onPasswordDecrypted(plainText)
|
||||
}, SUCCESS_DISMISS_DIALOG_DELAY)
|
||||
} catch (e: AEADBadTagException){
|
||||
ColoredAlertDialogBuilder(activityContext)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(activityContext.getString(R.string.MAC_verification_failed))
|
||||
.setPositiveButton(activityContext.getString(R.string.reset_hash_storage)) { _, _ ->
|
||||
resetHashStorage()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalBlockSizeException){
|
||||
stopListening()
|
||||
ColoredAlertDialogBuilder(activityContext)
|
||||
.setTitle(R.string.authentication_error)
|
||||
.setMessage(activityContext.getString(R.string.authentication_error_msg))
|
||||
.setPositiveButton(activityContext.getString(R.string.reset_hash_storage)) { _, _ ->
|
||||
resetHashStorage()
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -75,19 +75,15 @@
|
||||
<string name="fingerprint_save_checkbox_text">Save password hash using fingerprint</string>
|
||||
<string name="fingerprint_instruction">Please touch the fingerprint sensor</string>
|
||||
<string name="open_failed_hash_msg">Failed to open the volume. The password may have changed.</string>
|
||||
<string name="authenticated">Authenticated !</string>
|
||||
<string name="authentication_error">Authentication error</string>
|
||||
<string name="authentication_failed">Authentication failed</string>
|
||||
<string name="delete_hash_or_all">Delete only password hash or all saved volume data ? (This won\'t delete the volume itself)</string>
|
||||
<string name="delete_all">Delete all</string>
|
||||
<string name="delete_hash">Delete password hash</string>
|
||||
<string name="ask_delete_volume_path">Are you sure you want to forget this volume path ? (This won\'t delete the volume itself)</string>
|
||||
<string name="fingerprint_perm_denied">Fingerprint permission denied</string>
|
||||
<string name="no_fingerprint_sensor">No fingerprint sensor detected</string>
|
||||
<string name="no_fingerprint_configured">No fingerprint configured</string>
|
||||
<string name="authentication_error_msg">This can happen if you have added a new fingerprint. The only solution is to reset the hash storage.</string>
|
||||
<string name="illegal_block_size_exception">IllegalBlockSizeException</string>
|
||||
<string name="illegal_block_size_exception_msg">This can happen if you have added a new fingerprint. Resetting hash storage can solve this problem.</string>
|
||||
<string name="reset_hash_storage">Reset hash storage</string>
|
||||
<string name="MAC_verification_failed">Signature/MAC verification failed. Either Android KeyStore or the saved hash has been modified. Reseting hash storage can solve this problem.</string>
|
||||
<string name="MAC_verification_failed">Signature/MAC verification failed. Either Android KeyStore or the saved hash has been modified. Resetting hash storage can solve this problem.</string>
|
||||
<string name="hash_storage_reset">Hash storage successfully reset</string>
|
||||
<string name="encrypt_action_description">Encrypting and saving password hash.</string>
|
||||
<string name="decrypt_action_description">Decrypting password hash.</string>
|
||||
@ -179,4 +175,7 @@
|
||||
<string name="file_write_failed">Failed to write the file.</string>
|
||||
<string name="error_not_a_volume">Gocryptfs volume not recognized. Please check the selected path.</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="error_cipher_null">Error cipher is null</string>
|
||||
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
|
||||
<string name="key_permanently_invalidated_exception_msg">It looks like you have added a new fingerprint. Saved passwords hash have become unusable.</string>
|
||||
</resources>
|
||||
|
@ -1,12 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.3.72"
|
||||
ext.kotlin_version = "1.4.10"
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user