-
Notifications
You must be signed in to change notification settings - Fork 2k
PM-41073: Consume breaking changes from SDK making all children of CipherView decrypted #22245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
be5a7a1
33b19b4
5da5fc5
55982bc
f8d0e2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import { EncString } from "../../key-management/crypto/models/enc-string"; | ||
| import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key"; | ||
| import { CipherRepromptType } from "../../vault/enums/cipher-reprompt-type"; | ||
| import { CipherType } from "../../vault/enums/cipher-type"; | ||
| import { Cipher as CipherDomain } from "../../vault/models/domain/cipher"; | ||
|
|
@@ -43,7 +44,12 @@ export class CipherExport { | |
| view.notes = req.notes; | ||
| view.favorite = req.favorite; | ||
| view.reprompt = req.reprompt ?? CipherRepromptType.None; | ||
| view.key = req.key != null ? new EncString(req.key) : undefined; | ||
| try { | ||
| view.key = req.key != null ? SymmetricCryptoKey.fromString(req.key) : undefined; | ||
| } catch { | ||
| // Old exports stored the wrapped EncString key which cannot be used on import | ||
| view.key = undefined; | ||
| } | ||
|
|
||
| if (req.fields != null) { | ||
| view.fields = req.fields.map((f) => FieldExport.toView(f)); | ||
|
|
@@ -208,7 +214,10 @@ export class CipherExport { | |
| this.name = safeGetString(o.name) ?? ""; | ||
| this.notes = safeGetString(o.notes); | ||
| if ("key" in o) { | ||
| this.key = o.key?.encryptedString; | ||
| this.key = | ||
| o.key instanceof SymmetricCryptoKey | ||
| ? o.key.toBase64() | ||
| : (o.key as EncString | undefined)?.encryptedString; | ||
|
Comment on lines
+217
to
+220
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Details and fix
The export services already guard against this ( Consider redacting |
||
| } | ||
|
|
||
| this.favorite = o.favorite; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,6 @@ export class Attachment extends Domain { | |
|
|
||
| if (this.key != null) { | ||
| view.key = await this.decryptAttachmentKey(decryptionKey); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Details and fix
expect(view).toEqual({
...
key: expect.any(SymmetricCryptoKey),
encryptedKey: attachment.key,
});
|
||
| view.encryptedKey = this.key; // Keep the encrypted key for the view | ||
|
|
||
| // When the attachment key couldn't be decrypted, mark a decryption error | ||
| // The file won't be able to be downloaded in these cases | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,10 +7,10 @@ import { | |
| CipherView as SdkCipherView, | ||
| } from "@bitwarden/sdk-internal"; | ||
|
|
||
| import { EncString } from "../../../key-management/crypto/models/enc-string"; | ||
| import { View } from "../../../models/view/view"; | ||
| import { asUuid, uuidAsString } from "../../../platform/abstractions/sdk/sdk.service"; | ||
| import { InitializerMetadata } from "../../../platform/interfaces/initializer-metadata.interface"; | ||
| import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; | ||
| import { InitializerKey } from "../../../platform/services/cryptography/initializer-key"; | ||
| import { DeepJsonify } from "../../../types/deep-jsonify"; | ||
| import { CipherType, LinkedIdType } from "../../enums"; | ||
|
|
@@ -23,7 +23,6 @@ import { AttachmentView } from "./attachment.view"; | |
| import { BankAccountView } from "./bank-account.view"; | ||
| import { CardView } from "./card.view"; | ||
| import { DriversLicenseView } from "./drivers-license.view"; | ||
| import { Fido2CredentialView } from "./fido2-credential.view"; | ||
| import { FieldView } from "./field.view"; | ||
| import { IdentityView } from "./identity.view"; | ||
| import { ItemView } from "./item.view"; | ||
|
|
@@ -65,9 +64,7 @@ export class CipherView implements View, InitializerMetadata { | |
| deletedDate?: Date; | ||
| archivedDate?: Date; | ||
| reprompt: CipherRepromptType = CipherRepromptType.None; | ||
| // We need a copy of the encrypted key so we can pass it to | ||
| // the SdkCipherView during encryption | ||
| key?: EncString; | ||
| key?: SymmetricCryptoKey; | ||
|
|
||
| /** | ||
| * Flag to indicate if the cipher decryption failed. | ||
|
|
@@ -97,7 +94,6 @@ export class CipherView implements View, InitializerMetadata { | |
| this.archivedDate = c.archivedDate; | ||
| // Old locally stored ciphers might have reprompt == null. If so set it to None. | ||
| this.reprompt = c.reprompt ?? CipherRepromptType.None; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Details and fixRemoving
Suggested fix in const cipherKey = await encryptService.unwrapSymmetricKey(this.key, userKeyOrOrgKey);
cipherDecryptionKey = cipherKey;
model.key = cipherKey; |
||
| this.key = c.key; | ||
| } | ||
|
|
||
| private get item(): ItemView | undefined { | ||
|
|
@@ -252,17 +248,7 @@ export class CipherView implements View, InitializerMetadata { | |
| view.passwordHistory = | ||
| obj.passwordHistory?.map((ph: any) => PasswordHistoryView.fromJSON(ph)) ?? []; | ||
|
|
||
| if (obj.key != null) { | ||
| let key: EncString | undefined; | ||
| if (typeof obj.key === "string") { | ||
| // If the key is a string, we need to parse it as EncString | ||
| key = EncString.fromJSON(obj.key); | ||
| } else if ((obj.key as any) instanceof EncString) { | ||
| // If the key is already an EncString instance, we can use it directly | ||
| key = obj.key; | ||
| } | ||
| view.key = key; | ||
| } | ||
| view.key = obj.key != null ? SymmetricCryptoKey.fromJSON(obj.key) : undefined; | ||
|
|
||
| switch (obj.type) { | ||
| case CipherType.Card: | ||
|
|
@@ -299,7 +285,7 @@ export class CipherView implements View, InitializerMetadata { | |
| /** | ||
| * Creates a CipherView from the SDK CipherView. | ||
| */ | ||
| static fromSdkCipherView(obj: SdkCipherView, sdk?: CiphersClient): CipherView | undefined { | ||
| static fromSdkCipherView(obj: SdkCipherView): CipherView | undefined { | ||
| if (obj == null) { | ||
| return undefined; | ||
| } | ||
|
|
@@ -340,7 +326,7 @@ export class CipherView implements View, InitializerMetadata { | |
| cipherView.deletedDate = obj.deletedDate == null ? undefined : new Date(obj.deletedDate); | ||
| cipherView.archivedDate = obj.archivedDate == null ? undefined : new Date(obj.archivedDate); | ||
| cipherView.reprompt = obj.reprompt ?? CipherRepromptType.None; | ||
| cipherView.key = obj.key ? EncString.fromJSON(obj.key) : undefined; | ||
| cipherView.key = obj.key ? SymmetricCryptoKey.fromString(obj.key) : undefined; | ||
|
|
||
| switch (obj.type) { | ||
| case CipherType.Card: | ||
|
|
@@ -353,19 +339,6 @@ export class CipherView implements View, InitializerMetadata { | |
| break; | ||
| case CipherType.Login: | ||
| cipherView.login = obj.login ? LoginView.fromSdkLoginView(obj.login) : new LoginView(); | ||
| if (sdk && obj.login?.fido2Credentials?.length) { | ||
| const fido2CredentialViews = sdk.decrypt_fido2_credentials(obj); | ||
| const decryptedKeyValue = sdk.decrypt_fido2_private_key(obj); | ||
| cipherView.login.fido2Credentials = fido2CredentialViews | ||
| .map((cred) => { | ||
| const view = Fido2CredentialView.fromSdkFido2CredentialView(cred); | ||
| if (view) { | ||
| view.keyValue = decryptedKeyValue; | ||
| } | ||
| return view; | ||
| }) | ||
| .filter((cred): cred is Fido2CredentialView => !!cred); | ||
| } | ||
| break; | ||
| case CipherType.SecureNote: | ||
| cipherView.secureNote = obj.secureNote | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keyhere makesbw edit itemsilently strip the cipher's item key.Details and fix
The documented CLI edit flow round-trips
bw get itemoutput back throughbw edit item.EditCommandpasses the freshly decrypted view as the target ofCipherExport.toView(apps/cli/src/commands/edit.command.ts:143), andtoViewoverwriteskeyunconditionally:Since
req.keyis now always absent, the item key already present oncipherViewis discarded, andtoSdkUpdateCipherRequest/toSdkCipherViewthen sendkey: undefined. Every CLI edit of a cipher that has an item key drops it. PreviouslyCipherResponse.keycarried the wrappedEncString, so the round trip preserved it.The redaction itself is correct β the unconditional overwrite in
toViewis the problem. Consider only replacing the key when the request actually supplies one:BitwardenJsonImporteralready setscipher.key = nullimmediately aftertoView, so import behavior is unaffected by the change.