* fix: use random IV for link-sharing encryption (#2829) * fix: add backward compatibility for link-sharing encryption (#2829)
This commit is contained in:
parent
127c1be6ad
commit
c8743a8c02
@ -80,8 +80,10 @@ export type SocketUpdateData = SocketUpdateDataSource[keyof SocketUpdateDataSour
|
|||||||
_brand: "socketUpdateData";
|
_brand: "socketUpdateData";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const IV_LENGTH_BYTES = 12; // 96 bits
|
||||||
|
|
||||||
export const createIV = () => {
|
export const createIV = () => {
|
||||||
const arr = new Uint8Array(12);
|
const arr = new Uint8Array(IV_LENGTH_BYTES);
|
||||||
return window.crypto.getRandomValues(arr);
|
return window.crypto.getRandomValues(arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -175,6 +177,22 @@ export const getImportedKey = (key: string, usage: KeyUsage) =>
|
|||||||
[usage],
|
[usage],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const decryptImported = async (
|
||||||
|
iv: ArrayBuffer,
|
||||||
|
encrypted: ArrayBuffer,
|
||||||
|
privateKey: string,
|
||||||
|
): Promise<ArrayBuffer> => {
|
||||||
|
const key = await getImportedKey(privateKey, "decrypt");
|
||||||
|
return window.crypto.subtle.decrypt(
|
||||||
|
{
|
||||||
|
name: "AES-GCM",
|
||||||
|
iv,
|
||||||
|
},
|
||||||
|
key,
|
||||||
|
encrypted,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const importFromBackend = async (
|
const importFromBackend = async (
|
||||||
id: string | null,
|
id: string | null,
|
||||||
privateKey?: string | null,
|
privateKey?: string | null,
|
||||||
@ -183,6 +201,7 @@ const importFromBackend = async (
|
|||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
privateKey ? `${BACKEND_V2_GET}${id}` : `${BACKEND_GET}${id}.json`,
|
privateKey ? `${BACKEND_V2_GET}${id}` : `${BACKEND_GET}${id}.json`,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
window.alert(t("alerts.importBackendFailed"));
|
window.alert(t("alerts.importBackendFailed"));
|
||||||
return {};
|
return {};
|
||||||
@ -190,16 +209,19 @@ const importFromBackend = async (
|
|||||||
let data: ImportedDataState;
|
let data: ImportedDataState;
|
||||||
if (privateKey) {
|
if (privateKey) {
|
||||||
const buffer = await response.arrayBuffer();
|
const buffer = await response.arrayBuffer();
|
||||||
const key = await getImportedKey(privateKey, "decrypt");
|
|
||||||
const iv = new Uint8Array(12);
|
let decrypted: ArrayBuffer;
|
||||||
const decrypted = await window.crypto.subtle.decrypt(
|
try {
|
||||||
{
|
// Buffer should contain both the IV (fixed length) and encrypted data
|
||||||
name: "AES-GCM",
|
const iv = buffer.slice(0, IV_LENGTH_BYTES);
|
||||||
iv,
|
const encrypted = buffer.slice(IV_LENGTH_BYTES, buffer.byteLength);
|
||||||
},
|
decrypted = await decryptImported(iv, encrypted, privateKey);
|
||||||
key,
|
} catch (error) {
|
||||||
buffer,
|
// Fixed IV (old format, backward compatibility)
|
||||||
);
|
const fixedIv = new Uint8Array(IV_LENGTH_BYTES);
|
||||||
|
decrypted = await decryptImported(fixedIv, buffer, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
// We need to convert the decrypted array buffer to a string
|
// We need to convert the decrypted array buffer to a string
|
||||||
const string = new window.TextDecoder("utf-8").decode(
|
const string = new window.TextDecoder("utf-8").decode(
|
||||||
new Uint8Array(decrypted) as any,
|
new Uint8Array(decrypted) as any,
|
||||||
@ -263,9 +285,8 @@ export const exportToBackend = async (
|
|||||||
true, // extractable
|
true, // extractable
|
||||||
["encrypt", "decrypt"],
|
["encrypt", "decrypt"],
|
||||||
);
|
);
|
||||||
// The iv is set to 0. We are never going to reuse the same key so we don't
|
|
||||||
// need to have an iv. (I hope that's correct...)
|
const iv = createIV();
|
||||||
const iv = new Uint8Array(12);
|
|
||||||
// We use symmetric encryption. AES-GCM is the recommended algorithm and
|
// We use symmetric encryption. AES-GCM is the recommended algorithm and
|
||||||
// includes checks that the ciphertext has not been modified by an attacker.
|
// includes checks that the ciphertext has not been modified by an attacker.
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
@ -276,6 +297,11 @@ export const exportToBackend = async (
|
|||||||
key,
|
key,
|
||||||
encoded,
|
encoded,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Concatenate IV with encrypted data (IV does not have to be secret).
|
||||||
|
const payloadBlob = new Blob([iv.buffer, encrypted]);
|
||||||
|
const payload = await new Response(payloadBlob).arrayBuffer();
|
||||||
|
|
||||||
// We use jwk encoding to be able to extract just the base64 encoded key.
|
// We use jwk encoding to be able to extract just the base64 encoded key.
|
||||||
// We will hardcode the rest of the attributes when importing back the key.
|
// We will hardcode the rest of the attributes when importing back the key.
|
||||||
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
const exportedKey = await window.crypto.subtle.exportKey("jwk", key);
|
||||||
@ -283,7 +309,7 @@ export const exportToBackend = async (
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(BACKEND_V2_POST, {
|
const response = await fetch(BACKEND_V2_POST, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: encrypted,
|
body: payload,
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.id) {
|
if (json.id) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user