padloc/packages/core/src/legacy.ts

80 lines
2.6 KiB
TypeScript

import { sjcl } from "../vendor/sjcl";
import { Err, ErrorCode } from "./error";
import { PBKDF2_ITER_MAX } from "./crypto";
import { PBES2Container } from "./container";
/**
* Interface representing legacy container structure (i.e. encryption format
* used in Padlock versions 1.x and 2.x)
*/
export interface LegacyContainer {
version?: 1;
cipher: "aes";
mode: "ccm";
iv: string;
adata: string;
ts: 64 | 96 | 128;
keySize: 256;
salt: string;
iter: number;
ct: string;
}
/**
* Validates legacy container structure
*/
export function validateLegacyContainer(obj: any): boolean {
return (
typeof obj === "object" &&
(obj.version === undefined || obj.version === 1) && // has a valid version
[128, 192, 256].includes(obj.keySize) &&
typeof obj.iter == "number" && // valid PBKDF2 iteration count
obj.iter <= PBKDF2_ITER_MAX && // sane pbkdf2 iteration count
typeof obj.salt == "string" &&
["aes"].includes(obj.cipher) && // valid cipher
["ccm", "ocb2"].includes(obj.mode) && // exiting mode
typeof obj.iv == "string" && // valid initialisation vector
typeof obj.ct == "string" && // valid cipher text
typeof obj.adata == "string" && // valid authorisation data
[64, 96, 128].includes(obj.ts) // valid authorisation tag length
);
}
/**
* Transforms a legacy container object into an instance of [[PBES2Container]]
*/
export function parseLegacyContainer(raw: LegacyContainer): PBES2Container {
if (!validateLegacyContainer(raw)) {
throw new Err(ErrorCode.ENCODING_ERROR);
}
if (raw.version === undefined) {
// Legacy versions of Padlock had a bug where the base64-encoded
// `adata` value was not converted to a BitArray before being
// passed to `sjcl.mode.ccm.encrypt/decrypt` and the raw string was
// passed instead. This went unnoticed as the functions in question
// quietly accepted the string and simply treated it as an array.
// So in order to successfully decrypt legacy containers we have to
// perfom this conversion first.
raw.adata = sjcl.codec.base64.fromBits(raw.adata);
}
return new PBES2Container().fromRaw({
encryptionParams: {
algorithm: "AES-CCM",
tagSize: raw.ts,
keySize: raw.keySize,
iv: raw.iv,
additionalData: raw.adata,
},
keyParams: {
algorithm: "PBKDF2",
hash: "SHA-256",
keySize: raw.keySize,
iterations: raw.iter,
salt: raw.salt,
},
encryptedData: raw.ct,
});
}