mirror of
https://github.com/bitwarden/browser
synced 2026-02-10 13:40:06 +00:00
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import { Utils } from "../../../platform/misc/utils";
|
|
import { EncryptionType } from "../../enums";
|
|
import { Encrypted } from "../../interfaces/encrypted";
|
|
|
|
const ENC_TYPE_LENGTH = 1;
|
|
const IV_LENGTH = 16;
|
|
const MAC_LENGTH = 32;
|
|
const MIN_DATA_LENGTH = 1;
|
|
|
|
export class EncArrayBuffer implements Encrypted {
|
|
readonly encryptionType: EncryptionType;
|
|
readonly dataBytes: Uint8Array;
|
|
readonly ivBytes: Uint8Array;
|
|
readonly macBytes: Uint8Array | null = null;
|
|
private static readonly DecryptionError = new Error(
|
|
"Error parsing encrypted ArrayBuffer: data is corrupted or has an invalid format.",
|
|
);
|
|
|
|
constructor(readonly buffer: Uint8Array) {
|
|
if (buffer == null) {
|
|
throw new Error("EncArrayBuffer initialized with null buffer.");
|
|
}
|
|
|
|
this.encryptionType = this.buffer[0];
|
|
switch (this.encryptionType) {
|
|
case EncryptionType.AesCbc256_HmacSha256_B64: {
|
|
const minimumLength = ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH + MIN_DATA_LENGTH;
|
|
if (this.buffer.length < minimumLength) {
|
|
throw EncArrayBuffer.DecryptionError;
|
|
}
|
|
|
|
this.ivBytes = this.buffer.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
|
|
this.macBytes = this.buffer.slice(
|
|
ENC_TYPE_LENGTH + IV_LENGTH,
|
|
ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH,
|
|
);
|
|
this.dataBytes = this.buffer.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH);
|
|
break;
|
|
}
|
|
case EncryptionType.AesCbc256_B64: {
|
|
const minimumLength = ENC_TYPE_LENGTH + IV_LENGTH + MIN_DATA_LENGTH;
|
|
if (this.buffer.length < minimumLength) {
|
|
throw EncArrayBuffer.DecryptionError;
|
|
}
|
|
|
|
this.ivBytes = this.buffer.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
|
|
this.dataBytes = this.buffer.slice(ENC_TYPE_LENGTH + IV_LENGTH);
|
|
break;
|
|
}
|
|
default:
|
|
throw EncArrayBuffer.DecryptionError;
|
|
}
|
|
}
|
|
|
|
static fromParts(
|
|
encryptionType: EncryptionType,
|
|
iv: Uint8Array,
|
|
data: Uint8Array,
|
|
mac: Uint8Array | undefined | null,
|
|
) {
|
|
if (encryptionType == null || iv == null || data == null) {
|
|
throw new Error("encryptionType, iv, and data must be provided");
|
|
}
|
|
|
|
switch (encryptionType) {
|
|
case EncryptionType.AesCbc256_B64:
|
|
case EncryptionType.AesCbc128_HmacSha256_B64:
|
|
case EncryptionType.AesCbc256_HmacSha256_B64:
|
|
EncArrayBuffer.validateIvLength(iv);
|
|
EncArrayBuffer.validateMacLength(encryptionType, mac);
|
|
break;
|
|
default:
|
|
throw new Error(`Unknown EncryptionType ${encryptionType} for EncArrayBuffer.fromParts`);
|
|
}
|
|
|
|
let macLen = 0;
|
|
if (mac != null) {
|
|
macLen = mac.length;
|
|
}
|
|
|
|
const bytes = new Uint8Array(1 + iv.byteLength + macLen + data.byteLength);
|
|
bytes.set([encryptionType], 0);
|
|
bytes.set(iv, 1);
|
|
if (mac != null) {
|
|
bytes.set(mac, 1 + iv.byteLength);
|
|
}
|
|
bytes.set(data, 1 + iv.byteLength + macLen);
|
|
return new EncArrayBuffer(bytes);
|
|
}
|
|
|
|
static async fromResponse(response: {
|
|
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
}): Promise<EncArrayBuffer> {
|
|
const buffer = await response.arrayBuffer();
|
|
if (buffer == null) {
|
|
throw new Error("Cannot create EncArrayBuffer from Response - Response is empty");
|
|
}
|
|
return new EncArrayBuffer(new Uint8Array(buffer));
|
|
}
|
|
|
|
static fromB64(b64: string) {
|
|
const buffer = Utils.fromB64ToArray(b64);
|
|
return new EncArrayBuffer(buffer);
|
|
}
|
|
|
|
static validateIvLength(iv: Uint8Array) {
|
|
if (iv == null || iv.length !== IV_LENGTH) {
|
|
throw new Error("Invalid IV length");
|
|
}
|
|
}
|
|
|
|
static validateMacLength(encType: EncryptionType, mac: Uint8Array | null | undefined) {
|
|
switch (encType) {
|
|
case EncryptionType.AesCbc256_B64:
|
|
if (mac != null) {
|
|
throw new Error("mac must not be provided for AesCbc256_B64");
|
|
}
|
|
break;
|
|
case EncryptionType.AesCbc128_HmacSha256_B64:
|
|
case EncryptionType.AesCbc256_HmacSha256_B64:
|
|
if (mac == null || mac.length !== MAC_LENGTH) {
|
|
throw new Error("Invalid MAC length");
|
|
}
|
|
break;
|
|
default:
|
|
throw new Error("Invalid encryption type and mac combination");
|
|
}
|
|
}
|
|
}
|