Best error messages when opening volumes

This commit is contained in:
Matéo Duparc 2023-05-03 13:54:34 +02:00
parent f4f3239bb1
commit 4c412be7dc
Signed by untrusted user: hardcoresushi
GPG Key ID: AFE384344A45E13A
15 changed files with 114 additions and 47 deletions

@ -1 +1 @@
Subproject commit f40c2bbdcde77d8128da181edb72bf0e7a2168b5
Subproject commit 3c56f86d86afacaf4a07ae77aa3d146764d587ec

@ -1 +1 @@
Subproject commit 79f9a10e35847e46f8563941345355f15f2dba7c
Subproject commit 6308adf8e57379e1136f4f55ff0d7737c0d0a33b

View File

@ -2,8 +2,12 @@ package sushi.hardcore.droidfs
import android.os.Parcel
import android.os.Parcelable
import sushi.hardcore.droidfs.filesystems.CryfsVolume
import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.filesystems.GocryptfsVolume
import sushi.hardcore.droidfs.util.PathUtils
import java.io.File
import java.io.FileInputStream
class VolumeData(val name: String, val isHidden: Boolean = false, val type: Byte, var encryptedHash: ByteArray? = null, var iv: ByteArray? = null): Parcelable {
@ -26,6 +30,28 @@ class VolumeData(val name: String, val isHidden: Boolean = false, val type: Byte
name
}
fun canRead(filesDir: String): Boolean {
val volumePath = getFullPath(filesDir)
if (!File(volumePath).canRead()) {
return false
}
val configFile = when (type) {
EncryptedVolume.GOCRYPTFS_VOLUME_TYPE -> PathUtils.pathJoin(volumePath, GocryptfsVolume.CONFIG_FILE_NAME)
EncryptedVolume.CRYFS_VOLUME_TYPE -> PathUtils.pathJoin(volumePath, CryfsVolume.CONFIG_FILE_NAME)
else -> return false
}
var success = true
try {
with (FileInputStream(configFile)) {
read()
close()
}
} catch (e: Exception) {
success = false
}
return success
}
fun canWrite(filesDir: String): Boolean {
return File(getFullPath(filesDir)).canWrite()
}

View File

@ -39,6 +39,14 @@ class VolumeOpener(
}
}
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)
}
}
@SuppressLint("NewApi") // fingerprintProtector is non-null only when SDK_INT >= 23
fun openVolume(volume: VolumeData, isVolumeSaved: Boolean, callbacks: VolumeOpenerCallbacks) {
val volumeId = volumeManager.getVolumeId(volume)
@ -60,17 +68,18 @@ class VolumeOpener(
callbacks.onHashStorageReset()
}
override fun onPasswordHashDecrypted(hash: ByteArray) {
object : LoadingTask<EncryptedVolume?>(activity, theme, R.string.loading_msg_open) {
override suspend fun doTask(): EncryptedVolume? {
val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, null, hash, null)
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)
Arrays.fill(hash, 0)
return encryptedVolume
return result
}
}.startTask(activity.lifecycleScope) { encryptedVolume ->
}.startTask(activity.lifecycleScope) { result ->
val encryptedVolume = result.volume
if (encryptedVolume == null) {
CustomAlertDialogBuilder(activity, theme)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_failed_hash_msg)
.setMessage(getErrorMsg(result))
.setPositiveButton(R.string.ok, null)
.show()
} else {
@ -168,17 +177,18 @@ class VolumeOpener(
} else {
null
}
object : LoadingTask<EncryptedVolume?>(activity, theme, R.string.loading_msg_open) {
override suspend fun doTask(): EncryptedVolume? {
val encryptedVolume = EncryptedVolume.init(volume, activity.filesDir.path, password, null, returnedHash)
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)
Arrays.fill(password, 0)
return encryptedVolume
return result
}
}.startTask(activity.lifecycleScope) { encryptedVolume ->
}.startTask(activity.lifecycleScope) { result ->
val encryptedVolume = result.volume
if (encryptedVolume == null) {
CustomAlertDialogBuilder(activity, theme)
.setTitle(R.string.open_volume_failed)
.setMessage(R.string.open_volume_failed_msg)
.setMessage(getErrorMsg(result))
.setPositiveButton(R.string.ok) { _, _ ->
askForPassword(volume, isVolumeSaved, callbacks, savePasswordHash)
}

View File

@ -92,8 +92,10 @@ class VolumeAdapter(
itemView.findViewById<TextView>(R.id.text_info).text = context.getString(
if (volume.canWrite(context.filesDir.path)) {
R.string.volume_type
} else {
} else if (volume.canRead(context.filesDir.path)) {
R.string.volume_type_read_only
} else {
R.string.volume_type_inaccessible
},
context.getString(if (volume.type == EncryptedVolume.GOCRYPTFS_VOLUME_TYPE) {
R.string.gocryptfs

View File

@ -190,14 +190,14 @@ class CreateVolumeFragment: Fragment() {
encryptedVolume,
), EncryptedVolume.GOCRYPTFS_VOLUME_TYPE)
} else {
generateResult(CryfsVolume.create(
encryptedVolume.value = CryfsVolume.create(
volumePath,
CryfsVolume.getLocalStateDir(activity.filesDir.path),
password,
returnedHash,
resources.getStringArray(R.array.cryfs_encryption_ciphers)[binding.spinnerCipher.selectedItemPosition],
encryptedVolume,
), EncryptedVolume.CRYFS_VOLUME_TYPE)
)
generateResult(encryptedVolume.value != null, EncryptedVolume.CRYFS_VOLUME_TYPE)
}
Arrays.fill(password, 0)
return result

View File

@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.filesystems
import android.os.Parcel
import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.ObjRef
import sushi.hardcore.droidfs.util.PathUtils
@ -21,7 +22,8 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
givenHash: ByteArray?,
returnedHash: ObjRef<ByteArray?>?,
createBaseDir: Boolean,
cipher: String?
cipher: String?,
errorCode: ObjRef<Int?>,
): Long
private external fun nativeChangeEncryptionKey(
baseDir: String,
@ -58,22 +60,30 @@ class CryfsVolume(private val fusePtr: Long): EncryptedVolume() {
returnedHash: ObjRef<ByteArray?>?,
createBaseDir: Boolean,
cipher: String?
): CryfsVolume? {
val fusePtr = nativeInit(baseDir, localStateDir, password, givenHash, returnedHash, createBaseDir, cipher)
return if (fusePtr == 0L) {
null
): InitResult {
val errorCode = ObjRef<Int?>(null)
val fusePtr = nativeInit(baseDir, localStateDir, password, givenHash, returnedHash, createBaseDir, cipher, errorCode)
val result = InitResult.Builder()
if (fusePtr == 0L) {
result.errorCode = errorCode.value ?: 0
result.errorStringId = when (errorCode.value) {
// Values from src/cryfs/impl/ErrorCodes.h
11 -> R.string.wrong_password
19 -> R.string.config_load_error
20 -> R.string.filesystem_id_changed
else -> 0
}
} else {
CryfsVolume(fusePtr)
result.volume = CryfsVolume(fusePtr)
}
return result.build()
}
fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?, volume: ObjRef<EncryptedVolume?>): Boolean {
return init(baseDir, localStateDir, password, null, returnedHash, true, cipher)?.also {
volume.value = it
} != null
fun create(baseDir: String, localStateDir: String, password: ByteArray, returnedHash: ObjRef<ByteArray?>?, cipher: String?): EncryptedVolume? {
return init(baseDir, localStateDir, password, null, returnedHash, true, cipher).volume
}
fun init(baseDir: String, localStateDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ObjRef<ByteArray?>?): CryfsVolume? {
fun init(baseDir: String, localStateDir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ObjRef<ByteArray?>?): InitResult {
return init(baseDir, localStateDir, password, givenHash, returnedHash, false, null)
}

View File

@ -15,6 +15,17 @@ import java.io.InputStream
import java.io.OutputStream
abstract class EncryptedVolume: Parcelable {
class InitResult(val errorCode: Int, val errorStringId: Int, val volume: EncryptedVolume?) {
class Builder {
var errorCode = 0
var errorStringId = 0
var volume: EncryptedVolume? = null
fun build() = InitResult(errorCode, errorStringId, volume)
}
}
companion object {
const val GOCRYPTFS_VOLUME_TYPE: Byte = 0
const val CRYFS_VOLUME_TYPE: Byte = 1
@ -31,6 +42,11 @@ abstract class EncryptedVolume: Parcelable {
override fun newArray(size: Int) = arrayOfNulls<EncryptedVolume>(size)
}
/**
* Get the type of a volume.
*
* @return The volume type or -1 if the path is not recognized as a volume
*/
fun getVolumeType(path: String): Byte {
return if (File(path, GocryptfsVolume.CONFIG_FILE_NAME).isFile) {
GOCRYPTFS_VOLUME_TYPE
@ -47,7 +63,7 @@ abstract class EncryptedVolume: Parcelable {
password: ByteArray?,
givenHash: ByteArray?,
returnedHash: ObjRef<ByteArray?>?
): EncryptedVolume? {
): InitResult {
return when (volume.type) {
GOCRYPTFS_VOLUME_TYPE -> {
GocryptfsVolume.init(

View File

@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.filesystems
import android.os.Parcel
import android.util.Log
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.util.ObjRef
import kotlin.math.min
@ -75,13 +76,20 @@ class GocryptfsVolume(private val sessionID: Int): EncryptedVolume() {
}
}
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): GocryptfsVolume? {
fun init(root_cipher_dir: String, password: ByteArray?, givenHash: ByteArray?, returnedHash: ByteArray?): InitResult {
val sessionId = nativeInit(root_cipher_dir, password, givenHash, returnedHash)
return if (sessionId == -1) {
null
val result = InitResult.Builder()
if (sessionId < 0) {
result.errorCode = sessionId
result.errorStringId = when (sessionId) {
-1 -> R.string.config_load_error
-2 -> R.string.wrong_password
else -> 0
}
} else {
GocryptfsVolume(sessionId)
result.volume = GocryptfsVolume(sessionId)
}
return result.build()
}
init {

View File

@ -7,9 +7,9 @@ Java_sushi_hardcore_droidfs_filesystems_CryfsVolume_00024Companion_nativeInit(JN
jstring base_dir, jstring jlocalStateDir,
jbyteArray password, jbyteArray givenHash,
jobject returnedHash,
jboolean createBaseDir,
jstring cipher) {
return cryfs_init(env, base_dir, jlocalStateDir, password, givenHash, returnedHash, createBaseDir, cipher);
jboolean createBaseDir, jstring cipher,
jobject jerrorCode) {
return cryfs_init(env, base_dir, jlocalStateDir, password, givenHash, returnedHash, createBaseDir, cipher, jerrorCode);
}
JNIEXPORT jboolean JNICALL

View File

@ -27,7 +27,6 @@
<string name="dir_not_empty">إن المجلد المحدد ليس فارغاً</string>
<string name="create_volume_failed">إن عملية إنشاء المجلد المشفر قد فشلت.</string>
<string name="open_volume_failed">عملية الفتح قد فشلت</string>
<string name="open_volume_failed_msg">لقد تعذر فتح المجلد المشفر. من فضلك تحقق من كلمة المرور.</string>
<string name="share_chooser">مشاركة ملف</string>
<string name="storage_perm_denied">إن صلاحية التخزين مرفوضة</string>
<string name="storage_perm_denied_msg">إن DroidFS لا يمكنه العمل بدون صلاحبات التخزين.</string>
@ -58,7 +57,6 @@
<string name="listdir_null_error_msg">تعذر الوصول إلى هذا المسار</string>
<string name="fingerprint_save_checkbox_text">حفظ كلمة المرور ببصمة الإصبع</string>
<string name="fingerprint_instruction">يرجى لمس مستشعر بصمة الإصبع</string>
<string name="open_failed_hash_msg">فشل في فتح المجلد المشفر. ربما تغيرت كلمة المرور.</string>
<string name="illegal_block_size_exception">IllegalBlockSizeException</string>
<string name="illegal_block_size_exception_msg">يمكن أن يحدث هذا إذا أضفت بصمة إصبع جديدة. يمكنك أن تقوم بإعادة تعيين تخزين التجزئة لحل هذه المشكلة.</string>
<string name="reset_hash_storage">إعادة تعيين تخزين التجزئة</string>

View File

@ -28,7 +28,6 @@
<string name="dir_not_empty">El directorio seleccionado no está vacío</string>
<string name="create_volume_failed">La creación del volumen ha fallado.</string>
<string name="open_volume_failed">Fallo al abrir</string>
<string name="open_volume_failed_msg">No se ha podido abrir el volumen. Por favor, comprueba tu contraseña.</string>
<string name="share_chooser">Compartir archivo</string>
<string name="storage_perm_denied">Permiso de almacenamiento denegado</string>
<string name="storage_perm_denied_msg">DroidFS no puede funcionar sin permisos de almacenamiento.</string>
@ -59,7 +58,6 @@
<string name="listdir_null_error_msg">No se puede acceder a este directorio</string>
<string name="fingerprint_save_checkbox_text">Guardar el hash de la contraseña mediante la huella dactilar</string>
<string name="fingerprint_instruction">Por favor, toca el sensor de huellas dactilares</string>
<string name="open_failed_hash_msg">No se ha podido abrir el volumen. La contraseña puede haber cambiado.</string>
<string name="illegal_block_size_exception">IllegalBlockSizeException</string>
<string name="illegal_block_size_exception_msg">Esto puede ocurrir si has añadido una nueva huella digital. Restablecer el almacenamiento de hash puede resolver este problema.</string>
<string name="reset_hash_storage">Restablecer el almacenamiento de hash</string>

View File

@ -27,7 +27,6 @@
<string name="dir_not_empty">A pasta selecionada não está vazia</string>
<string name="create_volume_failed">Falha ao criar o volume.</string>
<string name="open_volume_failed">Não foi possível abrir</string>
<string name="open_volume_failed_msg">Falha ao abrir o volume. Por favor, verifique sua senha.</string>
<string name="share_chooser">Compartilhar arquivo</string>
<string name="storage_perm_denied">Permissão do armazenamento negada</string>
<string name="storage_perm_denied_msg">DroidFS não pode funcionar sem permissão ao armazenamento.</string>
@ -58,7 +57,6 @@
<string name="listdir_null_error_msg">Não foi possível acessar a pasta</string>
<string name="fingerprint_save_checkbox_text">Salvar o hash da senha usando impressão digital</string>
<string name="fingerprint_instruction">Por favor, toque no sensor da impressão digital</string>
<string name="open_failed_hash_msg">Falha ao abrir o volume. A senha podia ter mudado.</string>
<string name="illegal_block_size_exception">IllegalBlockSizeException</string>
<string name="illegal_block_size_exception_msg">Isto poderia ter acontecido, se você tiver adicionado uma nova impressão digital. Tente resetar o hash do armazenamento para resolver este problema.</string>
<string name="reset_hash_storage">Resetar o hash do armazenamento</string>

View File

@ -26,7 +26,6 @@
<string name="dir_not_empty">Выбранная папка не пустая</string>
<string name="create_volume_failed">Невозможно создать том.</string>
<string name="open_volume_failed">Ошибка открытия</string>
<string name="open_volume_failed_msg">Невозможно открыть том. Проверьте пароль.</string>
<string name="share_chooser">Поделиться файлом</string>
<string name="storage_perm_denied">Доступ к хранилищу запрещён</string>
<string name="storage_perm_denied_msg">DroidFS не может работать без разрешения на доступ к хранилищу.</string>
@ -57,7 +56,6 @@
<string name="listdir_null_error_msg">Невозможно получить доступ к этой папке.</string>
<string name="fingerprint_save_checkbox_text">Сохранить хеш пароля отпечатком пальца</string>
<string name="fingerprint_instruction">Прикоснитесь к сканеру отпечатков пальцев</string>
<string name="open_failed_hash_msg">Невозможно открыть том. Возможно, изменился пароль.</string>
<string name="illegal_block_size_exception_msg">Это могло произойти, если вы добавили новый отпечаток пальца. Сброс хранилища хешей должен решить эту проблему.</string>
<string name="reset_hash_storage">Сброс хранилища хешей</string>
<string name="MAC_verification_failed">Проверка подписи/MAC не выполнена. Хранилище ключей Android или сохранённый хеш был изменён. Сброс хранилища хешей должен решить эту проблему.</string>

View File

@ -28,7 +28,6 @@
<string name="dir_not_empty">The selected directory isn\'t empty</string>
<string name="create_volume_failed">The volume creation has failed.</string>
<string name="open_volume_failed">Open failed</string>
<string name="open_volume_failed_msg">Failed to open the volume. Please check your password.</string>
<string name="share_chooser">Share File</string>
<string name="storage_perm_denied">Storage permission denied</string>
<string name="storage_perm_denied_msg">DroidFS can\'t work without storage permissions.</string>
@ -59,7 +58,6 @@
<string name="listdir_null_error_msg">Unable to access this directory</string>
<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="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>
@ -244,6 +242,7 @@
<string name="file_op_delete_msg">Deleting files…</string>
<string name="volume_type">(%s)</string>
<string name="volume_type_read_only">(%s, read-only)</string>
<string name="volume_type_inaccessible">(%s, inaccessible)</string>
<string name="io_error">I/O Error.</string>
<string name="use_fingerprint">Use fingerprint instead of current password</string>
<string name="remember_volume">Remember volume</string>
@ -258,4 +257,8 @@
<string name="black_theme">Black theme</string>
<string name="password_fallback">Fallback to password</string>
<string name="password_fallback_summary">Prompt for password when fingerprint authentication is cancelled</string>
<string name="unknown_error_code">Unknown error code: %d</string>
<string name="config_load_error">The configuration file cannot be loaded. Make sure the volume is accessible.</string>
<string name="wrong_password">The configuration file cannot be decrypted. Please check your password.</string>
<string name="filesystem_id_changed">The filesystem id in the config file is different to the last time we opened this volume. This could mean an attacker replaced the filesystem with a different one.</string>
</resources>