mirror of
https://github.com/bitwarden/browser
synced 2026-02-14 15:33:55 +00:00
add emailHashes and migrate emails to EncString
This commit is contained in:
@@ -23,6 +23,7 @@ export class SendData {
|
||||
deletionDate: string;
|
||||
password: string;
|
||||
emails: string;
|
||||
emailHashes: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
authType: AuthType;
|
||||
@@ -46,6 +47,7 @@ export class SendData {
|
||||
this.deletionDate = response.deletionDate;
|
||||
this.password = response.password;
|
||||
this.emails = response.emails;
|
||||
this.emailHashes = "";
|
||||
this.disabled = response.disable;
|
||||
this.hideEmail = response.hideEmail;
|
||||
this.authType = response.authType;
|
||||
|
||||
@@ -31,7 +31,8 @@ export class Send extends Domain {
|
||||
expirationDate: Date;
|
||||
deletionDate: Date;
|
||||
password: string;
|
||||
emails: string;
|
||||
emails: EncString;
|
||||
emailHashes: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
authType: AuthType;
|
||||
@@ -51,6 +52,7 @@ export class Send extends Domain {
|
||||
name: null,
|
||||
notes: null,
|
||||
key: null,
|
||||
emails: null,
|
||||
},
|
||||
["id", "accessId"],
|
||||
);
|
||||
@@ -60,7 +62,7 @@ export class Send extends Domain {
|
||||
this.maxAccessCount = obj.maxAccessCount;
|
||||
this.accessCount = obj.accessCount;
|
||||
this.password = obj.password;
|
||||
this.emails = obj.emails;
|
||||
this.emailHashes = obj.emailHashes;
|
||||
this.disabled = obj.disabled;
|
||||
this.revisionDate = obj.revisionDate != null ? new Date(obj.revisionDate) : null;
|
||||
this.deletionDate = obj.deletionDate != null ? new Date(obj.deletionDate) : null;
|
||||
@@ -92,8 +94,17 @@ export class Send extends Domain {
|
||||
// model.key is a seed used to derive a key, not a SymmetricCryptoKey
|
||||
model.key = await encryptService.decryptBytes(this.key, sendKeyEncryptionKey);
|
||||
model.cryptoKey = await keyService.makeSendKey(model.key);
|
||||
model.name =
|
||||
this.name != null ? await encryptService.decryptString(this.name, model.cryptoKey) : null;
|
||||
model.notes =
|
||||
this.notes != null ? await encryptService.decryptString(this.notes, model.cryptoKey) : null;
|
||||
|
||||
await this.decryptObj<Send, SendView>(this, model, ["name", "notes"], model.cryptoKey);
|
||||
if (this.emails != null) {
|
||||
const decryptedEmails = await encryptService.decryptString(this.emails, model.cryptoKey);
|
||||
model.emails = decryptedEmails ? decryptedEmails.split(",").map((e) => e.trim()) : [];
|
||||
} else {
|
||||
model.emails = [];
|
||||
}
|
||||
|
||||
switch (this.type) {
|
||||
case SendType.File:
|
||||
@@ -122,6 +133,7 @@ export class Send extends Domain {
|
||||
key: EncString.fromJSON(obj.key),
|
||||
name: EncString.fromJSON(obj.name),
|
||||
notes: EncString.fromJSON(obj.notes),
|
||||
emails: EncString.fromJSON(obj.emails),
|
||||
text: SendText.fromJSON(obj.text),
|
||||
file: SendFile.fromJSON(obj.file),
|
||||
revisionDate,
|
||||
|
||||
@@ -18,6 +18,7 @@ export class SendRequest {
|
||||
file: SendFileApi;
|
||||
password: string;
|
||||
emails: string;
|
||||
emailHashes: string;
|
||||
disabled: boolean;
|
||||
hideEmail: boolean;
|
||||
|
||||
@@ -31,7 +32,8 @@ export class SendRequest {
|
||||
this.deletionDate = send.deletionDate != null ? send.deletionDate.toISOString() : null;
|
||||
this.key = send.key != null ? send.key.encryptedString : null;
|
||||
this.password = send.password;
|
||||
this.emails = send.emails;
|
||||
this.emails = send.emails ? send.emails.encryptedString : null;
|
||||
this.emailHashes = send.emailHashes;
|
||||
this.disabled = send.disabled;
|
||||
this.hideEmail = send.hideEmail;
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ export class SendView implements View {
|
||||
this.password = s.password;
|
||||
this.hideEmail = s.hideEmail;
|
||||
this.authType = s.authType;
|
||||
this.emails = s.emails ? s.emails.split(",").map((e) => e.trim()) : [];
|
||||
}
|
||||
|
||||
get urlB64Key(): string {
|
||||
|
||||
@@ -148,6 +148,7 @@ export class SendApiService implements SendApiServiceAbstraction {
|
||||
|
||||
private async upload(sendData: [Send, EncArrayBuffer]): Promise<SendResponse> {
|
||||
const request = new SendRequest(sendData[0], sendData[1]?.buffer.byteLength);
|
||||
|
||||
let response: SendResponse;
|
||||
if (sendData[0].id == null) {
|
||||
if (sendData[0].type === SendType.Text) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
|
||||
import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { KeyGenerationService } from "../../../key-management/crypto";
|
||||
import { CryptoFunctionService } from "../../../key-management/crypto/abstractions/crypto-function.service";
|
||||
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
|
||||
import { EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { I18nService } from "../../../platform/abstractions/i18n.service";
|
||||
@@ -51,6 +52,7 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
private keyGenerationService: KeyGenerationService,
|
||||
private stateProvider: SendStateProvider,
|
||||
private encryptService: EncryptService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
) {}
|
||||
|
||||
async encrypt(
|
||||
@@ -82,17 +84,23 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
|
||||
const hasEmails = (model.emails?.length ?? 0) > 0;
|
||||
if (hasEmails) {
|
||||
send.emails = model.emails.join(",");
|
||||
const plaintextEmails = model.emails.join(",");
|
||||
send.emails = await this.encryptService.encryptString(plaintextEmails, model.cryptoKey);
|
||||
send.emailHashes = await this.hashEmails(plaintextEmails);
|
||||
send.password = null;
|
||||
} else if (password != null) {
|
||||
// Note: Despite being called key, the passwordKey is not used for encryption.
|
||||
// It is used as a static proof that the client knows the password, and has the encryption key.
|
||||
const passwordKey = await this.keyGenerationService.deriveKeyFromPassword(
|
||||
password,
|
||||
model.key,
|
||||
new PBKDF2KdfConfig(SEND_KDF_ITERATIONS),
|
||||
);
|
||||
send.password = passwordKey.keyB64;
|
||||
} else {
|
||||
send.emails = null;
|
||||
send.emailHashes = "";
|
||||
if (password != null) {
|
||||
// Note: Despite being called key, the passwordKey is not used for encryption.
|
||||
// It is used as a static proof that the client knows the password, and has the encryption key.
|
||||
const passwordKey = await this.keyGenerationService.deriveKeyFromPassword(
|
||||
password,
|
||||
model.key,
|
||||
new PBKDF2KdfConfig(SEND_KDF_ITERATIONS),
|
||||
);
|
||||
send.password = passwordKey.keyB64;
|
||||
}
|
||||
}
|
||||
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
|
||||
if (userKey == null) {
|
||||
@@ -100,10 +108,14 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
}
|
||||
// Key is not a SymmetricCryptoKey, but key material used to derive the cryptoKey
|
||||
send.key = await this.encryptService.encryptBytes(model.key, userKey);
|
||||
// FIXME: model.name can be null. encryptString should not be called with null values.
|
||||
send.name = await this.encryptService.encryptString(model.name, model.cryptoKey);
|
||||
// FIXME: model.notes can be null. encryptString should not be called with null values.
|
||||
send.notes = await this.encryptService.encryptString(model.notes, model.cryptoKey);
|
||||
send.name =
|
||||
model.name != null
|
||||
? await this.encryptService.encryptString(model.name, model.cryptoKey)
|
||||
: null;
|
||||
send.notes =
|
||||
model.notes != null
|
||||
? await this.encryptService.encryptString(model.notes, model.cryptoKey)
|
||||
: null;
|
||||
if (send.type === SendType.Text) {
|
||||
send.text = new SendText();
|
||||
// FIXME: model.text.text can be null. encryptString should not be called with null values.
|
||||
@@ -373,4 +385,19 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
decryptedSends.sort(Utils.getSortFunction(this.i18nService, "name"));
|
||||
return decryptedSends;
|
||||
}
|
||||
|
||||
private async hashEmails(emails: string): Promise<string> {
|
||||
if (!emails) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const emailArray = emails.split(",").map((e) => e.trim().toLowerCase());
|
||||
const hashPromises = emailArray.map(async (email) => {
|
||||
const hash: Uint8Array = await this.cryptoFunctionService.hash(email, "sha256");
|
||||
return Utils.fromBufferToHex(hash).toUpperCase();
|
||||
});
|
||||
|
||||
const hashes = await Promise.all(hashPromises);
|
||||
return hashes.join(",");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user