improvement: Support new per-cipher encryption key
This commit is contained in:
parent
a1210b4ef8
commit
e3be565699
|
@ -26,6 +26,7 @@ data class BitwardenCipher(
|
|||
// service fields
|
||||
override val service: BitwardenService,
|
||||
// common
|
||||
val keyBase64: String? = null,
|
||||
val name: String?,
|
||||
val notes: String?,
|
||||
val favorite: Boolean,
|
||||
|
|
|
@ -226,6 +226,59 @@ class SyncEngine(
|
|||
)
|
||||
}
|
||||
|
||||
fun getCipherCodecPair(
|
||||
mode: BitwardenCrCta.Mode,
|
||||
key: ByteArray?,
|
||||
organizationId: String?,
|
||||
) = kotlin.run {
|
||||
val globalCrypto = getCodec(
|
||||
mode = mode,
|
||||
organizationId = organizationId,
|
||||
)
|
||||
val itemCrypto = if (key != null) kotlin.run {
|
||||
val symmetricCryptoKey = key
|
||||
.let(CryptoKey.Companion::decodeSymmetricOrThrow)
|
||||
val cryptoKey = BitwardenCrKey.CryptoKey(
|
||||
symmetricCryptoKey = symmetricCryptoKey,
|
||||
)
|
||||
val cryptoKeyEnv = BitwardenCrCta.BitwardenCrCtaEnv(
|
||||
key = cryptoKey,
|
||||
)
|
||||
crypto.cta(
|
||||
env = cryptoKeyEnv,
|
||||
mode = mode,
|
||||
)
|
||||
} else {
|
||||
globalCrypto
|
||||
}
|
||||
itemCrypto to globalCrypto
|
||||
}
|
||||
|
||||
fun getCipherCodecPairFromEncrypted(
|
||||
mode: BitwardenCrCta.Mode,
|
||||
keyCipherText: String?,
|
||||
organizationId: String?,
|
||||
) = kotlin.run {
|
||||
val key = if (keyCipherText != null) {
|
||||
val decoderKey = if (organizationId != null) {
|
||||
BitwardenCrKey.OrganizationToken(
|
||||
id = organizationId,
|
||||
)
|
||||
} else {
|
||||
BitwardenCrKey.UserToken
|
||||
}
|
||||
crypto.decoder(decoderKey)(keyCipherText)
|
||||
.data
|
||||
} else {
|
||||
null
|
||||
}
|
||||
getCipherCodecPair(
|
||||
mode = mode,
|
||||
key = key,
|
||||
organizationId = organizationId,
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
// Profile
|
||||
//
|
||||
|
@ -449,6 +502,7 @@ class SyncEngine(
|
|||
|
||||
fun BitwardenCrCta.cipherDecoder(
|
||||
entity: CipherEntity,
|
||||
codec2: BitwardenCrCta,
|
||||
localCipherId: String?,
|
||||
) = kotlin.run {
|
||||
val folderId = entity.folderId
|
||||
|
@ -478,7 +532,7 @@ class SyncEngine(
|
|||
folderId = folderId,
|
||||
entity = entity,
|
||||
)
|
||||
.transform(this)
|
||||
.transform(this, codec2)
|
||||
}
|
||||
|
||||
val cipherDao = db.cipherQueries
|
||||
|
@ -505,11 +559,20 @@ class SyncEngine(
|
|||
model
|
||||
},
|
||||
localDecoder = { local, remote ->
|
||||
val codec = getCodec(
|
||||
val itemKey = local.keyBase64
|
||||
?.let(base64Service::decode)
|
||||
val (
|
||||
itemCrypto,
|
||||
globalCrypto,
|
||||
) = getCipherCodecPair(
|
||||
mode = BitwardenCrCta.Mode.ENCRYPT,
|
||||
key = itemKey,
|
||||
organizationId = local.organizationId,
|
||||
)
|
||||
val encryptedCipher = local.transform(codec)
|
||||
val encryptedCipher = local.transform(
|
||||
itemCrypto = itemCrypto,
|
||||
globalCrypto = globalCrypto,
|
||||
)
|
||||
CipherUpdate.of(
|
||||
model = encryptedCipher,
|
||||
folders = localToRemoteFolders,
|
||||
|
@ -562,13 +625,18 @@ class SyncEngine(
|
|||
remoteItems = response.ciphers.orEmpty(),
|
||||
remoteLens = cipherRemoteLens,
|
||||
remoteDecoder = { remote, local ->
|
||||
val codec = getCodec(
|
||||
val (
|
||||
itemCrypto,
|
||||
globalCrypto,
|
||||
) = getCipherCodecPairFromEncrypted(
|
||||
mode = BitwardenCrCta.Mode.DECRYPT,
|
||||
keyCipherText = remote.key,
|
||||
organizationId = remote.organizationId,
|
||||
)
|
||||
codec
|
||||
itemCrypto
|
||||
.cipherDecoder(
|
||||
entity = remote,
|
||||
codec2 = globalCrypto,
|
||||
localCipherId = local?.cipherId,
|
||||
)
|
||||
.let { remoteDecoded ->
|
||||
|
@ -715,13 +783,20 @@ class SyncEngine(
|
|||
}
|
||||
}
|
||||
|
||||
val codec = getCodec(
|
||||
val itemKey = local.keyBase64
|
||||
?.let(base64Service::decode)
|
||||
val (
|
||||
itemCrypto,
|
||||
globalCrypto,
|
||||
) = getCipherCodecPair(
|
||||
mode = BitwardenCrCta.Mode.DECRYPT,
|
||||
key = itemKey,
|
||||
organizationId = r.source.organizationId,
|
||||
)
|
||||
codec
|
||||
itemCrypto
|
||||
.cipherDecoder(
|
||||
entity = cipherResponse,
|
||||
codec2 = globalCrypto,
|
||||
localCipherId = r.source.cipherId,
|
||||
)
|
||||
.let { remoteDecoded ->
|
||||
|
|
|
@ -554,7 +554,7 @@ fun HttpRequestBuilder.headers(env: ServerEnv) {
|
|||
// Seems like now Bitwarden now requires you to specify
|
||||
// the client name and version.
|
||||
header("Bitwarden-Client-Name", "web")
|
||||
header("Bitwarden-Client-Version", "2023.10.1")
|
||||
header("Bitwarden-Client-Version", "2024.03.0")
|
||||
// App does not work if hidden behind reverse-proxy under
|
||||
// a subdirectory. We should specify the 'referer' so the server
|
||||
// generates correct urls for us.
|
||||
|
|
|
@ -342,6 +342,12 @@ fun BitwardenCrFactoryScope.appendOrganizationToken2(
|
|||
appendEncoder(key, encoder)
|
||||
}
|
||||
|
||||
//
|
||||
// Cipher
|
||||
//
|
||||
|
||||
fun CryptoGenerator.makeCipherCryptoKeyMaterial() = seed(length = 64)
|
||||
|
||||
//
|
||||
// Sends
|
||||
//
|
||||
|
|
|
@ -3,18 +3,20 @@ package com.artemchep.keyguard.provider.bitwarden.crypto
|
|||
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
|
||||
|
||||
fun BitwardenCipher.transform(
|
||||
crypto: BitwardenCrCta,
|
||||
itemCrypto: BitwardenCrCta,
|
||||
globalCrypto: BitwardenCrCta,
|
||||
) = copy(
|
||||
// common
|
||||
name = crypto.transformString(name),
|
||||
notes = crypto.transformString(notes),
|
||||
fields = fields.transform(crypto),
|
||||
attachments = attachments.transform(crypto),
|
||||
keyBase64 = keyBase64?.let(globalCrypto::transformBase64),
|
||||
name = itemCrypto.transformString(name),
|
||||
notes = itemCrypto.transformString(notes),
|
||||
fields = fields.transform(itemCrypto),
|
||||
attachments = attachments.transform(itemCrypto),
|
||||
// types
|
||||
login = login?.transform(crypto),
|
||||
secureNote = secureNote?.transform(crypto),
|
||||
card = card?.transform(crypto),
|
||||
identity = identity?.transform(crypto),
|
||||
login = login?.transform(itemCrypto),
|
||||
secureNote = secureNote?.transform(itemCrypto),
|
||||
card = card?.transform(itemCrypto),
|
||||
identity = identity?.transform(itemCrypto),
|
||||
)
|
||||
|
||||
@JvmName("encryptListOfBitwardenCipherAttachment")
|
||||
|
|
|
@ -30,6 +30,7 @@ fun BitwardenCipher.Companion.encrypted(
|
|||
createdDate = entity.creationDate,
|
||||
revisionDate = entity.revisionDate,
|
||||
deletedDate = entity.deletedDate,
|
||||
keyBase64 = entity.key,
|
||||
// service fields
|
||||
service = service,
|
||||
// common
|
||||
|
|
|
@ -22,6 +22,9 @@ data class CipherEntity(
|
|||
@JsonNames("userId")
|
||||
@SerialName("UserId")
|
||||
val userId: String? = null,
|
||||
@JsonNames("key")
|
||||
@SerialName("Key")
|
||||
val key: String? = null,
|
||||
@JsonNames("edit")
|
||||
@SerialName("Edit")
|
||||
val edit: Boolean = true,
|
||||
|
|
|
@ -15,6 +15,8 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
data class CipherRequest(
|
||||
@SerialName("key")
|
||||
val key: String?,
|
||||
@SerialName("type")
|
||||
val type: CipherTypeEntity,
|
||||
@SerialName("organizationId")
|
||||
|
@ -93,7 +95,9 @@ fun CipherRequest.Companion.of(
|
|||
attachments to attachments2
|
||||
}
|
||||
|
||||
val keyBase64 = model.keyBase64
|
||||
CipherRequest(
|
||||
key = keyBase64,
|
||||
type = type,
|
||||
organizationId = model.organizationId,
|
||||
folderId = model.folderId
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.artemchep.keyguard.common.model.DSecret
|
|||
import com.artemchep.keyguard.common.model.canDelete
|
||||
import com.artemchep.keyguard.common.model.create.CreateRequest
|
||||
import com.artemchep.keyguard.common.service.crypto.CryptoGenerator
|
||||
import com.artemchep.keyguard.common.service.text.Base64Service
|
||||
import com.artemchep.keyguard.common.usecase.AddCipher
|
||||
import com.artemchep.keyguard.common.usecase.AddFolder
|
||||
import com.artemchep.keyguard.common.usecase.GetPasswordStrength
|
||||
|
@ -23,6 +24,9 @@ import com.artemchep.keyguard.common.usecase.TrashCipherById
|
|||
import com.artemchep.keyguard.core.store.bitwarden.BitwardenCipher
|
||||
import com.artemchep.keyguard.core.store.bitwarden.BitwardenService
|
||||
import com.artemchep.keyguard.feature.confirmation.organization.FolderInfo
|
||||
import com.artemchep.keyguard.platform.util.isRelease
|
||||
import com.artemchep.keyguard.provider.bitwarden.crypto.makeCipherCryptoKeyMaterial
|
||||
import com.artemchep.keyguard.provider.bitwarden.crypto.makeSendCryptoKeyMaterial
|
||||
import com.artemchep.keyguard.provider.bitwarden.usecase.util.ModifyDatabase
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.datetime.Clock
|
||||
|
@ -39,6 +43,7 @@ class AddCipherImpl(
|
|||
private val trashCipherById: TrashCipherById,
|
||||
private val cryptoGenerator: CryptoGenerator,
|
||||
private val getPasswordStrength: GetPasswordStrength,
|
||||
private val base64Service: Base64Service,
|
||||
) : AddCipher {
|
||||
companion object {
|
||||
private const val TAG = "AddCipher.bitwarden"
|
||||
|
@ -52,6 +57,7 @@ class AddCipherImpl(
|
|||
trashCipherById = directDI.instance(),
|
||||
cryptoGenerator = directDI.instance(),
|
||||
getPasswordStrength = directDI.instance(),
|
||||
base64Service = directDI.instance(),
|
||||
)
|
||||
|
||||
override fun invoke(
|
||||
|
@ -106,6 +112,7 @@ class AddCipherImpl(
|
|||
val old = oldCiphersMap[cipherId]?.data_
|
||||
BitwardenCipher.of(
|
||||
cryptoGenerator = cryptoGenerator,
|
||||
base64Service = base64Service,
|
||||
getPasswordStrength = getPasswordStrength,
|
||||
now = now,
|
||||
request = request,
|
||||
|
@ -233,6 +240,7 @@ class AddCipherImpl(
|
|||
|
||||
private suspend fun BitwardenCipher.Companion.of(
|
||||
cryptoGenerator: CryptoGenerator,
|
||||
base64Service: Base64Service,
|
||||
getPasswordStrength: GetPasswordStrength,
|
||||
request: CreateRequest,
|
||||
now: Instant,
|
||||
|
@ -374,6 +382,13 @@ private suspend fun BitwardenCipher.Companion.of(
|
|||
}
|
||||
}
|
||||
|
||||
val keyBase64 = old?.keyBase64
|
||||
?: if (!isRelease) {
|
||||
val key = cryptoGenerator.makeCipherCryptoKeyMaterial()
|
||||
base64Service.encodeToString(key)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val cipherId = old?.cipherId ?: cryptoGenerator.uuid()
|
||||
val createdDate = old?.createdDate ?: request.now
|
||||
val deletedDate = old?.deletedDate
|
||||
|
@ -386,6 +401,7 @@ private suspend fun BitwardenCipher.Companion.of(
|
|||
revisionDate = now,
|
||||
createdDate = createdDate,
|
||||
deletedDate = deletedDate,
|
||||
keyBase64 = keyBase64,
|
||||
// service fields
|
||||
service = BitwardenService(
|
||||
remote = old?.service?.remote,
|
||||
|
|
Loading…
Reference in New Issue