Encrypted overlay filesystems implementation for Android. Also available on GitHub: https://github.com/hardcore-sushi/DroidFS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

207 lines
9.1 KiB

package sushi.hardcore.droidfs.add_volume
import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import sushi.hardcore.droidfs.*
import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File
import java.util.*
class CreateVolumeFragment: Fragment() {
companion object {
private const val KEY_THEME_VALUE = "theme"
private const val KEY_VOLUME_PATH = "path"
private const val KEY_IS_HIDDEN = "hidden"
private const val KEY_PIN_PASSWORDS = ConstValues.PIN_PASSWORDS_KEY
private const val KEY_USF_FINGERPRINT = "fingerprint"
fun newInstance(
themeValue: String,
volumePath: String,
isHidden: Boolean,
pinPasswords: Boolean,
usfFingerprint: Boolean,
): CreateVolumeFragment {
return CreateVolumeFragment().apply {
arguments = Bundle().apply {
putString(KEY_THEME_VALUE, themeValue)
putString(KEY_VOLUME_PATH, volumePath)
putBoolean(KEY_IS_HIDDEN, isHidden)
putBoolean(KEY_PIN_PASSWORDS, pinPasswords)
putBoolean(KEY_USF_FINGERPRINT, usfFingerprint)
}
}
}
}
private lateinit var binding: FragmentCreateVolumeBinding
private var themeValue = ConstValues.DEFAULT_THEME_VALUE
private lateinit var volumePath: String
private var isHiddenVolume: Boolean = false
private var usfFingerprint: Boolean = false
private lateinit var volumeDatabase: VolumeDatabase
private var fingerprintProtector: FingerprintProtector? = null
private var hashStorageReset = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCreateVolumeBinding.inflate(layoutInflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val pinPasswords = requireArguments().let { arguments ->
arguments.getString(KEY_THEME_VALUE)?.let { themeValue = it }
volumePath = arguments.getString(KEY_VOLUME_PATH)!!
isHiddenVolume = arguments.getBoolean(KEY_IS_HIDDEN)
usfFingerprint = arguments.getBoolean(KEY_USF_FINGERPRINT)
arguments.getBoolean(KEY_PIN_PASSWORDS)
}
volumeDatabase = VolumeDatabase(requireContext())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
fingerprintProtector = FingerprintProtector.new(requireActivity(), themeValue, volumeDatabase)
}
if (!usfFingerprint || fingerprintProtector == null) {
binding.checkboxSavePassword.visibility = View.GONE
}
if (pinPasswords) {
arrayOf(binding.editPassword, binding.editPasswordConfirm).forEach {
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
}
}
binding.editPasswordConfirm.setOnEditorActionListener { _, _, _ ->
createVolume()
true
}
binding.spinnerXchacha.adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_spinner_item,
resources.getStringArray(R.array.encryption_cipher)
).apply {
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
}
binding.spinnerXchacha.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if (position == 1)
CustomAlertDialogBuilder(requireContext(), themeValue)
.setTitle(R.string.warning)
.setMessage(R.string.xchacha_warning)
.setPositiveButton(R.string.ok, null)
.show()
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
binding.buttonCreate.setOnClickListener {
createVolume()
}
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
(activity as AddVolumeActivity).onFragmentLoaded(false)
}
private fun createVolume() {
val password = CharArray(binding.editPassword.text.length)
binding.editPassword.text.getChars(0, password.size, password, 0)
val passwordConfirm = CharArray(binding.editPasswordConfirm.text.length)
binding.editPasswordConfirm.text.getChars(0, passwordConfirm.size, passwordConfirm, 0)
if (!password.contentEquals(passwordConfirm)) {
Toast.makeText(requireContext(), R.string.passwords_mismatch, Toast.LENGTH_SHORT).show()
Arrays.fill(password, 0.toChar())
Arrays.fill(passwordConfirm, 0.toChar())
} else {
Arrays.fill(passwordConfirm, 0.toChar())
var returnedHash: ByteArray? = null
if (binding.checkboxSavePassword.isChecked)
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
object: LoadingTask<Volume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
override suspend fun doTask(): Volume? {
val xchacha = when (binding.spinnerXchacha.selectedItemPosition) {
0 -> 0
1 -> 1
else -> -1
}
val volumeFile = File(volumePath)
if (!volumeFile.exists())
volumeFile.mkdirs()
val volume = if (GocryptfsVolume.createVolume(
volumePath,
password,
false,
xchacha,
GocryptfsVolume.ScryptDefaultLogN,
ConstValues.CREATOR,
returnedHash
)
) {
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
val volume = Volume(volumeName, isHiddenVolume)
volumeDatabase.apply {
if (isVolumeSaved(volumeName, isHiddenVolume)) // cleaning old saved path
removeVolume(volumeName)
saveVolume(volume)
}
volume
} else {
null
}
Arrays.fill(password, 0.toChar())
return volume
}
}.startTask(lifecycleScope) { volume ->
if (volume == null) {
CustomAlertDialogBuilder(requireContext(), themeValue)
.setTitle(R.string.error)
.setMessage(R.string.create_volume_failed)
.setPositiveButton(R.string.ok, null)
.show()
} else {
@SuppressLint("NewApi") // if fingerprintProtector is null checkboxSavePassword is hidden
if (binding.checkboxSavePassword.isChecked && returnedHash != null) {
fingerprintProtector!!.let {
it.listener = object : FingerprintProtector.Listener {
override fun onHashStorageReset() {
hashStorageReset = true
// retry
it.savePasswordHash(volume, returnedHash)
}
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0)
onVolumeCreated()
}
override fun onFailed(pending: Boolean) {
if (!pending) {
Arrays.fill(returnedHash, 0)
onVolumeCreated()
}
}
}
it.savePasswordHash(volume, returnedHash)
}
} else onVolumeCreated()
}
}
}
}
private fun onVolumeCreated() {
(activity as AddVolumeActivity).onVolumeAdded(hashStorageReset)
}
}