Полное управление шифрованием и ключами

This commit is contained in:
2026-04-18 17:36:29 +03:00
parent 3455b91bca
commit db9463c2c6
18 changed files with 484 additions and 128 deletions

View File

@@ -1,15 +0,0 @@
package com.github.nullptroma.wallenc.domain.enums
/**
* Политика удаления/закрытия хранилища.
*
* [CLOSE_ENCRYPTED_OVERLAYS_ONLY] — только закрыть расшифрованные представления (overlay),
* физические данные не трогаем.
*
* [REMOVE_PHYSICAL] — удалить физическое хранилище у провайдера (сейчас local vault),
* предварительно закрыв все overlay.
*/
enum class StorageDeletionPolicy {
CLOSE_ENCRYPTED_OVERLAYS_ONLY,
REMOVE_PHYSICAL,
}

View File

@@ -1,6 +1,7 @@
package com.github.nullptroma.wallenc.domain.interfaces
import com.github.nullptroma.wallenc.domain.datatypes.StorageEncryptionInfo
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import java.time.Instant
import java.util.UUID
@@ -10,6 +11,7 @@ sealed interface IStorageInfo {
val isAvailable: StateFlow<Boolean>
val size: StateFlow<Long?>
val numberOfFiles: StateFlow<Int?>
val isEmpty: Flow<Boolean?>
val metaInfo: StateFlow<IStorageMetaInfo>
val isVirtualStorage: Boolean
}
@@ -18,7 +20,8 @@ interface IStorage: IStorageInfo {
val accessor: IStorageAccessor
suspend fun rename(newName: String)
suspend fun setEncInfo(encInfo: StorageEncryptionInfo)
suspend fun setEncInfo(encInfo: StorageEncryptionInfo?)
suspend fun clearAllContent()
}
interface IStorageMetaInfo {

View File

@@ -14,7 +14,7 @@ interface IUnlockManager {
*/
val openedStorages: StateFlow<Map<UUID, IStorage>>
suspend fun open(storage: IStorage, key: EncryptKey): IStorage
suspend fun open(storage: IStorage, key: EncryptKey, rememberPassword: Boolean = true): IStorage
suspend fun close(storage: IStorage)
suspend fun close(uuid: UUID): Unit
}

View File

@@ -5,23 +5,70 @@ 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
import kotlinx.coroutines.flow.first
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))
}
class ManageStoragesEncryptionUseCase(
private val unlockManager: IUnlockManager,
) {
sealed interface CanEncryptResult {
data object Allowed : CanEncryptResult
data object UnsupportedStorageType : CanEncryptResult
data object AlreadyEncrypted : CanEncryptResult
data object StorageIsNotEmpty : CanEncryptResult
data object StorageStateUnknown : CanEncryptResult
}
suspend fun canEncrypt(storage: IStorageInfo): CanEncryptResult {
if (storage !is IStorage) return CanEncryptResult.UnsupportedStorageType
if (storage.metaInfo.value.encInfo != null) return CanEncryptResult.AlreadyEncrypted
val isEmpty = storage.isEmpty.first()
return when (isEmpty) {
true -> CanEncryptResult.Allowed
false -> CanEncryptResult.StorageIsNotEmpty
null -> CanEncryptResult.StorageStateUnknown
}
}
suspend fun openStorage(storage: IStorageInfo, key: EncryptKey): IStorageInfo {
when(storage) {
is IStorage -> {
return unlockManager.open(storage, key)
}
suspend fun enableEncryption(storage: IStorageInfo, key: EncryptKey, encryptPath: Boolean) {
when (val result = canEncrypt(storage)) {
CanEncryptResult.Allowed -> (storage as IStorage).setEncInfo(
Encryptor.generateEncryptionInfo(key, encryptPath)
)
CanEncryptResult.AlreadyEncrypted -> throw IllegalStateException("Storage is already encrypted")
CanEncryptResult.StorageIsNotEmpty -> throw IllegalStateException("Storage is not empty")
CanEncryptResult.StorageStateUnknown -> throw IllegalStateException("Storage state is unknown")
CanEncryptResult.UnsupportedStorageType -> throw IllegalStateException("Unsupported storage type")
}
}
suspend fun openStorage(storage: IStorageInfo, key: EncryptKey, rememberPassword: Boolean): IStorageInfo {
if (storage is IStorage) return unlockManager.open(storage, key, rememberPassword)
throw IllegalStateException("Unsupported storage type")
}
suspend fun closeStorage(storage: IStorageInfo) {
if (storage is IStorage) {
unlockManager.close(storage)
}
}
suspend fun disableEncryption(storage: IStorageInfo) {
clearAndDisableEncryption(storage)
}
suspend fun clearAndDisableEncryption(storage: IStorageInfo) {
if (storage !is IStorage) return
storage.clearAllContent()
storage.setEncInfo(null)
unlockManager.close(storage)
}
suspend fun changePassword(storage: IStorageInfo, newKey: EncryptKey, encryptPath: Boolean) {
if (storage !is IStorage) return
if (storage.metaInfo.value.encInfo == null) {
throw IllegalStateException("Storage is not encrypted")
}
storage.setEncInfo(Encryptor.generateEncryptionInfo(newKey, encryptPath))
}
}

View File

@@ -1,6 +1,5 @@
package com.github.nullptroma.wallenc.domain.usecases
import com.github.nullptroma.wallenc.domain.enums.StorageDeletionPolicy
import com.github.nullptroma.wallenc.domain.interfaces.IStorage
import com.github.nullptroma.wallenc.domain.interfaces.IStorageInfo
import com.github.nullptroma.wallenc.domain.interfaces.IUnlockManager
@@ -10,36 +9,30 @@ import java.util.UUID
class RemoveStorageUseCase(
private val vaultsManager: IVaultsManager,
private val unlockManager: IUnlockManager,
private val manageStoragesEncryptionUseCase: ManageStoragesEncryptionUseCase,
) {
suspend fun remove(storage: IStorageInfo, policy: StorageDeletionPolicy) {
suspend fun remove(storage: IStorageInfo) {
if (storage !is IStorage) return
when (policy) {
StorageDeletionPolicy.CLOSE_ENCRYPTED_OVERLAYS_ONLY -> {
unlockManager.close(storage)
}
StorageDeletionPolicy.REMOVE_PHYSICAL -> {
val physical = findPhysicalRootStorage(storage) ?: return
unlockManager.close(physical.uuid)
vaultsManager.localVault.remove(physical)
}
if (!storage.isVirtualStorage) {
unlockManager.close(storage)
vaultsManager.localVault.remove(storage)
return
}
val parent = findParentStorage(storage) ?: return
manageStoragesEncryptionUseCase.clearAndDisableEncryption(parent)
}
/**
* Поднимается по цепочке overlay (sourceUuid -> decrypted view), пока не дойдёт
* до корневого физического storage из [IVaultsManager.localVault].
*/
private fun findPhysicalRootStorage(storage: IStorage): IStorage? {
val locals = vaultsManager.localVault.storages.value ?: return null
private fun findParentStorage(storage: IStorage): IStorage? {
val opened = unlockManager.openedStorages.value
var id: UUID = storage.uuid
while (true) {
val parent = opened.entries.firstOrNull { it.value.uuid == id }?.key ?: break
id = parent
}
return locals.firstOrNull { it.uuid == id }
val parentUuid = opened.entries.firstOrNull { it.value.uuid == storage.uuid }?.key ?: return null
val locals = vaultsManager.localVault.storages.value.orEmpty()
return locals.firstOrNull { it.uuid == parentUuid }
?: opened[parentUuid]
}
}