Changeset View
Changeset View
Standalone View
Standalone View
web/media/encryption-utils.js
Show All 15 Lines | |||||
const PADDING_THRESHOLD = 5000000; // 5MB | const PADDING_THRESHOLD = 5000000; // 5MB | ||||
type EncryptFileResult = { | type EncryptFileResult = { | ||||
+success: true, | +success: true, | ||||
+file: File, | +file: File, | ||||
+uri: string, | +uri: string, | ||||
+encryptionKey: string, | +encryptionKey: string, | ||||
+sha256Hash: string, | |||||
}; | }; | ||||
async function encryptFile(input: File): Promise<{ | async function encryptFile(input: File): Promise<{ | ||||
steps: $ReadOnlyArray<MediaMissionStep>, | steps: $ReadOnlyArray<MediaMissionStep>, | ||||
result: EncryptFileResult | MediaMissionFailure, | result: EncryptFileResult | MediaMissionFailure, | ||||
}> { | }> { | ||||
const steps = []; | const steps = []; | ||||
let success = true, | let success = true, | ||||
Show All 18 Lines | async function encryptFile(input: File): Promise<{ | ||||
if (!success || !data) { | if (!success || !data) { | ||||
return { steps, result: { success: false, reason: 'array_buffer_failed' } }; | return { steps, result: { success: false, reason: 'array_buffer_failed' } }; | ||||
} | } | ||||
// Step 2: Encrypt the data | // Step 2: Encrypt the data | ||||
const startEncrypt = Date.now(); | const startEncrypt = Date.now(); | ||||
const paddedLength = calculatePaddedLength(data.length); | const paddedLength = calculatePaddedLength(data.length); | ||||
const shouldPad = paddedLength <= PADDING_THRESHOLD; | const shouldPad = paddedLength <= PADDING_THRESHOLD; | ||||
let key, encryptedData; | let key, encryptedData, sha256; | ||||
try { | try { | ||||
const plaintextData = shouldPad ? pad(data) : data; | const plaintextData = shouldPad ? pad(data) : data; | ||||
key = await AES.generateKey(); | key = await AES.generateKey(); | ||||
encryptedData = await AES.encrypt(key, plaintextData); | encryptedData = await AES.encrypt(key, plaintextData); | ||||
const hashBytes = await crypto.subtle.digest('SHA-256', encryptedData); | |||||
sha256 = btoa(String.fromCharCode(...new Uint8Array(hashBytes))); | |||||
} catch (e) { | } catch (e) { | ||||
success = false; | success = false; | ||||
exceptionMessage = getMessageForException(e); | exceptionMessage = getMessageForException(e); | ||||
} | } | ||||
steps.push({ | steps.push({ | ||||
step: 'encrypt_data', | step: 'encrypt_data', | ||||
dataSize: encryptedData?.byteLength ?? -1, | dataSize: encryptedData?.byteLength ?? -1, | ||||
isPadded: shouldPad, | isPadded: shouldPad, | ||||
time: Date.now() - startEncrypt, | time: Date.now() - startEncrypt, | ||||
sha256: null, | sha256, | ||||
success, | success, | ||||
exceptionMessage, | exceptionMessage, | ||||
}); | }); | ||||
if (!success || !encryptedData || !key) { | if (encryptedData && !sha256) { | ||||
return { steps, result: { success: false, reason: 'digest_failed' } }; | |||||
} | |||||
if (!success || !encryptedData || !key || !sha256) { | |||||
return { steps, result: { success: false, reason: 'encryption_failed' } }; | return { steps, result: { success: false, reason: 'encryption_failed' } }; | ||||
} | } | ||||
// Step 3: Create a File from the encrypted data | // Step 3: Create a File from the encrypted data | ||||
const output = new File([encryptedData], input.name, { type: input.type }); | const output = new File([encryptedData], input.name, { type: input.type }); | ||||
return { | return { | ||||
steps, | steps, | ||||
result: { | result: { | ||||
success: true, | success: true, | ||||
file: output, | file: output, | ||||
uri: URL.createObjectURL(output), | uri: URL.createObjectURL(output), | ||||
encryptionKey: uintArrayToHexString(key), | encryptionKey: uintArrayToHexString(key), | ||||
sha256Hash: sha256, | |||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
type DecryptFileStep = | type DecryptFileStep = | ||||
| { | | { | ||||
+step: 'fetch_buffer', | +step: 'fetch_buffer', | ||||
+url: string, | +url: string, | ||||
▲ Show 20 Lines • Show All 135 Lines • Show Last 20 Lines |