diff --git a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/data/SingletonModule.kt b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/data/SingletonModule.kt index 8de3143..94d5e79 100644 --- a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/data/SingletonModule.kt +++ b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/data/SingletonModule.kt @@ -53,12 +53,10 @@ class SingletonModule { fun provideUnlockManager( @IoDispatcher ioDispatcher: CoroutineDispatcher, keyRepo: StorageKeyMapRepository, - metaRepo: StorageMetaInfoRepository, vaultsManager: IVaultsManager ): IUnlockManager { return UnlockManager( keymapRepository = keyRepo, - metaInfoRepository = metaRepo, ioDispatcher = ioDispatcher, vaultsManager = vaultsManager ) diff --git a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt index 13fbe01..4289b10 100644 --- a/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt +++ b/app/src/main/java/com/github/nullptroma/wallenc/app/di/modules/domain/UseCasesModule.kt @@ -4,6 +4,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase +import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase import dagger.Module @@ -38,4 +39,10 @@ class UseCasesModule { fun provideRenameStorageUseCase(): RenameStorageUseCase { return RenameStorageUseCase() } + + @Provides + @Singleton + fun provideManageStoragesEncryptionUseCase(unlockManager: IUnlockManager): ManageStoragesEncryptionUseCase { + return ManageStoragesEncryptionUseCase(unlockManager) + } } \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/UnlockManager.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/UnlockManager.kt index eadd018..203785a 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/UnlockManager.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/UnlockManager.kt @@ -11,6 +11,7 @@ import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager import com.github.nullptroma.wallenc.domain.interfaces.IVaultsManager import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -22,7 +23,6 @@ import java.util.UUID class UnlockManager( private val keymapRepository: StorageKeyMapRepository, - private val metaInfoRepository: StorageMetaInfoRepository, private val ioDispatcher: CoroutineDispatcher, vaultsManager: IVaultsManager ) : IUnlockManager { @@ -36,23 +36,32 @@ class UnlockManager( vaultsManager.allStorages.collectLatest { mutex.lock() val allKeys = keymapRepository.getAll() + val usedKeys = mutableListOf() val keysToRemove = mutableListOf() - val allStorages = it.associateBy({ it.uuid }, { it }) + val allStorages = it.toMutableList() val map = _openedStorages.value?.toMutableMap() ?: mutableMapOf() - for(keymap in allKeys) { - if(map.contains(keymap.sourceUuid)) + while(allStorages.size > 0) { + val storage = allStorages[allStorages.size-1] + val key = allKeys.find { key -> key.sourceUuid == storage.uuid } + if(key == null) { + allStorages.removeAt(allStorages.size - 1) continue + } try { - val storage = allStorages[keymap.sourceUuid] ?: continue - val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid) + val encStorage = createEncryptedStorage(storage, key.key, key.destUuid) map[storage.uuid] = encStorage + usedKeys.add(key) + allStorages.removeAt(allStorages.size - 1) + allStorages.add(encStorage) } catch (_: Exception) { - keysToRemove.add(keymap) + // ключ не подошёл + keysToRemove.add(key) + allStorages.removeAt(allStorages.size - 1) } } - _openedStorages.value = map keymapRepository.delete(*keysToRemove.toTypedArray()) // удалить мёртвые ключи + _openedStorages.value = map.toMap() mutex.unlock() } } @@ -63,7 +72,6 @@ class UnlockManager( source = storage, key = key, ioDispatcher = ioDispatcher, - metaInfoProvider = metaInfoRepository.createSingleStorageProvider(uuid), uuid = uuid ) } @@ -71,7 +79,7 @@ class UnlockManager( override suspend fun open( storage: IStorage, key: EncryptKey - ) = withContext(ioDispatcher) { + ): EncryptedStorage = withContext(ioDispatcher) { mutex.lock() val encInfo = storage.metaInfo.value.encInfo ?: throw Exception("EncInfo is null") // TODO if (!Encryptor.checkKey(key, encInfo)) @@ -92,6 +100,7 @@ class UnlockManager( _openedStorages.value = opened keymapRepository.add(keymap) mutex.unlock() + return@withContext encStorage } override suspend fun close(storage: IStorage) = withContext(ioDispatcher) { diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt index d3a3e14..e43e4fb 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorage.kt @@ -1,5 +1,6 @@ package com.github.nullptroma.wallenc.data.storages.encrypt +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.github.nullptroma.wallenc.data.db.app.repository.StorageMetaInfoRepository import com.github.nullptroma.wallenc.domain.common.impl.CommonStorageMetaInfo import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey @@ -13,24 +14,27 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext +import java.io.InputStream import java.util.UUID class EncryptedStorage private constructor( private val source: IStorage, private val key: EncryptKey, ioDispatcher: CoroutineDispatcher, - private val metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider, override val uuid: UUID = UUID.randomUUID() ) : IStorage, DisposableHandle { private val job = Job() private val scope = CoroutineScope(ioDispatcher + job) - private val encInfo = source.metaInfo.value.encInfo ?: throw Exception("Storage is not encrypted") // TODO + private val encInfo = + source.metaInfo.value.encInfo ?: throw Exception("Storage is not encrypted") // TODO + private val metaInfoFileName: String = "${uuid.toString().take(8)}$STORAGE_INFO_FILE_POSTFIX" override val size: StateFlow - get() = source.size + get() = accessor.size override val numberOfFiles: StateFlow - get() = source.numberOfFiles + get() = accessor.numberOfFiles private val _metaInfo = MutableStateFlow( CommonStorageMetaInfo() @@ -42,45 +46,64 @@ class EncryptedStorage private constructor( override val isAvailable: StateFlow get() = source.isAvailable override val accessor: EncryptedStorageAccessor = - EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, scope) + EncryptedStorageAccessor(source.accessor, encInfo.pathIv, key, "${uuid.toString().take(8)}$SYSTEM_HIDDEN_DIRNAME_POSTFIX", scope) private suspend fun init() { checkKey() - readMeta() + readMetaInfo() } private fun checkKey() { - if(!Encryptor.checkKey(key, encInfo)) + if (!Encryptor.checkKey(key, encInfo)) throw Exception("Incorrect key") // TODO } - private suspend fun readMeta() = scope.run { - var meta = metaInfoProvider.get() - if(meta == null) { + private suspend fun readMetaInfo() = scope.run { + var meta: CommonStorageMetaInfo + var reader: InputStream? = null + try { + reader = accessor.openReadSystemFile(metaInfoFileName) + meta = jackson.readValue(reader, CommonStorageMetaInfo::class.java) + } catch (e: Exception) { + // чтение не удалось, значит нужно записать файл meta = CommonStorageMetaInfo() - metaInfoProvider.set(meta) + updateMetaInfo(meta) + } finally { + reader?.close() + } + _metaInfo.value = meta + } + + private suspend fun updateMetaInfo(meta: IStorageMetaInfo) = scope.run { + val writer = accessor.openWriteSystemFile(metaInfoFileName) + try { + jackson.writeValue(writer, meta) + } catch (e: Exception) { + throw e + } finally { + writer.close() } _metaInfo.value = meta } override suspend fun rename(newName: String) = scope.run { - val cur = _metaInfo.value - val newMeta = CommonStorageMetaInfo( - encInfo = cur.encInfo, - name = newName + val curMeta = metaInfo.value + updateMetaInfo( + CommonStorageMetaInfo( + encInfo = curMeta.encInfo, + name = newName + ) ) - _metaInfo.value = newMeta - metaInfoProvider.set(newMeta) } override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = scope.run { - val cur = _metaInfo.value - val newMeta = CommonStorageMetaInfo( - encInfo = encInfo, - name = cur.name + val curMeta = metaInfo.value + updateMetaInfo( + CommonStorageMetaInfo( + encInfo = encInfo, + name = curMeta.name + ) ) - _metaInfo.value = newMeta - metaInfoProvider.set(newMeta) } override fun dispose() { @@ -93,24 +116,25 @@ class EncryptedStorage private constructor( source: IStorage, key: EncryptKey, ioDispatcher: CoroutineDispatcher, - metaInfoProvider: StorageMetaInfoRepository.SingleStorageMetaInfoProvider, uuid: UUID = UUID.randomUUID() ): EncryptedStorage = withContext(ioDispatcher) { val storage = EncryptedStorage( source = source, key = key, ioDispatcher = ioDispatcher, - metaInfoProvider = metaInfoProvider, uuid = uuid ) try { storage.init() - } - catch (e: Exception) { + } catch (e: Exception) { storage.dispose() throw e } return@withContext storage } + + private const val SYSTEM_HIDDEN_DIRNAME_POSTFIX = "-enc-dir" + const val STORAGE_INFO_FILE_POSTFIX = ".enc-meta" + private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() } } } \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt index 383f4fa..c656867 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/encrypt/EncryptedStorageAccessor.kt @@ -1,6 +1,7 @@ package com.github.nullptroma.wallenc.data.storages.encrypt -import android.util.Log +import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed +import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosing import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory import com.github.nullptroma.wallenc.domain.common.impl.CommonFile import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo @@ -16,6 +17,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map @@ -27,12 +29,17 @@ import kotlin.io.path.pathString class EncryptedStorageAccessor( private val source: IStorageAccessor, - pathIv: ByteArray, + pathIv: ByteArray?, key: EncryptKey, + private val systemHiddenDirName: String, private val scope: CoroutineScope ) : IStorageAccessor, DisposableHandle { - override val size: StateFlow = source.size - override val numberOfFiles: StateFlow = source.numberOfFiles + private val _size = MutableStateFlow(null) + override val size: StateFlow = _size + + private val _numberOfFiles = MutableStateFlow(null) + override val numberOfFiles: StateFlow = _numberOfFiles + override val isAvailable: StateFlow = source.isAvailable private val _filesUpdates = MutableSharedFlow>>() @@ -42,46 +49,70 @@ class EncryptedStorageAccessor( override val dirsUpdates: SharedFlow>> = _dirsUpdates private val dataEncryptor = Encryptor(key.toAesKey()) - private val pathEncryptor = EncryptorWithStaticIv(key.toAesKey(), pathIv) + private val pathEncryptor: EncryptorWithStaticIv? = if(pathIv != null) EncryptorWithStaticIv(key.toAesKey(), pathIv) else null + + private var systemHiddenFiles: List? = null + private var systemHiddenFilesIsActual = false init { collectSourceState() - - for(i in 1..5) { - val orig = "/hello/path/test.txt" - val enc = encryptPath(orig) - val dec = decryptPath(enc) - - Log.d("MyTag", "Path $orig to $enc to $dec") - } } - private fun collectSourceState() { scope.launch { launch { source.filesUpdates.collect { - val files = it.data.map(::decryptEntity) - _filesUpdates.emit(DataPackage( - data = files, - isLoading = it.isLoading, - isError = it.isError - )) + val files = it.data.map(::decryptEntity).filterSystemHiddenFiles() + _filesUpdates.emit( + DataPackage( + data = files, + isLoading = it.isLoading, + isError = it.isError + ) + ) } } launch { source.dirsUpdates.collect { - val dirs = it.data.map(::decryptEntity) - _dirsUpdates.emit(DataPackage( - data = dirs, - isLoading = it.isLoading, - isError = it.isError - )) + val dirs = it.data.map(::decryptEntity).filterSystemHiddenDirs() + _dirsUpdates.emit( + DataPackage( + data = dirs, + isLoading = it.isLoading, + isError = it.isError + ) + ) + } + } + + launch { + source.numberOfFiles.collect { + if(it == null) + _numberOfFiles.value = null + else + { + _numberOfFiles.value = it - getSystemFiles().size + } + } + } + + launch { + source.size.collect { sourceSize -> + if(sourceSize == null) + _size.value = null + else + { + _size.value = sourceSize - getSystemFiles().sumOf { it.metaInfo.size } + } } } } } + private suspend fun getSystemFiles(): List { + return source.getFiles(encryptPath(systemHiddenDirName)) + } + private fun encryptEntity(file: IFile): IFile { return CommonFile(encryptMeta(file.metaInfo)) } @@ -119,35 +150,40 @@ class EncryptedStorageAccessor( } private fun encryptPath(pathStr: String): String { + if(pathEncryptor == null) + return pathStr val path = Path(pathStr) val segments = mutableListOf() for (segment in path) segments.add(pathEncryptor.encryptString(segment.pathString)) - val res = Path("/",*(segments.toTypedArray())) + val res = Path("/", *(segments.toTypedArray())) return res.pathString } private fun decryptPath(pathStr: String): String { + if(pathEncryptor == null) + return pathStr + val path = Path(pathStr) val segments = mutableListOf() for (segment in path) segments.add(pathEncryptor.decryptString(segment.pathString)) - val res = Path("/",*(segments.toTypedArray())) + val res = Path("/", *(segments.toTypedArray())) return res.pathString } override suspend fun getAllFiles(): List { - return source.getAllFiles().map(::decryptEntity) + return source.getAllFiles().map(::decryptEntity).filterSystemHiddenFiles() } override suspend fun getFiles(path: String): List { - return source.getFiles(encryptPath(path)).map(::decryptEntity) + return source.getFiles(encryptPath(path)).map(::decryptEntity).filterSystemHiddenFiles() } override fun getFilesFlow(path: String): Flow>> { val flow = source.getFilesFlow(encryptPath(path)).map { DataPackage( - data = it.data.map(::decryptEntity), + data = it.data.map(::decryptEntity).filterSystemHiddenFiles(), isLoading = it.isLoading, isError = it.isError ) @@ -156,17 +192,18 @@ class EncryptedStorageAccessor( } override suspend fun getAllDirs(): List { - return source.getAllDirs().map(::decryptEntity) + return source.getAllDirs().map(::decryptEntity).filterSystemHiddenDirs() } override suspend fun getDirs(path: String): List { - return source.getDirs(encryptPath(path)).map(::decryptEntity) + return source.getDirs(encryptPath(path)).map(::decryptEntity).filterSystemHiddenDirs() } override fun getDirsFlow(path: String): Flow>> { val flow = source.getDirsFlow(encryptPath(path)).map { DataPackage( - data = it.data.map(::decryptEntity), + // включать все папки, кроме системной + data = it.data.map(::decryptEntity).filterSystemHiddenDirs(), isLoading = it.isLoading, isError = it.isError ) @@ -220,4 +257,33 @@ class EncryptedStorageAccessor( dataEncryptor.dispose() } + suspend fun openReadSystemFile(name: String): InputStream = scope.run { + val path = Path(systemHiddenDirName, name).pathString + return@run openRead(path) + } + + suspend fun openWriteSystemFile(name: String): OutputStream = scope.run { + val path = Path(systemHiddenDirName, name).pathString + systemHiddenFilesIsActual = false + return@run openWrite(path).onClosing { + systemHiddenFilesIsActual = false + } + } + + private fun Iterable.filterSystemHiddenFiles(): List { + return this.filter { file -> + !file.metaInfo.path.contains( + systemHiddenDirName + ) + } + } + + private fun Iterable.filterSystemHiddenDirs(): List { + return this.filter { dir -> + !dir.metaInfo.path.contains( + systemHiddenDirName + ) + } + } + } \ No newline at end of file diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorageAccessor.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorageAccessor.kt index 28ecc75..846142f 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorageAccessor.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/storages/local/LocalStorageAccessor.kt @@ -3,7 +3,7 @@ package com.github.nullptroma.wallenc.data.storages.local import com.fasterxml.jackson.core.JacksonException import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClose +import com.github.nullptroma.wallenc.data.utils.CloseHandledStreamExtension.Companion.onClosed import com.github.nullptroma.wallenc.domain.common.impl.CommonDirectory import com.github.nullptroma.wallenc.domain.common.impl.CommonFile import com.github.nullptroma.wallenc.domain.common.impl.CommonMetaInfo @@ -433,6 +433,8 @@ class LocalStorageAccessor( if (file.exists() && file.isDirectory) { throw Exception("Что то пошло не так") // TODO } else if(!file.exists()) { + val parent = Path(storagePath).parent + createDir(parent.pathString) file.createNewFile() val cur = _numberOfFiles.value @@ -441,7 +443,7 @@ class LocalStorageAccessor( val pair = LocalStorageFilePair.from(_filesystemBasePath, file) ?: throw Exception("Что то пошло не так") // TODO - val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant()) + val newMeta = pair.meta.copy(lastModified = Clock.systemUTC().instant(), size = Files.size(pair.file.toPath())) writeMeta(pair.metaFile, newMeta) _filesUpdates.emit( DataPage( @@ -503,9 +505,10 @@ class LocalStorageAccessor( touchFile(path) val pair = LocalStorageFilePair.from(_filesystemBasePath, path) ?: throw Exception("Файла нет") // TODO - return@withContext pair.file.outputStream().onClose { + return@withContext pair.file.outputStream().onClosed { CoroutineScope(ioDispatcher).launch { touchFile(path) + scanSizeAndNumOfFiles() } } } diff --git a/data/src/main/java/com/github/nullptroma/wallenc/data/utils/CloseHandledStream.kt b/data/src/main/java/com/github/nullptroma/wallenc/data/utils/CloseHandledStream.kt index 606ade1..52340bf 100644 --- a/data/src/main/java/com/github/nullptroma/wallenc/data/utils/CloseHandledStream.kt +++ b/data/src/main/java/com/github/nullptroma/wallenc/data/utils/CloseHandledStream.kt @@ -5,6 +5,7 @@ import java.io.OutputStream private class CloseHandledOutputStream( private val stream: OutputStream, + private val onClosing: () -> Unit, private val onClose: () -> Unit ) : OutputStream() { @@ -25,6 +26,7 @@ private class CloseHandledOutputStream( } override fun close() { + onClosing() try { stream.close() } finally { @@ -35,6 +37,7 @@ private class CloseHandledOutputStream( private class CloseHandledInputStream( private val stream: InputStream, + private val onClosing: () -> Unit, private val onClose: () -> Unit ) : InputStream() { @@ -59,6 +62,7 @@ private class CloseHandledInputStream( } override fun close() { + onClosing() try { stream.close() } finally { @@ -81,12 +85,20 @@ private class CloseHandledInputStream( class CloseHandledStreamExtension { companion object { - fun OutputStream.onClose(callback: ()->Unit): OutputStream { - return CloseHandledOutputStream(this, callback) + fun OutputStream.onClosed(callback: ()->Unit): OutputStream { + return CloseHandledOutputStream(this, {}, callback) } - fun InputStream.onClose(callback: ()->Unit): InputStream { - return CloseHandledInputStream(this, callback) + fun InputStream.onClosed(callback: ()->Unit): InputStream { + return CloseHandledInputStream(this, {}, callback) + } + + fun OutputStream.onClosing(callback: ()->Unit): OutputStream { + return CloseHandledOutputStream(this, callback, {}) + } + + fun InputStream.onClosing(callback: ()->Unit): InputStream { + return CloseHandledInputStream(this, callback, {}) } } } \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/StorageEncryptionInfo.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/StorageEncryptionInfo.kt index cb83099..5bd7e31 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/StorageEncryptionInfo.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/datatypes/StorageEncryptionInfo.kt @@ -2,7 +2,7 @@ package com.github.nullptroma.wallenc.domain.datatypes data class StorageEncryptionInfo( val encryptedTestData: String, - val pathIv: ByteArray + val pathIv: ByteArray? ) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/encrypt/Encryptor.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/encrypt/Encryptor.kt index 6bf8244..0253129 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/encrypt/Encryptor.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/encrypt/Encryptor.kt @@ -74,7 +74,7 @@ class Encryptor(private var secretKey: SecretKey) : DisposableHandle { } override fun dispose() { - secretKey.destroy() + //secretKey.destroy() } companion object { @@ -83,13 +83,13 @@ class Encryptor(private var secretKey: SecretKey) : DisposableHandle { private const val TEST_DATA_LEN = 512 @OptIn(ExperimentalEncodingApi::class) - fun generateEncryptionInfo(key: EncryptKey) : StorageEncryptionInfo { + fun generateEncryptionInfo(key: EncryptKey, encryptPath: Boolean) : StorageEncryptionInfo { val encryptor = Encryptor(key.toAesKey()) val testData = ByteArray(TEST_DATA_LEN) val encryptedData = encryptor.encryptBytes(testData) return StorageEncryptionInfo( encryptedTestData = Base64.Default.encode(encryptedData), - pathIv = Random.nextBytes(IV_LEN) + pathIv = if(encryptPath) Random.nextBytes(IV_LEN) else null ) } diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IUnlockManager.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IUnlockManager.kt index 0245e54..333b50d 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IUnlockManager.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/interfaces/IUnlockManager.kt @@ -10,6 +10,6 @@ interface IUnlockManager { */ val openedStorages: StateFlow?> - suspend fun open(storage: IStorage, key: EncryptKey) + suspend fun open(storage: IStorage, key: EncryptKey): IStorage suspend fun close(storage: IStorage) } \ No newline at end of file diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt index e8a3b33..764235b 100644 --- a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageLocalVaultUseCase.kt @@ -17,12 +17,6 @@ class ManageLocalVaultUseCase(private val manager: IVaultsManager, private val u manager.localVault.createStorage() } - suspend fun createStorage(key: EncryptKey) { - val encInfo = Encryptor.generateEncryptionInfo(key) - val storage = manager.localVault.createStorage(encInfo) - unlockManager.open(storage, key) - } - suspend fun remove(storage: IStorageInfo) { when(storage) { is IStorage -> manager.localVault.remove(storage) diff --git a/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageStoragesEncryptionUseCase.kt b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageStoragesEncryptionUseCase.kt new file mode 100644 index 0000000..3e3cbc4 --- /dev/null +++ b/domain/src/main/java/com/github/nullptroma/wallenc/domain/usecases/ManageStoragesEncryptionUseCase.kt @@ -0,0 +1,27 @@ +package com.github.nullptroma.wallenc.domain.usecases + +import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey +import com.github.nullptroma.wallenc.domain.encrypt.Encryptor +import com.github.nullptroma.wallenc.domain.interfaces.IStorage +import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo +import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager + +class ManageStoragesEncryptionUseCase(private val unlockManager: IUnlockManager) { + suspend fun enableEncryption(storage: IStorageInfo, key: EncryptKey, encryptPath: Boolean) { + when(storage) { + is IStorage -> { + if(storage.metaInfo.value.encInfo != null) + throw Exception() // TODO + storage.setEncInfo(Encryptor.generateEncryptionInfo(key, encryptPath)) + } + } + } + + suspend fun openStorage(storage: IStorageInfo, key: EncryptKey): IStorageInfo { + when(storage) { + is IStorage -> { + return unlockManager.open(storage, key) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt index f24e4c4..f49c7d2 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/elements/StorageTree.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.DropdownMenu @@ -57,6 +58,7 @@ fun StorageTree( onClick: (Tree) -> Unit, onRename: (Tree, String) -> Unit, onRemove: (Tree) -> Unit, + onEncrypt: (Tree) -> Unit, ) { val cur = tree.value val available by cur.isAvailable.collectAsStateWithLifecycle() @@ -66,7 +68,9 @@ fun StorageTree( val borderColor = if (cur.isVirtualStorage) MaterialTheme.colorScheme.secondary else MaterialTheme.colorScheme.primary Column(modifier) { - Box(modifier = Modifier.height(IntrinsicSize.Min).zIndex(100f)) { + Box(modifier = Modifier + .height(IntrinsicSize.Min) + .zIndex(100f)) { val interactionSource = remember { MutableInteractionSource() } Box( modifier = Modifier @@ -171,6 +175,9 @@ fun StorageTree( } } Spacer(modifier = Modifier.weight(1f)) + Button(onClick = { onEncrypt(tree) }, enabled = metaInfo.encInfo == null) { + Text("Encrypt") + } Text( modifier = Modifier .fillMaxWidth() @@ -190,7 +197,16 @@ fun StorageTree( } } for (i in tree.children ?: listOf()) { - StorageTree(Modifier.padding(16.dp, 0.dp, 0.dp, 0.dp).offset(y = (-4).dp), i, onClick, onRename, onRemove) + StorageTree( + Modifier + .padding(16.dp, 0.dp, 0.dp, 0.dp) + .offset(y = (-4).dp), + i, + onClick, + onRename, + onRemove, + onEncrypt + ) } } } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt index 2571c90..edff918 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultScreen.kt @@ -51,6 +51,9 @@ fun LocalVaultScreen( }, onRemove = { tree -> viewModel.remove(tree.value) + }, + onEncrypt = { tree -> + viewModel.enableEncryptionAndOpenStorage(tree.value) } ) } diff --git a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt index dd745d1..f06b122 100644 --- a/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt +++ b/presentation/src/main/java/com/github/nullptroma/wallenc/presentation/screens/main/screens/local/vault/LocalVaultViewModel.kt @@ -9,10 +9,11 @@ import com.github.nullptroma.wallenc.domain.interfaces.ILogger import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo import com.github.nullptroma.wallenc.domain.usecases.GetOpenedStoragesUseCase import com.github.nullptroma.wallenc.domain.usecases.ManageLocalVaultUseCase +import com.github.nullptroma.wallenc.domain.usecases.ManageStoragesEncryptionUseCase import com.github.nullptroma.wallenc.domain.usecases.RenameStorageUseCase import com.github.nullptroma.wallenc.domain.usecases.StorageFileManagementUseCase -import com.github.nullptroma.wallenc.presentation.extensions.toPrintable import com.github.nullptroma.wallenc.presentation.ViewModelBase +import com.github.nullptroma.wallenc.presentation.extensions.toPrintable import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine @@ -25,6 +26,7 @@ class LocalVaultViewModel @Inject constructor( private val manageLocalVaultUseCase: ManageLocalVaultUseCase, private val getOpenedStoragesUseCase: GetOpenedStoragesUseCase, private val storageFileManagementUseCase: StorageFileManagementUseCase, + private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase, private val renameStorageUseCase: RenameStorageUseCase, private val logger: ILogger ) : ViewModelBase(LocalVaultScreenState(listOf())) { @@ -50,6 +52,11 @@ class LocalVaultViewModel @Inject constructor( updateState(newState) } } + viewModelScope.launch { + getOpenedStoragesUseCase.openedStorages.collectLatest { + logger.debug("ViewModel", "Collected opened: ${it?.size}") + } + } } fun printStorageInfoToLog(storage: IStorageInfo) { @@ -74,7 +81,24 @@ class LocalVaultViewModel @Inject constructor( fun createStorage() { viewModelScope.launch { - manageLocalVaultUseCase.createStorage(EncryptKey("Hello")) + manageLocalVaultUseCase.createStorage() + } + } + + private val runningStorages = mutableSetOf() + fun enableEncryptionAndOpenStorage(storage: IStorageInfo) { + if(runningStorages.contains(storage)) + return + runningStorages.add(storage) + val key = EncryptKey("Hello") + viewModelScope.launch { + try { + manageStoragesEncryptionUseCase.enableEncryption(storage, key, false) + manageStoragesEncryptionUseCase.openStorage(storage, key) + } + finally { + runningStorages.remove(storage) + } } }