Allow opening CryFS volumes with password hash

This commit is contained in:
Matéo Duparc 2022-06-29 13:43:56 +02:00
parent cf4927a90b
commit e01932acda
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
9 changed files with 73 additions and 42 deletions

@ -1 +1 @@
Subproject commit 356cf8a1604776cb2cc4f4ff873936f7b396bd49 Subproject commit cf822d6a5bb0bb3492ec350d2648f14c859f846f

View File

@ -31,7 +31,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
import sushi.hardcore.droidfs.file_operations.FileOperationService import sushi.hardcore.droidfs.file_operations.FileOperationService
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
@ -535,7 +535,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) { private fun askForPassword(volume: SavedVolume, position: Int, savePasswordHash: Boolean = false) {
val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater) val dialogBinding = DialogOpenVolumeBinding.inflate(layoutInflater)
if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null || volume.type == EncryptedVolume.CRYFS_VOLUME_TYPE) { if (!usfFingerprint || fingerprintProtector == null || volume.encryptedHash != null) {
dialogBinding.checkboxSavePassword.visibility = View.GONE dialogBinding.checkboxSavePassword.visibility = View.GONE
} else { } else {
dialogBinding.checkboxSavePassword.isChecked = savePasswordHash dialogBinding.checkboxSavePassword.isChecked = savePasswordHash
@ -564,10 +564,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) { private fun openVolumeWithPassword(volume: SavedVolume, position: Int, password: ByteArray, savePasswordHash: Boolean) {
val usfFingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) val returnedHash: ObjRef<ByteArray?>? = if (savePasswordHash) {
var returnedHash: ByteArray? = null ObjRef(null)
if (savePasswordHash && usfFingerprint) { } else {
returnedHash = ByteArray(GocryptfsVolume.KeyLen) null
} }
object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) { object : LoadingTask<EncryptedVolume?>(this, themeValue, R.string.loading_msg_open) {
override suspend fun doTask(): EncryptedVolume? { override suspend fun doTask(): EncryptedVolume? {
@ -594,7 +594,7 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
} }
override fun onPasswordHashDecrypted(hash: ByteArray) {} override fun onPasswordHashDecrypted(hash: ByteArray) {}
override fun onPasswordHashSaved() { override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash.value!!, 0)
volumeAdapter.onVolumeChanged(position) volumeAdapter.onVolumeChanged(position)
startExplorer(encryptedVolume, volume.shortName) startExplorer(encryptedVolume, volume.shortName)
} }
@ -604,10 +604,10 @@ class MainActivity : BaseActivity(), VolumeAdapter.Listener {
encryptedVolume.close() encryptedVolume.close()
isClosed = true isClosed = true
} }
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash.value!!, 0)
} }
} }
fingerprintProtector.savePasswordHash(volume, returnedHash) fingerprintProtector.savePasswordHash(volume, returnedHash.value!!)
} else { } else {
startExplorer(encryptedVolume, volume.shortName) startExplorer(encryptedVolume, volume.shortName)
} }

View File

