Skip to content

Commit 0892e0f

Browse files
authored
[PM-21782] Pass encryptedFor to cipher functions (#5297)
1 parent 0934d47 commit 0892e0f

File tree

20 files changed

+372
-137
lines changed

20 files changed

+372
-137
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSource.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.bitwarden.vault.CipherListView
2222
import com.bitwarden.vault.CipherView
2323
import com.bitwarden.vault.Collection
2424
import com.bitwarden.vault.CollectionView
25+
import com.bitwarden.vault.EncryptionContext
2526
import com.bitwarden.vault.Folder
2627
import com.bitwarden.vault.FolderView
2728
import com.bitwarden.vault.PasswordHistory
@@ -187,7 +188,7 @@ interface VaultSdkSource {
187188
suspend fun encryptCipher(
188189
userId: String,
189190
cipherView: CipherView,
190-
): Result<Cipher>
191+
): Result<EncryptionContext>
191192

192193
/**
193194
* Decrypts a [Cipher] for the user with the given [userId], returning a [CipherView] wrapped
@@ -349,13 +350,13 @@ interface VaultSdkSource {
349350
): Result<List<FolderView>>
350351

351352
/**
352-
* Decrypts a [cipher] [attachment] file found at [encryptedFilePath] saving it at
353+
* Decrypts a [cipher] [attachmentView] file found at [encryptedFilePath] saving it at
353354
* [decryptedFilePath] for the user with the given [userId]
354355
*/
355356
suspend fun decryptFile(
356357
userId: String,
357358
cipher: Cipher,
358-
attachment: Attachment,
359+
attachmentView: AttachmentView,
359360
encryptedFilePath: String,
360361
decryptedFilePath: String,
361362
): Result<Unit>

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/VaultSdkSourceImpl.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import com.bitwarden.vault.CipherListView
2525
import com.bitwarden.vault.CipherView
2626
import com.bitwarden.vault.Collection
2727
import com.bitwarden.vault.CollectionView
28+
import com.bitwarden.vault.EncryptionContext
2829
import com.bitwarden.vault.Folder
2930
import com.bitwarden.vault.FolderView
3031
import com.bitwarden.vault.PasswordHistory
@@ -269,7 +270,7 @@ class VaultSdkSourceImpl(
269270
override suspend fun encryptCipher(
270271
userId: String,
271272
cipherView: CipherView,
272-
): Result<Cipher> =
273+
): Result<EncryptionContext> =
273274
runCatchingWithLogs {
274275
getClient(userId = userId)
275276
.vault()
@@ -389,7 +390,7 @@ class VaultSdkSourceImpl(
389390
override suspend fun decryptFile(
390391
userId: String,
391392
cipher: Cipher,
392-
attachment: Attachment,
393+
attachmentView: AttachmentView,
393394
encryptedFilePath: String,
394395
decryptedFilePath: String,
395396
): Result<Unit> =
@@ -399,7 +400,7 @@ class VaultSdkSourceImpl(
399400
.attachments()
400401
.decryptFile(
401402
cipher = cipher,
402-
attachment = attachment,
403+
attachment = attachmentView,
403404
encryptedFilePath = encryptedFilePath,
404405
decryptedFilePath = decryptedFilePath,
405406
)

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/Fido2CredentialStoreImpl.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package com.x8bit.bitwarden.data.vault.datasource.sdk.model
33
import com.bitwarden.annotation.OmitFromCoverage
44
import com.bitwarden.fido.Fido2CredentialAutofillView
55
import com.bitwarden.sdk.Fido2CredentialStore
6-
import com.bitwarden.vault.Cipher
76
import com.bitwarden.vault.CipherListView
87
import com.bitwarden.vault.CipherView
8+
import com.bitwarden.vault.EncryptionContext
99
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
1010
import com.x8bit.bitwarden.data.autofill.util.isActiveWithFido2Credentials
1111
import com.x8bit.bitwarden.data.vault.datasource.sdk.VaultSdkSource
@@ -91,11 +91,12 @@ class Fido2CredentialStoreImpl(
9191
/**
9292
* Save the provided [cred] to the users vault.
9393
*/
94-
override suspend fun saveCredential(cred: Cipher) {
95-
val userId = getActiveUserIdOrThrow()
96-
94+
override suspend fun saveCredential(cred: EncryptionContext) {
9795
vaultSdkSource
98-
.decryptCipher(userId, cred)
96+
.decryptCipher(
97+
userId = cred.encryptedFor,
98+
cipher = cred.cipher,
99+
)
99100
.map { decryptedCipherView ->
100101
decryptedCipherView.id
101102
?.let { vaultRepository.updateCipher(it, decryptedCipherView) }

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import com.bitwarden.network.model.UpdateCipherCollectionsJsonRequest
1212
import com.bitwarden.network.model.UpdateCipherResponseJson
1313
import com.bitwarden.network.service.CiphersService
1414
import com.bitwarden.vault.AttachmentView
15-
import com.bitwarden.vault.Cipher
1615
import com.bitwarden.vault.CipherView
16+
import com.bitwarden.vault.EncryptionContext
1717
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
1818
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
1919
import com.x8bit.bitwarden.data.platform.manager.ReviewPromptManager
@@ -80,10 +80,10 @@ class CipherManagerImpl(
8080
userId = userId,
8181
cipherView = cipherView,
8282
)
83-
.flatMap { cipher ->
83+
.flatMap {
8484
ciphersService.createCipherInOrganization(
8585
body = CreateCipherInOrganizationJsonRequest(
86-
cipher = cipher.toEncryptedNetworkCipher(),
86+
cipher = it.toEncryptedNetworkCipher(),
8787
collectionIds = collectionIds,
8888
),
8989
)
@@ -123,10 +123,15 @@ class CipherManagerImpl(
123123
?: return DeleteCipherResult.Error(error = NoActiveUserException())
124124
return cipherView
125125
.encryptCipherAndCheckForMigration(userId = userId, cipherId = cipherId)
126-
.flatMap { cipher ->
126+
.flatMap { encryptionContext ->
127127
ciphersService
128128
.softDeleteCipher(cipherId = cipherId)
129-
.flatMap { vaultSdkSource.decryptCipher(userId = userId, cipher = cipher) }
129+
.flatMap {
130+
vaultSdkSource.decryptCipher(
131+
userId = userId,
132+
cipher = encryptionContext.cipher,
133+
)
134+
}
130135
}
131136
.flatMap {
132137
vaultSdkSource.encryptCipher(
@@ -165,7 +170,7 @@ class CipherManagerImpl(
165170
cipherId: String,
166171
attachmentId: String,
167172
cipherView: CipherView,
168-
): Result<Cipher> {
173+
): Result<EncryptionContext> {
169174
val userId = activeUserId ?: return NoActiveUserException().asFailure()
170175
return ciphersService
171176
.deleteCipherAttachment(
@@ -181,10 +186,10 @@ class CipherManagerImpl(
181186
)
182187
.encryptCipherAndCheckForMigration(userId = userId, cipherId = cipherId)
183188
}
184-
.onSuccess { cipher ->
189+
.onSuccess { encryptionContext ->
185190
vaultDiskSource.saveCipher(
186191
userId = userId,
187-
cipher = cipher.toEncryptedNetworkCipherResponse(),
192+
cipher = encryptionContext.toEncryptedNetworkCipherResponse(),
188193
)
189194
}
190195
}
@@ -220,10 +225,10 @@ class CipherManagerImpl(
220225
userId = userId,
221226
cipherView = cipherView,
222227
)
223-
.flatMap { cipher ->
228+
.flatMap {
224229
ciphersService.updateCipher(
225230
cipherId = cipherId,
226-
body = cipher.toEncryptedNetworkCipher(),
231+
body = it.toEncryptedNetworkCipher(),
227232
)
228233
}
229234
.map { response ->
@@ -263,11 +268,11 @@ class CipherManagerImpl(
263268
)
264269
}
265270
.flatMap { vaultSdkSource.encryptCipher(userId = userId, cipherView = it) }
266-
.flatMap { cipher ->
271+
.flatMap {
267272
ciphersService.shareCipher(
268273
cipherId = cipherId,
269274
body = ShareCipherJsonRequest(
270-
cipher = cipher.toEncryptedNetworkCipher(),
275+
cipher = it.toEncryptedNetworkCipher(),
271276
collectionIds = collectionIds,
272277
),
273278
)
@@ -301,10 +306,10 @@ class CipherManagerImpl(
301306
cipherView = cipherView.copy(collectionIds = collectionIds),
302307
)
303308
}
304-
.onSuccess { cipher ->
309+
.onSuccess { encryptionContext ->
305310
vaultDiskSource.saveCipher(
306311
userId = userId,
307-
cipher = cipher.toEncryptedNetworkCipherResponse(),
312+
cipher = encryptionContext.toEncryptedNetworkCipherResponse(),
308313
)
309314
}
310315
.fold(
@@ -362,14 +367,14 @@ class CipherManagerImpl(
362367
userId = userId,
363368
cipherId = requireNotNull(cipherView.id),
364369
)
365-
.flatMap { cipher ->
370+
.flatMap { encryptionContext ->
366371
fileManager
367372
.writeUriToCache(fileUri = fileUri)
368373
.flatMap { cacheFile ->
369374
vaultSdkSource
370375
.encryptAttachment(
371376
userId = userId,
372-
cipher = cipher,
377+
cipher = encryptionContext.cipher,
373378
attachmentView = attachmentView,
374379
decryptedFilePath = cacheFile.absolutePath,
375380
encryptedFilePath = "${cacheFile.absolutePath}.enc",
@@ -447,10 +452,10 @@ class CipherManagerImpl(
447452
cipherId = requireNotNull(cipherView.id),
448453
)
449454
.fold(
450-
onSuccess = { it },
455+
onSuccess = { it.cipher },
451456
onFailure = { return it.asFailure() },
452457
)
453-
val attachment = cipher.attachments?.find { it.id == attachmentId }
458+
val attachmentView = cipherView.attachments?.find { it.id == attachmentId }
454459
?: return IllegalStateException("No attachment to download").asFailure()
455460

456461
val attachmentData = ciphersService
@@ -479,7 +484,7 @@ class CipherManagerImpl(
479484
.decryptFile(
480485
userId = userId,
481486
cipher = cipher,
482-
attachment = attachment,
487+
attachmentView = attachmentView,
483488
encryptedFilePath = encryptedFile.path,
484489
decryptedFilePath = decryptedFile.path,
485490
)
@@ -494,17 +499,17 @@ class CipherManagerImpl(
494499
private suspend fun CipherView.encryptCipherAndCheckForMigration(
495500
userId: String,
496501
cipherId: String,
497-
): Result<Cipher> =
502+
): Result<EncryptionContext> =
498503
vaultSdkSource
499504
.encryptCipher(userId = userId, cipherView = this)
500-
.flatMap {
505+
.flatMap { encryptionContext ->
501506
// We only migrate the cipher if the original cipher did not have a key and the
502507
// new cipher does. This means the SDK created the key and migration is required.
503-
if (it.key != null && this.key == null) {
508+
if (encryptionContext.cipher.key != null && this.key == null) {
504509
ciphersService
505510
.updateCipher(
506511
cipherId = cipherId,
507-
body = it.toEncryptedNetworkCipher(),
512+
body = encryptionContext.toEncryptedNetworkCipher(),
508513
)
509514
.flatMap { response ->
510515
when (response) {
@@ -520,12 +525,14 @@ class CipherManagerImpl(
520525
userId = userId,
521526
cipher = response.cipher,
522527
)
523-
response.cipher.toEncryptedSdkCipher().asSuccess()
528+
encryptionContext
529+
.copy(cipher = response.cipher.toEncryptedSdkCipher())
530+
.asSuccess()
524531
}
525532
}
526533
}
527534
} else {
528-
it.asSuccess()
535+
encryptionContext.asSuccess()
529536
}
530537
}
531538

@@ -541,7 +548,7 @@ class CipherManagerImpl(
541548
?: return IllegalStateException("CipherView must have an ID").asFailure()
542549
var migratedCipherView = cipherView
543550
.encryptCipherAndCheckForMigration(userId = userId, cipherId = cipherViewId)
544-
.flatMap { vaultSdkSource.decryptCipher(userId = userId, cipher = it) }
551+
.flatMap { vaultSdkSource.decryptCipher(userId = userId, cipher = it.cipher) }
545552
.getOrElse { return it.asFailure() }
546553

547554
attachmentViewsToMigrate
@@ -574,7 +581,12 @@ class CipherManagerImpl(
574581
cipherId = cipherViewId,
575582
)
576583
}
577-
.flatMap { vaultSdkSource.decryptCipher(userId = userId, cipher = it) }
584+
.flatMap {
585+
vaultSdkSource.decryptCipher(
586+
userId = userId,
587+
cipher = it.cipher,
588+
)
589+
}
578590
.onSuccess { migratedCipherView = it }
579591
}
580592
?: IllegalStateException("AttachmentView must have an ID").asFailure()

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/manager/VaultLockManagerImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class VaultLockManagerImpl(
181181
email = email,
182182
privateKey = privateKey,
183183
method = initUserCryptoMethod,
184+
userId = userId,
184185
),
185186
)
186187
.flatMap { result ->

app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.bitwarden.vault.CipherPermissions
2020
import com.bitwarden.vault.CipherRepromptType
2121
import com.bitwarden.vault.CipherType
2222
import com.bitwarden.vault.CipherView
23+
import com.bitwarden.vault.EncryptionContext
2324
import com.bitwarden.vault.Fido2Credential
2425
import com.bitwarden.vault.Field
2526
import com.bitwarden.vault.FieldType
@@ -37,8 +38,12 @@ import java.time.ZonedDateTime
3738
/**
3839
* Converts a Bitwarden SDK [Cipher] object to a corresponding
3940
* [SyncResponseJson.Cipher] object.
41+
*
42+
* @param encryptedFor The ID of the user who this cipher is encrypted by.
4043
*/
41-
fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
44+
fun Cipher.toEncryptedNetworkCipher(
45+
encryptedFor: String,
46+
): CipherJsonRequest =
4247
CipherJsonRequest(
4348
notes = notes,
4449
attachments = attachments
@@ -59,13 +64,18 @@ fun Cipher.toEncryptedNetworkCipher(): CipherJsonRequest =
5964
card = card?.toEncryptedNetworkCard(),
6065
key = key,
6166
sshKey = sshKey?.toEncryptedNetworkSshKey(),
67+
encryptedFor = encryptedFor,
6268
)
6369

6470
/**
6571
* Converts a Bitwarden SDK [Cipher] object to a corresponding
6672
* [SyncResponseJson.Cipher] object.
73+
*
74+
* @param encryptedFor The ID of the user who this cipher is encrypted by.
6775
*/
68-
fun Cipher.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
76+
fun Cipher.toEncryptedNetworkCipherResponse(
77+
encryptedFor: String,
78+
): SyncResponseJson.Cipher =
6979
SyncResponseJson.Cipher(
7080
notes = notes,
7181
reprompt = reprompt.toNetworkRepromptType(),
@@ -92,6 +102,7 @@ fun Cipher.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
92102
id = id.orEmpty(),
93103
shouldViewPassword = viewPassword,
94104
key = key,
105+
encryptedFor = encryptedFor,
95106
)
96107

97108
/**
@@ -626,3 +637,17 @@ fun List<CipherListView>.sortAlphabetically(): List<CipherListView> {
626637
},
627638
)
628639
}
640+
641+
/**
642+
* Converts a Bitwarden SDK [EncryptionContext] object to a corresponding [CipherJsonRequest]
643+
* object.
644+
*/
645+
fun EncryptionContext.toEncryptedNetworkCipher(): CipherJsonRequest =
646+
cipher.toEncryptedNetworkCipher(encryptedFor = encryptedFor)
647+
648+
/**
649+
* Converts a Bitwarden SDK [EncryptionContext] object to a corresponding [SyncResponseJson.Cipher]
650+
* object.
651+
*/
652+
fun EncryptionContext.toEncryptedNetworkCipherResponse(): SyncResponseJson.Cipher =
653+
cipher.toEncryptedNetworkCipherResponse(encryptedFor = encryptedFor)

app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,8 @@ private const val CIPHER_JSON = """
419419
"publicKey": "mockPublicKey-1",
420420
"privateKey": "mockPrivateKey-1",
421421
"keyFingerprint": "mockKeyFingerprint-1"
422-
}
422+
},
423+
"encryptedFor": "mockEncryptedFor-1"
423424
}
424425
"""
425426

0 commit comments

Comments
 (0)