2022-09-30 21:22:37 +02:00
|
|
|
package sushi.hardcore.droidfs
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.os.Build
|
|
|
|
import android.text.InputType
|
|
|
|
import android.view.View
|
|
|
|
import android.view.WindowManager
|
|
|
|
import android.widget.Toast
|
|
|
|
import androidx.fragment.app.FragmentActivity
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
import androidx.preference.PreferenceManager
|
2023-02-06 10:52:51 +01:00
|
|
|
import sushi.hardcore.droidfs.Constants.DEFAULT_VOLUME_KEY
|
2022-09-30 21:22:37 +02:00
|
|
|
import sushi.hardcore.droidfs.databinding.DialogOpenVolumeBinding
|
|
|
|
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
|
|
|
|
import sushi.hardcore.droidfs.util.ObjRef
|
|
|
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
|
|
|
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
class VolumeOpener(
|
|
|
|
private val activity: FragmentActivity,
|
|
|
|
) {
|
|
|
|
interface VolumeOpenerCallbacks {
|
|
|
|
fun onHashStorageReset() {}
|
2023-03-07 23:25:17 +01:00
|
|
|
fun onVolumeOpened(id: Int)
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private val volumeDatabase = VolumeDatabase(activity)
|
|
|
|
private var fingerprintProtector: FingerprintProtector? = null
|
|
|
|
private val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
2023-02-28 22:50:59 +01:00
|
|
|
private val theme = (activity as BaseActivity).theme
|
2022-09-30 21:22:37 +02:00
|
|
|
var defaultVolumeName: String? = sharedPrefs.getString(DEFAULT_VOLUME_KEY, null)
|
2023-02-02 19:37:10 +01:00
|
|
|
private var dialogBinding: DialogOpenVolumeBinding? = null
|
2023-03-07 23:25:17 +01:00
|
|
|
private val volumeManager = (activity.application as VolumeManagerApp).volumeManager
|
2022-09-30 21:22:37 +02:00
|
|
|
|
|
|
|
init {
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
2023-02-28 22:50:59 +01:00
|
|
|
fingerprintProtector = FingerprintProtector.new(activity, theme, volumeDatabase)
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-03 13:54:34 +02:00
|
|
|
private fun getErrorMsg(result: EncryptedVolume.InitResult): String {
|
|
|
|
return if (result.errorStringId == 0) {
|
|
|
|
activity.getString(R.string.unknown_error_code, result.errorCode)
|
|
|
|
} else {
|
|
|
|
activity.getString(result.errorStringId)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-30 21:22:37 +02:00
|
|
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
|
|
|
fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
|
2023-03-07 23:25:17 +01:00
|
|
|
val volumeId = volumeManager.getVolumeId(volume)
|
|
|
|
if (volumeId == null) {
|
|
|
|
if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE && BuildConfig.GOCRYPTFS_DISABLED) {
|
|
|
|
Toast.makeText(activity, R.string.gocryptfs_disabled, Toast.LENGTH_SHORT).show()
|
|
|
|
return
|
|
|
|
} else if (volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE && BuildConfig.CRYFS_DISABLED) {
|
|
|
|
Toast.makeText(activity, R.string.cryfs_disabled, Toast.LENGTH_SHORT).show()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var askForPassword = true
|
|
|
|
fingerprintProtector?.let { fingerprintProtector ->
|
|
|
|
volume.encryptedHash?.let { encryptedHash ->
|
|
|
|
volume.iv?.let { iv ->
|
|
|
|
askForPassword = false
|
|
|
|
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
|
|
|
override fun onHashStorageReset() {
|
|
|
|
callbacks.onHashStorageReset()
|
|
|
|
}
|
|
|
|
override fun onPasswordHashDecrypted(hash: ByteArray) {
|
2023-05-03 13:54:34 +02:00
|
|
|
object : LoadingTask<EncryptedVolume.InitResult>(activity, theme, R.string.loading_msg_open) {
|
|
|
|
override suspend fun doTask(): EncryptedVolume.InitResult {
|
|
|
|
val result = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null)
|
2023-03-07 23:25:17 +01:00
|
|
|
Arrays.fill(hash, 0)
|
2023-05-03 13:54:34 +02:00
|
|
|
return result
|
2023-03-07 23:25:17 +01:00
|
|
|
}
|
2023-05-03 13:54:34 +02:00
|
|
|
}.startTask(activity.lifecycleScope) { result ->
|
|
|
|
val encryptedVolume = result.volume
|
2023-03-07 23:25:17 +01:00
|
|
|
if (encryptedVolume == null) {
|
2023-02-28 22:50:59 +01:00
|
|
|
CustomAlertDialogBuilder(activity, theme)
|
2023-03-07 23:25:17 +01:00
|
|
|
.setTitle(R.string.open_volume_failed)
|
2023-05-03 13:54:34 +02:00
|
|
|
.setMessage(getErrorMsg(result))
|
2023-03-07 23:25:17 +01:00
|
|
|
.setPositiveButton(R.string.ok, null)
|
|
|
|
.show()
|
|
|
|
} else {
|
|
|
|
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
|
|
|
|
}
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-07 23:25:17 +01:00
|
|
|
override fun onPasswordHashSaved() {}
|
|
|
|
override fun onFailed(pending: Boolean) {
|
2023-03-08 12:03:05 +01:00
|
|
|
if (!pending && sharedPrefs.getBoolean("passwordFallback", true)) {
|
2023-03-07 23:25:17 +01:00
|
|
|
askForPassword(volume, isVolumeSaved, callbacks)
|
|
|
|
}
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
2023-03-07 23:25:17 +01:00
|
|
|
fingerprintProtector.loadPasswordHash(volume.shortName, encryptedHash, iv)
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-07 23:25:17 +01:00
|
|
|
if (askForPassword) {
|
|
|
|
askForPassword(volume, isVolumeSaved, callbacks)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
callbacks.onVolumeOpened(volumeId)
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-02 19:37:10 +01:00
|
|
|
fun wipeSensitive() {
|
|
|
|
dialogBinding?.editPassword?.text?.clear()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun onPasswordSubmitted(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
|
|
|
|
if (dialogBinding!!.checkboxDefaultOpen.isChecked xor (defaultVolumeName == volume.name)) {
|
2022-09-30 21:22:37 +02:00
|
|
|
with (sharedPrefs.edit()) {
|
2023-02-02 19:37:10 +01:00
|
|
|
defaultVolumeName = if (dialogBinding!!.checkboxDefaultOpen.isChecked) {
|
2022-09-30 21:22:37 +02:00
|
|
|
putString(DEFAULT_VOLUME_KEY, volume.name)
|
|
|
|
volume.name
|
|
|
|
} else {
|
|
|
|
remove(DEFAULT_VOLUME_KEY)
|
|
|
|
null
|
|
|
|
}
|
|
|
|
apply()
|
|
|
|
}
|
|
|
|
}
|
2023-02-02 19:37:10 +01:00
|
|
|
val password = WidgetUtil.encodeEditTextContent(dialogBinding!!.editPassword)
|
|
|
|
val savePasswordHash = dialogBinding!!.checkboxSavePassword.isChecked
|
|
|
|
dialogBinding = null
|
2022-09-30 21:22:37 +02:00
|
|
|
// openVolumeWithPassword is responsible for wiping the password
|
|
|
|
openVolumeWithPassword(
|
|
|
|
volume,
|
2023-02-02 19:37:10 +01:00
|
|
|
password,
|
2022-09-30 21:22:37 +02:00
|
|
|
isVolumeSaved,
|
2023-02-02 19:37:10 +01:00
|
|
|
savePasswordHash,
|
2022-09-30 21:22:37 +02:00
|
|
|
callbacks,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun askForPassword(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks, savePasswordHash: Boolean = false) {
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding = DialogOpenVolumeBinding.inflate(activity.layoutInflater)
|
2022-09-30 21:22:37 +02:00
|
|
|
if (isVolumeSaved) {
|
|
|
|
if (!sharedPrefs.getBoolean("usf_fingerprint", false) || fingerprintProtector == null || volume.encryptedHash != null) {
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding!!.checkboxSavePassword.visibility = View.GONE
|
2022-09-30 21:22:37 +02:00
|
|
|
} else {
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding!!.checkboxSavePassword.isChecked = savePasswordHash
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding!!.checkboxDefaultOpen.isChecked = defaultVolumeName == volume.name
|
2022-09-30 21:22:37 +02:00
|
|
|
} else {
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding!!.checkboxSavePassword.visibility = View.GONE
|
|
|
|
dialogBinding!!.checkboxDefaultOpen.visibility = View.GONE
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
2023-02-28 22:50:59 +01:00
|
|
|
val dialog = CustomAlertDialogBuilder(activity, theme)
|
2022-09-30 21:22:37 +02:00
|
|
|
.setTitle(activity.getString(R.string.open_dialog_title, volume.shortName))
|
2023-02-02 19:37:10 +01:00
|
|
|
.setView(dialogBinding!!.root)
|
2022-09-30 21:22:37 +02:00
|
|
|
.setNegativeButton(R.string.cancel, null)
|
|
|
|
.setPositiveButton(R.string.open) { _, _ ->
|
2023-02-02 19:37:10 +01:00
|
|
|
onPasswordSubmitted(volume, isVolumeSaved, callbacks)
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
.create()
|
2023-02-02 19:37:10 +01:00
|
|
|
dialogBinding!!.editPassword.apply {
|
2022-09-30 21:22:37 +02:00
|
|
|
setOnEditorActionListener { _, _, _ ->
|
|
|
|
dialog.dismiss()
|
2023-02-02 19:37:10 +01:00
|
|
|
onPasswordSubmitted(volume, isVolumeSaved, callbacks)
|
2022-09-30 21:22:37 +02:00
|
|
|
true
|
|
|
|
}
|
2023-02-06 10:52:51 +01:00
|
|
|
if (sharedPrefs.getBoolean(Constants.PIN_PASSWORDS_KEY, false)) {
|
2022-09-30 21:22:37 +02:00
|
|
|
inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
|
|
|
dialog.show()
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun openVolumeWithPassword(volume: VolumeData, password: ByteArray, isVolumeSaved: Boolean, savePasswordHash: Boolean, callbacks: VolumeOpenerCallbacks) {
|
|
|
|
val returnedHash: ObjRef<ByteArray?>? = if (savePasswordHash) {
|
|
|
|
ObjRef(null)
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
2023-05-03 13:54:34 +02:00
|
|
|
object : LoadingTask<EncryptedVolume.InitResult>(activity, theme, R.string.loading_msg_open) {
|
|
|
|
override suspend fun doTask(): EncryptedVolume.InitResult {
|
|
|
|
val result = EncryptedVolume.init(volume, activity.filesDir.path, password, null, returnedHash)
|
2022-09-30 21:22:37 +02:00
|
|
|
Arrays.fill(password, 0)
|
2023-05-03 13:54:34 +02:00
|
|
|
return result
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
2023-05-03 13:54:34 +02:00
|
|
|
}.startTask(activity.lifecycleScope) { result ->
|
|
|
|
val encryptedVolume = result.volume
|
2022-09-30 21:22:37 +02:00
|
|
|
if (encryptedVolume == null) {
|
2023-02-28 22:50:59 +01:00
|
|
|
CustomAlertDialogBuilder(activity, theme)
|
2022-09-30 21:22:37 +02:00
|
|
|
.setTitle(R.string.open_volume_failed)
|
2023-05-03 13:54:34 +02:00
|
|
|
.setMessage(getErrorMsg(result))
|
2022-09-30 21:22:37 +02:00
|
|
|
.setPositiveButton(R.string.ok) { _, _ ->
|
|
|
|
askForPassword(volume, isVolumeSaved, callbacks, savePasswordHash)
|
|
|
|
}
|
|
|
|
.show()
|
|
|
|
} else {
|
|
|
|
val fingerprintProtector = fingerprintProtector
|
|
|
|
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
|
|
|
|
if (savePasswordHash && returnedHash != null && fingerprintProtector != null) {
|
|
|
|
fingerprintProtector.listener = object : FingerprintProtector.Listener {
|
|
|
|
override fun onHashStorageReset() {
|
|
|
|
callbacks.onHashStorageReset()
|
|
|
|
}
|
|
|
|
override fun onPasswordHashDecrypted(hash: ByteArray) {}
|
|
|
|
override fun onPasswordHashSaved() {
|
|
|
|
Arrays.fill(returnedHash.value!!, 0)
|
2023-03-07 23:25:17 +01:00
|
|
|
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
private var isClosed = false
|
|
|
|
override fun onFailed(pending: Boolean) {
|
|
|
|
if (!isClosed) {
|
|
|
|
encryptedVolume.close()
|
|
|
|
isClosed = true
|
|
|
|
}
|
|
|
|
Arrays.fill(returnedHash.value!!, 0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
|
|
|
|
} else {
|
2023-03-07 23:25:17 +01:00
|
|
|
callbacks.onVolumeOpened(volumeManager.insert(encryptedVolume, volume))
|
2022-09-30 21:22:37 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|