@ -18,6 +18,7 @@ import sushi.hardcore.droidfs.databinding.FragmentCreateVolumeBinding
import sushi.hardcore.droidfs.filesystems.CryfsVolume import sushi.hardcore.droidfs.filesystems.CryfsVolume
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.util.WidgetUtil
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
import java.io.File import java.io.File
@ -108,12 +109,8 @@ class CreateVolumeFragment: Fragment() {
binding.spinnerVolumeType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spinnerVolumeType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val ciphersArray = if (volumeTypes[position] == resources.getString(R.string.gocryptfs)) { val ciphersArray = if (volumeTypes[position] == resources.getString(R.string.gocryptfs)) {
if (usfFingerprint && fingerprintProtector != null) {
binding.checkboxSavePassword.visibility = View.VISIBLE
}
R.array.gocryptfs_encryption_ciphers R.array.gocryptfs_encryption_ciphers
} else { } else {
binding.checkboxSavePassword.visibility = View.GONE
R.array.cryfs_encryption_ciphers R.array.cryfs_encryption_ciphers
} }
with(encryptionCipherAdapter) { with(encryptionCipherAdapter) {
@ -167,9 +164,11 @@ class CreateVolumeFragment: Fragment() {
Arrays.fill(passwordConfirm, 0) Arrays.fill(passwordConfirm, 0)
} else { } else {
Arrays.fill(passwordConfirm, 0) Arrays.fill(passwordConfirm, 0)
var returnedHash: ByteArray? = null val returnedHash: ObjRef<ByteArray?>? = if (binding.checkboxSavePassword.isChecked) {
if (binding.checkboxSavePassword.isChecked) ObjRef(null)
returnedHash = ByteArray(GocryptfsVolume.KeyLen) } else {
null
}
object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) { object: LoadingTask<SavedVolume?>(requireActivity() as AppCompatActivity, themeValue, R.string.loading_msg_create) {
override suspend fun doTask(): SavedVolume? { override suspend fun doTask(): SavedVolume? {
val volumeFile = File(volumePath) val volumeFile = File(volumePath)
@ -188,13 +187,16 @@ class CreateVolumeFragment: Fragment() {
xchacha, xchacha,
GocryptfsVolume.ScryptDefaultLogN, GocryptfsVolume.ScryptDefaultLogN,
ConstValues.CREATOR, ConstValues.CREATOR,
returnedHash returnedHash?.apply {
value = ByteArray(GocryptfsVolume.KeyLen)
}?.value,
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE) ), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
} else { } else {
saveVolume(CryfsVolume.create( saveVolume(CryfsVolume.create(
volumePath, volumePath,
CryfsVolume.getLocalStateDir(activity.filesDir.path), CryfsVolume.getLocalStateDir(activity.filesDir.path),
password, password,
returnedHash,
resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition] resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition]
), EncryptedVolume.CRYFS_VOLUME_TYPE) ), EncryptedVolume.CRYFS_VOLUME_TYPE)
} }
@ -216,21 +218,21 @@ class CreateVolumeFragment: Fragment() {
override fun onHashStorageReset() { override fun onHashStorageReset() {
hashStorageReset = true hashStorageReset = true
// retry // retry
it.savePasswordHash(volume, returnedHash) it.savePasswordHash(volume, returnedHash.value!!)
} }
override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here override fun onPasswordHashDecrypted(hash: ByteArray) {} // shouldn't happen here
override fun onPasswordHashSaved() { override fun onPasswordHashSaved() {
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated() onVolumeCreated()
} }
override fun onFailed(pending: Boolean) { override fun onFailed(pending: Boolean) {
if (!pending) { if (!pending) {
Arrays.fill(returnedHash, 0) Arrays.fill(returnedHash.value!!, 0)
onVolumeCreated() onVolumeCreated()
} }
} }
} }
it.savePasswordHash(volume, returnedHash) it.savePasswordHash(volume, returnedHash.value!!)
} }
} else onVolumeCreated() } else onVolumeCreated()
} }

View File

