Полное управление шифрованием и ключами
This commit is contained in:
@@ -12,7 +12,7 @@ interface IAppDb {
|
||||
val storageMetaInfoDao: StorageMetaInfoDao
|
||||
}
|
||||
|
||||
@Database(entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class], version = 2, exportSchema = false)
|
||||
@Database(entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class], version = 3, exportSchema = false)
|
||||
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||
|
||||
@@ -7,16 +7,15 @@ import com.github.nullptroma.wallenc.data.model.StorageKeyMap
|
||||
import com.github.nullptroma.wallenc.domain.datatypes.EncryptKey
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "storage_key_maps", primaryKeys = [ "source_uuid", "dest_uuid" ])
|
||||
@Entity(tableName = "storage_key_maps")
|
||||
data class DbStorageKeyMap(
|
||||
@androidx.room.PrimaryKey
|
||||
@ColumnInfo(name = "source_uuid") val sourceUuid: UUID,
|
||||
@ColumnInfo(name = "dest_uuid") val destUuid: UUID,
|
||||
@ColumnInfo(name = "key") val key: ByteArray
|
||||
) {
|
||||
fun toModel(): StorageKeyMap {
|
||||
return StorageKeyMap(
|
||||
sourceUuid = sourceUuid,
|
||||
destUuid = destUuid,
|
||||
key = EncryptKey(key)
|
||||
)
|
||||
}
|
||||
@@ -28,7 +27,6 @@ data class DbStorageKeyMap(
|
||||
other as DbStorageKeyMap
|
||||
|
||||
if (sourceUuid != other.sourceUuid) return false
|
||||
if (destUuid != other.destUuid) return false
|
||||
if (!key.contentEquals(other.key)) return false
|
||||
|
||||
return true
|
||||
@@ -36,7 +34,6 @@ data class DbStorageKeyMap(
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = sourceUuid.hashCode()
|
||||
result = 31 * result + destUuid.hashCode()
|
||||
result = 31 * result + key.contentHashCode()
|
||||
return result
|
||||
}
|
||||
@@ -45,7 +42,6 @@ data class DbStorageKeyMap(
|
||||
fun fromModel(keymap: StorageKeyMap): DbStorageKeyMap {
|
||||
return DbStorageKeyMap(
|
||||
sourceUuid = keymap.sourceUuid,
|
||||
destUuid = keymap.destUuid,
|
||||
key = keymap.key.bytes
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,5 @@ import java.util.UUID
|
||||
|
||||
data class StorageKeyMap(
|
||||
val sourceUuid: UUID,
|
||||
val destUuid: UUID,
|
||||
val key: EncryptKey
|
||||
)
|
||||
|
||||
@@ -17,6 +17,8 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
|
||||
class UnlockManager(
|
||||
@@ -45,7 +47,7 @@ class UnlockManager(
|
||||
continue
|
||||
}
|
||||
try {
|
||||
val encStorage = createEncryptedStorage(storage, key.key, key.destUuid)
|
||||
val encStorage = createEncryptedStorage(storage, key.key, getDestUuid(storage.uuid))
|
||||
map[storage.uuid] = encStorage
|
||||
allStorages.removeAt(allStorages.size - 1)
|
||||
allStorages.add(encStorage)
|
||||
@@ -72,9 +74,34 @@ class UnlockManager(
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDestUuid(sourceUuid: UUID): UUID {
|
||||
return uuid5(
|
||||
namespace = UUID.fromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"), // URL namespace
|
||||
name = "$sourceUuid:open"
|
||||
)
|
||||
}
|
||||
|
||||
private fun uuid5(namespace: UUID, name: String): UUID {
|
||||
val digest = MessageDigest.getInstance("SHA-1")
|
||||
val nsBytes = ByteBuffer.allocate(16)
|
||||
.putLong(namespace.mostSignificantBits)
|
||||
.putLong(namespace.leastSignificantBits)
|
||||
.array()
|
||||
digest.update(nsBytes)
|
||||
digest.update(name.toByteArray(Charsets.UTF_8))
|
||||
val hash = digest.digest()
|
||||
|
||||
hash[6] = (hash[6].toInt() and 0x0f or 0x50).toByte() // version 5
|
||||
hash[8] = (hash[8].toInt() and 0x3f or 0x80).toByte() // RFC 4122 variant
|
||||
|
||||
val bb = ByteBuffer.wrap(hash, 0, 16)
|
||||
return UUID(bb.long, bb.long)
|
||||
}
|
||||
|
||||
override suspend fun open(
|
||||
storage: IStorage,
|
||||
key: EncryptKey
|
||||
key: EncryptKey,
|
||||
rememberPassword: Boolean
|
||||
): EncryptedStorage = withContext(ioDispatcher) {
|
||||
return@withContext mutex.withLock {
|
||||
val encInfo = storage.metaInfo.value.encInfo ?: throw Exception("EncInfo is null") // TODO
|
||||
@@ -84,17 +111,18 @@ class UnlockManager(
|
||||
val opened = _openedStorages.value.toMutableMap()
|
||||
val cur = opened[storage.uuid]
|
||||
if (cur != null)
|
||||
throw Exception("Storage is already open")
|
||||
return@withLock cur
|
||||
|
||||
val keymap = StorageKeyMap(
|
||||
sourceUuid = storage.uuid,
|
||||
destUuid = UUID.randomUUID(),
|
||||
key = key
|
||||
)
|
||||
val encStorage = createEncryptedStorage(storage, keymap.key, keymap.destUuid)
|
||||
val encStorage = createEncryptedStorage(storage, keymap.key, getDestUuid(storage.uuid))
|
||||
opened[storage.uuid] = encStorage
|
||||
_openedStorages.value = opened
|
||||
keymapRepository.add(keymap)
|
||||
if (rememberPassword) {
|
||||
keymapRepository.add(keymap)
|
||||
}
|
||||
encStorage
|
||||
}
|
||||
}
|
||||
@@ -111,30 +139,21 @@ class UnlockManager(
|
||||
}
|
||||
}
|
||||
|
||||
// Закрытие отображения по экземпляру (source или decrypted).
|
||||
// Закрытие только по source-экземпляру.
|
||||
override suspend fun close(storage: IStorage) {
|
||||
val opened = _openedStorages.value
|
||||
val source = opened.entries.firstOrNull {
|
||||
it.key == storage.uuid || it.value.uuid == storage.uuid
|
||||
if (opened.containsKey(storage.uuid)) {
|
||||
close(storage.uuid)
|
||||
}
|
||||
if (source != null)
|
||||
close(source.key)
|
||||
}
|
||||
|
||||
private suspend fun closeBySourceUuid(opened: MutableMap<UUID, EncryptedStorage>, sourceUuid: UUID) {
|
||||
val enc = opened[sourceUuid] ?: return
|
||||
val childSourceUuid = opened.entries.firstOrNull { it.value.uuid == enc.uuid }?.key
|
||||
if (childSourceUuid != null) {
|
||||
closeBySourceUuid(opened, childSourceUuid)
|
||||
val nestedSourceUuid = enc.uuid
|
||||
if (nestedSourceUuid != sourceUuid && opened.containsKey(nestedSourceUuid)) {
|
||||
closeBySourceUuid(opened, nestedSourceUuid)
|
||||
}
|
||||
opened.remove(sourceUuid)
|
||||
enc.dispose()
|
||||
keymapRepository.delete(
|
||||
StorageKeyMap(
|
||||
sourceUuid = sourceUuid,
|
||||
destUuid = enc.uuid,
|
||||
key = EncryptKey("")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -35,6 +36,8 @@ class EncryptedStorage private constructor(
|
||||
get() = accessor.size
|
||||
override val numberOfFiles: StateFlow<Int?>
|
||||
get() = accessor.numberOfFiles
|
||||
override val isEmpty: Flow<Boolean?>
|
||||
get() = accessor.numberOfFiles.map { n -> n?.let { it == 0 } }
|
||||
|
||||
private val _metaInfo = MutableStateFlow<IStorageMetaInfo>(
|
||||
CommonStorageMetaInfo()
|
||||
@@ -102,7 +105,7 @@ class EncryptedStorage private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = scope.run {
|
||||
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo?) = scope.run {
|
||||
val curMeta = metaInfo.value
|
||||
updateMetaInfo(
|
||||
CommonStorageMetaInfo(
|
||||
@@ -112,6 +115,20 @@ class EncryptedStorage private constructor(
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun clearAllContent() = scope.run {
|
||||
val files = accessor.getAllFiles()
|
||||
val dirs = accessor.getAllDirs()
|
||||
val paths = buildList {
|
||||
addAll(files.map { it.metaInfo.path })
|
||||
addAll(dirs.map { it.metaInfo.path })
|
||||
}
|
||||
.filter { it != "/" && it.isNotBlank() }
|
||||
.sortedByDescending { it.length }
|
||||
for (path in paths) {
|
||||
accessor.delete(path)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
accessor.dispose()
|
||||
job.cancel()
|
||||
|
||||
@@ -7,8 +7,10 @@ import com.github.nullptroma.wallenc.domain.interfaces.IStorage
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageAccessor
|
||||
import com.github.nullptroma.wallenc.domain.interfaces.IStorageMetaInfo
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
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
|
||||
@@ -23,6 +25,8 @@ class LocalStorage(
|
||||
get() = accessor.size
|
||||
override val numberOfFiles: StateFlow<Int?>
|
||||
get() = accessor.numberOfFiles
|
||||
override val isEmpty: Flow<Boolean?>
|
||||
get() = accessor.numberOfFiles.map { n -> n?.let { it == 0 } }
|
||||
|
||||
private val _metaInfo = MutableStateFlow<IStorageMetaInfo>(
|
||||
CommonStorageMetaInfo()
|
||||
@@ -82,7 +86,7 @@ class LocalStorage(
|
||||
))
|
||||
}
|
||||
|
||||
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo) = withContext(ioDispatcher) {
|
||||
override suspend fun setEncInfo(encInfo: StorageEncryptionInfo?) = withContext(ioDispatcher) {
|
||||
val curMeta = metaInfo.value
|
||||
updateMetaInfo(CommonStorageMetaInfo(
|
||||
encInfo = encInfo,
|
||||
@@ -90,6 +94,20 @@ class LocalStorage(
|
||||
))
|
||||
}
|
||||
|
||||
override suspend fun clearAllContent() = withContext(ioDispatcher) {
|
||||
val files = accessor.getAllFiles()
|
||||
val dirs = accessor.getAllDirs()
|
||||
val paths = buildList {
|
||||
addAll(files.map { it.metaInfo.path })
|
||||
addAll(dirs.map { it.metaInfo.path })
|
||||
}
|
||||
.filter { it != "/" && it.isNotBlank() }
|
||||
.sortedByDescending { it.length }
|
||||
for (path in paths) {
|
||||
accessor.delete(path)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STORAGE_INFO_FILE_POSTFIX = ".storage-info"
|
||||
private val jackson = jacksonObjectMapper().apply { findAndRegisterModules() }
|
||||
|
||||
@@ -494,10 +494,15 @@ class LocalStorageAccessor(
|
||||
}
|
||||
|
||||
override suspend fun delete(path: String) = withContext(ioDispatcher) {
|
||||
if (path == "/" || path.isBlank()) {
|
||||
throw IllegalArgumentException("Deleting root path is forbidden")
|
||||
}
|
||||
val pair = LocalStorageFilePair.from(_filesystemBasePath, path)
|
||||
if (pair != null) {
|
||||
pair.file.delete()
|
||||
if (pair.file.isDirectory) pair.file.deleteRecursively()
|
||||
else pair.file.delete()
|
||||
pair.metaFile.delete()
|
||||
scanSizeAndNumOfFiles()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user