-
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 4 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,7 @@ 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; | ||
| view.key = req.key != null ? SymmetricCryptoKey.fromString(req.key) : undefined; | ||
|
|
||
| if (req.fields != null) { | ||
| view.fields = req.fields.map((f) => FieldExport.toView(f)); | ||
|
|
@@ -208,7 +209,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 |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| import { Jsonify } from "type-fest"; | ||
|
|
||
| import { AttachmentView as SdkAttachmentView } from "@bitwarden/sdk-internal"; | ||
| import { AttachmentView as SdkAttachmentView, SymmetricKey } from "@bitwarden/sdk-internal"; | ||
|
|
||
| import { DECRYPT_ERROR, EncString } from "../../../key-management/crypto/models/enc-string"; | ||
| import { DECRYPT_ERROR } from "../../../key-management/crypto/models/enc-string"; | ||
| import { View } from "../../../models/view/view"; | ||
| import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; | ||
| import { Attachment } from "../domain/attachment"; | ||
|
|
@@ -14,10 +14,6 @@ export class AttachmentView implements View { | |
| sizeName?: string; | ||
| fileName?: string; | ||
| key?: SymmetricCryptoKey; | ||
| /** | ||
| * The SDK returns an encrypted key for the attachment. | ||
| */ | ||
| encryptedKey: EncString | undefined; | ||
| private _hasDecryptionError?: boolean; | ||
|
|
||
| constructor(a?: Attachment) { | ||
|
|
@@ -52,18 +48,7 @@ export class AttachmentView implements View { | |
|
|
||
| static fromJSON(obj: Partial<Jsonify<AttachmentView>>): AttachmentView { | ||
| const key = obj.key == null ? null : SymmetricCryptoKey.fromJSON(obj.key); | ||
|
|
||
| let encryptedKey: EncString | undefined; | ||
| if (obj.encryptedKey != null) { | ||
| if (typeof obj.encryptedKey === "string") { | ||
| // If the key is a string, we need to parse it as EncString | ||
| encryptedKey = EncString.fromJSON(obj.encryptedKey); | ||
| } else if ((obj.encryptedKey as any) instanceof EncString) { | ||
| // If the key is already an EncString instance, we can use it directly | ||
| encryptedKey = obj.encryptedKey; | ||
| } | ||
| } | ||
| return Object.assign(new AttachmentView(), obj, { key: key, encryptedKey: encryptedKey }); | ||
| return Object.assign(new AttachmentView(), obj, { key: key }); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -76,9 +61,7 @@ export class AttachmentView implements View { | |
| size: this.size, | ||
| sizeName: this.sizeName, | ||
| fileName: this.fileName, | ||
| key: this.encryptedKey?.toSdk(), | ||
| // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete | ||
| decryptedKey: this.key ? this.key.toBase64() : undefined, | ||
| key: (this.key?.toBase64() ?? undefined) as SymmetricKey | undefined, | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -99,9 +82,7 @@ export class AttachmentView implements View { | |
| view.size = obj.size; | ||
| view.sizeName = obj.sizeName; | ||
| view.fileName = obj.fileName; | ||
| // TODO: PM-23005 - Temporary field, should be removed when encrypted migration is complete | ||
| view.key = obj.decryptedKey ? SymmetricCryptoKey.fromString(obj.decryptedKey) : undefined; | ||
| view.encryptedKey = obj.key ? new EncString(obj.key) : undefined; | ||
| view.key = obj.key ? SymmetricCryptoKey.fromString(obj.key) : undefined; | ||
| view._hasDecryptionError = failure; | ||
|
|
||
| return view; | ||
|
|
@@ -112,6 +93,6 @@ export class AttachmentView implements View { | |
| * In this case, the attachment is encrypted with the user's user-key | ||
| */ | ||
| isLegacyAttachment(): boolean { | ||
| return this.key == null && this.encryptedKey == null; | ||
| return this.key == null; | ||
|
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
Downstream effects for a user with one undecryptable attachment key:
Suggested fix: isLegacyAttachment(): boolean {
return this.key == null && !this.hasDecryptionError;
} |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,13 @@ import { | |
| CiphersClient, | ||
| CipherViewType, | ||
| CipherView as SdkCipherView, | ||
| SymmetricKey, | ||
| } 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"; | ||
|
|
@@ -65,9 +66,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 +96,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 +250,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 +287,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 +328,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,17 +341,9 @@ 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; | ||
| }) | ||
| if (obj.login?.fido2Credentials?.length) { | ||
| cipherView.login.fido2Credentials = obj.login.fido2Credentials | ||
| .map((cred) => Fido2CredentialView.fromSdkFido2CredentialView(cred)) | ||
| .filter((cred): cred is Fido2CredentialView => !!cred); | ||
| } | ||
| break; | ||
|
|
@@ -447,7 +427,7 @@ export class CipherView implements View, InitializerMetadata { | |
| revisionDate: this.revisionDate?.toISOString(), | ||
| archivedDate: this.archivedDate?.toISOString(), | ||
| attachments: this.attachments?.map((a) => a.toSdkAttachmentView()), | ||
| key: this.key?.toSdk(), | ||
| key: (this.key?.toBase64() ?? undefined) as SymmetricKey | undefined, | ||
|
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. π¨ SUGGESTED: Detailskey: this.key?.toSdk(),
|
||
| }; | ||
|
|
||
| // If the cipher has FIDO2 credentials, we need to set them on the SDK edit request | ||
|
|
@@ -552,7 +532,7 @@ export class CipherView implements View, InitializerMetadata { | |
| deletedDate: this.deletedDate?.toISOString(), | ||
| archivedDate: this.archivedDate?.toISOString(), | ||
| reprompt: this.reprompt ?? CipherRepromptType.None, | ||
| key: this.key?.toSdk(), | ||
| key: (this.key?.toBase64() ?? undefined) as any, | ||
| // Cipher type specific properties are set in the switch statement below | ||
| // CipherView initializes each with default constructors (undefined values) | ||
| // The SDK does not expect those undefined values and will throw exceptions | ||
|
|
||
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.
EncStringversion of the key will now fail to import. (TheEncStringwas removed in f167f06, but existed for exports before then).We'll want to add some type of try/catch or format checking to avoid breaking the entire import just because an old key value is present.