@ -18,6 +18,7 @@ import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.Wiper import sushi.hardcore.droidfs.util.Wiper
import java.io.File import java.io.File
@ -415,8 +416,6 @@ class FileOperationService : Service() {
return count return count
} }
internal class ObjRef<T>(var value: T)
private fun recursiveCopyVolume( private fun recursiveCopyVolume(
src: DocumentFile, src: DocumentFile,
dst: DocumentFile, dst: DocumentFile,

View File

@ -3,6 +3,7 @@ package sushi.hardcore.droidfs.filesystems
import android.os.Parcel import android.os.Parcel
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
class CryfsVolume(private val fusePtr: Long): EncryptedVolume() { class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
@ -16,7 +17,9 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
private external fun nativeInit( private external fun nativeInit(
baseDir: String, baseDir: String,
localStateDir: String, localStateDir: String,
password: ByteArray, password: ByteArray?,
givenHash: ByteArray?,
returnedHash: ObjRef<ByteArray?>?,
createBaseDir: Boolean, createBaseDir: Boolean,
cipher: String? cipher: String?
): Long ): Long
@ -39,8 +42,16 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
return PathUtils.pathJoin(filesDir, ConstValues.CRYFS_LOCAL_STATE_DIR) return PathUtils.pathJoin(filesDir, ConstValues.CRYFS_LOCAL_STATE_DIR)
} }
private fun init(baseDir: String, localStateDir: String, password: ByteArray, createBaseDir: Boolean, cipher: String?): CryfsVolume? { private fun init(
val fusePtr = nativeInit(baseDir, localStateDir, password, createBaseDir, cipher) baseDir: String,
localStateDir: String,
password: ByteArray?,
givenHash: ByteArray?,
returnedHash: ObjRef<ByteArray?>?,
createBaseDir: Boolean,
cipher: String?
): CryfsVolume? {
val fusePtr = nativeInit(baseDir, localStateDir, password, givenHash, returnedHash, createBaseDir, cipher)
return if (fusePtr == 0L) { return if (fusePtr == 0L) {
null null
} else { } else {
@ -48,12 +59,12 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
} }
} }
fun create(baseDir: String, localStateDir: String, password: ByteArray, cipher: String?): Boolean { fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?): Boolean {
return init(baseDir, localStateDir, password, true, cipher)?.also { it.close() } != null return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also { it.close() } != null
} }
fun init(baseDir: String, localStateDir: String, password: ByteArray): CryfsVolume? { fun init(baseDir: String, localStateDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ObjRef<ByteArray?>?): CryfsVolume? {
return init(baseDir, localStateDir, password, false, null) return init(baseDir, localStateDir, password, givenHash, returnedHash, false, null)
} }
} }

View File

@ -7,6 +7,7 @@ import android.os.Parcelable
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.SavedVolume import sushi.hardcore.droidfs.SavedVolume
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@ -40,13 +41,26 @@ abstract class EncryptedVolume: Parcelable {
} }
} }
fun init(volume: SavedVolume, filesDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): EncryptedVolume? { fun init(
volume: SavedVolume,
filesDir: String,
password: ByteArray?,
givenHash: ByteArray?,
returnedHash: ObjRef<ByteArray?>?
): EncryptedVolume? {
return when (volume.type) { return when (volume.type) {
GOCRYPTFS_VOLUME_TYPE -> { GOCRYPTFS_VOLUME_TYPE -> {
GocryptfsVolume.init(volume.getFullPath(filesDir), password, givenHash, returnedHash) GocryptfsVolume.init(
volume.getFullPath(filesDir),
password,
givenHash,
returnedHash?.apply {
value = ByteArray(GocryptfsVolume.KeyLen)
}?.value
)
} }
CRYFS_VOLUME_TYPE -> { CRYFS_VOLUME_TYPE -> {
CryfsVolume.init(volume.getFullPath(filesDir), CryfsVolume.getLocalStateDir(filesDir), password!!) CryfsVolume.init(volume.getFullPath(filesDir), CryfsVolume.getLocalStateDir(filesDir), password, givenHash, returnedHash)
} }
else -> throw invalidVolumeType() else -> throw invalidVolumeType()
} }

View File

@ -0,0 +1,3 @@
package sushi.hardcore.droidfs.util
class ObjRef<T>(var value: T)

View File

@ -5,9 +5,11 @@
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject thiz, Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JNIEnv *env, jobject thiz,
jstring base_dir, jstring jlocalStateDir, jstring base_dir, jstring jlocalStateDir,
jbyteArray password, jboolean createBaseDir, jbyteArray password, jbyteArray givenHash,
jobject returnedHash,
jboolean createBaseDir,
jstring cipher) { jstring cipher) {
return cryfs_init(env, base_dir, jlocalStateDir, password, createBaseDir, cipher); return cryfs_init(env, base_dir, jlocalStateDir, password, givenHash, returnedHash, createBaseDir, cipher);
} }
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL

View File

@ -17,16 +17,16 @@
<requestFocus/> <requestFocus/>
</EditText> </EditText>
<CheckBox
android:id="@+id/checkbox_save_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fingerprint_save_checkbox_text"/>
<CheckBox <CheckBox
android:id="@+id/checkbox_default_open" android:id="@+id/checkbox_default_open"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/default_open"/> android:text="@string/default_open"/>
<CheckBox
android:id="@+id/checkbox_save_password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/fingerprint_save_checkbox_text"/>
</LinearLayout> </LinearLayout>