You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
67 lines
2.4 KiB
TypeScript
67 lines
2.4 KiB
TypeScript
import { getSodiumRenderer } from '../../session/crypto';
|
|
|
|
const PROFILE_IV_LENGTH = 12; // bytes
|
|
const PROFILE_KEY_LENGTH = 32; // bytes
|
|
const PROFILE_TAG_LENGTH = 128; // bits
|
|
|
|
export async function decryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
|
if (data.byteLength < 12 + 16 + 1) {
|
|
throw new Error(`Got too short input: ${data.byteLength}`);
|
|
}
|
|
const iv = data.slice(0, PROFILE_IV_LENGTH);
|
|
const ciphertext = data.slice(PROFILE_IV_LENGTH, data.byteLength);
|
|
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
|
throw new Error('Got invalid length profile key');
|
|
}
|
|
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
|
throw new Error('Got invalid length profile iv');
|
|
}
|
|
const error = new Error(); // save stack
|
|
return crypto.subtle
|
|
.importKey('raw', key, { name: 'AES-GCM' }, false, ['decrypt'])
|
|
.then(keyForEncryption =>
|
|
crypto.subtle
|
|
.decrypt(
|
|
{ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH },
|
|
keyForEncryption,
|
|
ciphertext
|
|
)
|
|
.catch(e => {
|
|
if (e.name === 'OperationError') {
|
|
// bad mac, basically.
|
|
error.message =
|
|
'Failed to decrypt profile data. Most likely the profile key has changed.';
|
|
error.name = 'ProfileDecryptError';
|
|
throw error;
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
async function getRandomBytesFromLength(n: number) {
|
|
return (await getSodiumRenderer()).randombytes_buf(n);
|
|
}
|
|
|
|
export async function encryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
|
const iv = await getRandomBytesFromLength(PROFILE_IV_LENGTH);
|
|
if (key.byteLength !== PROFILE_KEY_LENGTH) {
|
|
throw new Error('Got invalid length profile key');
|
|
}
|
|
if (iv.byteLength !== PROFILE_IV_LENGTH) {
|
|
throw new Error('Got invalid length profile iv');
|
|
}
|
|
return crypto.subtle
|
|
.importKey('raw', key, { name: 'AES-GCM' }, false, ['encrypt'])
|
|
.then(keyForEncryption =>
|
|
crypto.subtle
|
|
.encrypt({ name: 'AES-GCM', iv, tagLength: PROFILE_TAG_LENGTH }, keyForEncryption, data)
|
|
.then(ciphertext => {
|
|
// tslint:disable-next-line: restrict-plus-operands
|
|
const ivAndCiphertext = new Uint8Array(PROFILE_IV_LENGTH + ciphertext.byteLength);
|
|
ivAndCiphertext.set(new Uint8Array(iv));
|
|
ivAndCiphertext.set(new Uint8Array(ciphertext), PROFILE_IV_LENGTH);
|
|
return ivAndCiphertext.buffer;
|
|
})
|
|
);
|
|
}
|