Implemented local cover encryption (#881)

* Implemented local cover encryption and made coil capable of reading encrypted cover archives

* add check that the file is not an image before determining that it is a zip file
This commit is contained in:
Shamicen
2023-05-13 04:51:37 +02:00
committed by GitHub
parent 282a0c4e16
commit 291734a406
10 changed files with 81 additions and 154 deletions
@@ -1,13 +1,11 @@
package eu.kanade.tachiyomi.core.security
import android.content.Context
import eu.kanade.tachiyomi.core.R
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.getEnum
class SecurityPreferences(
private val preferenceStore: PreferenceStore,
private val context: Context,
) {
fun useAuthenticator() = preferenceStore.getBoolean("use_biometric_lock", false)
@@ -23,7 +21,7 @@ class SecurityPreferences(
fun authenticatorDays() = this.preferenceStore.getInt("biometric_days", 0x7F)
fun encryptDatabase() = this.preferenceStore.getBoolean("encrypt_database", !context.getDatabasePath("tachiyomi.db").exists())
fun encryptDatabase() = this.preferenceStore.getBoolean("encrypt_database", false)
fun sqlPassword() = this.preferenceStore.getString("sql_password", "")
@@ -32,9 +30,6 @@ class SecurityPreferences(
fun encryptionType() = this.preferenceStore.getEnum("encryption_type", EncryptionType.AES_256)
fun cbzPassword() = this.preferenceStore.getString("cbz_password", "")
fun localCoverLocation() = this.preferenceStore.getEnum("local_cover_location", CoverCacheLocation.IN_MANGA_DIRECTORY)
// SY <--
/**
@@ -56,11 +51,5 @@ class SecurityPreferences(
AES_128(R.string.aes_128),
ZIP_STANDARD(R.string.standard_zip_encryption),
}
enum class CoverCacheLocation(val titleResId: Int) {
IN_MANGA_DIRECTORY(R.string.save_in_manga_directory),
INTERNAL(R.string.save_internally),
NEVER(R.string.save_never),
}
// SY <--
}
@@ -1,10 +1,8 @@
package eu.kanade.tachiyomi.util.storage
import android.content.Context
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Base64
import eu.kanade.tachiyomi.core.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
@@ -20,7 +18,7 @@ import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream
import java.security.KeyStore
import java.security.SecureRandom
import javax.crypto.Cipher
@@ -36,6 +34,7 @@ import javax.crypto.spec.IvParameterSpec
*/
object CbzCrypto {
const val DATABASE_NAME = "tachiyomiEncrypted.db"
const val DEFAULT_COVER_NAME = "cover.jpg"
private val securityPreferences: SecurityPreferences by injectLazy()
private val keyStore = KeyStore.getInstance(KEYSTORE).apply {
load(null)
@@ -210,23 +209,15 @@ object CbzCrypto {
}
}
fun deleteLocalCoverCache(context: Context) {
if (context.getExternalFilesDir(LOCAL_CACHE_DIR)?.exists() == true) {
context.getExternalFilesDir(LOCAL_CACHE_DIR)?.deleteRecursively()
fun detectCoverImageArchive(stream: InputStream): Boolean {
val bytes = ByteArray(128)
if (stream.markSupported()) {
stream.mark(bytes.size)
stream.read(bytes, 0, bytes.size).also { stream.reset() }
} else {
stream.read(bytes, 0, bytes.size)
}
}
fun deleteLocalCoverSystemFiles(context: Context) {
val baseFolderLocation = "${context.getString(R.string.app_name)}${File.separator}local"
DiskUtil.getExternalStorages(context)
.map { File(it.absolutePath, baseFolderLocation) }
.asSequence()
.flatMap { it.listFiles().orEmpty().toList() }
.filter { it.isDirectory }
.flatMap { it.listFiles().orEmpty().toList() }
.filter { it.name == ".cacheCoverInternal" || it.name == ".nocover" }
.forEach { it.delete() }
return String(bytes).contains(DEFAULT_COVER_NAME, ignoreCase = true)
}
}
@@ -242,7 +233,4 @@ private const val CRYPTO_SETTINGS = "$ALGORITHM/$BLOCK_MODE/$PADDING"
private const val KEYSTORE = "AndroidKeyStore"
private const val ALIAS_CBZ = "cbzPw"
private const val ALIAS_SQL = "sqlPw"
private const val LOCAL_CACHE_DIR = "covers/local"
// SY <--
@@ -42,6 +42,10 @@ import kotlin.math.min
object ImageUtil {
fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean {
// SY -->
if (File(name).extension.equals("cbi", ignoreCase = true)) return true
// SY <--
val contentType = try {
URLConnection.guessContentTypeFromName(name)
} catch (e: Exception) {