Changeset View
Changeset View
Standalone View
Standalone View
native/media/encryption-utils.js
Show All 22 Lines | |||||
import { commUtilsModule } from '../native-modules.js'; | import { commUtilsModule } from '../native-modules.js'; | ||||
import * as AES from '../utils/aes-crypto-module.js'; | import * as AES from '../utils/aes-crypto-module.js'; | ||||
const PADDING_THRESHOLD = 5000000; // we don't pad files larger than this | const PADDING_THRESHOLD = 5000000; // we don't pad files larger than this | ||||
type EncryptedFileResult = { | type EncryptedFileResult = { | ||||
+success: true, | +success: true, | ||||
+uri: string, | +uri: string, | ||||
+sha256Hash: string, | |||||
+encryptionKey: string, | +encryptionKey: string, | ||||
}; | }; | ||||
/** | /** | ||||
* Encrypts a single file and returns the encrypted file URI | * Encrypts a single file and returns the encrypted file URI | ||||
* and the encryption key. The encryption key is returned as a hex string. | * and the encryption key. The encryption key is returned as a hex string. | ||||
* The encrypted file is written to the same directory as the original file, | * The encrypted file is written to the same directory as the original file, | ||||
* with the same name, but with the extension ".dat". | * with the same name, but with the extension ".dat". | ||||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Lines | return { | ||||
result: { success: false, reason: 'fetch_failed' }, | result: { success: false, reason: 'fetch_failed' }, | ||||
}; | }; | ||||
} | } | ||||
// Step 2. Encrypt the file | // Step 2. Encrypt the file | ||||
const startEncrypt = Date.now(); | const startEncrypt = Date.now(); | ||||
const paddedLength = calculatePaddedLength(data.byteLength); | const paddedLength = calculatePaddedLength(data.byteLength); | ||||
const shouldPad = paddedLength <= PADDING_THRESHOLD; | const shouldPad = paddedLength <= PADDING_THRESHOLD; | ||||
let key, encryptedData; | let key, encryptedData, sha256Hash; | ||||
try { | try { | ||||
const plaintextData = shouldPad ? pad(data) : data; | const plaintextData = shouldPad ? pad(data) : data; | ||||
key = AES.generateKey(); | key = AES.generateKey(); | ||||
encryptedData = AES.encrypt(key, plaintextData); | encryptedData = AES.encrypt(key, plaintextData); | ||||
sha256Hash = commUtilsModule.sha256(encryptedData.buffer); | |||||
} 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: sha256Hash, | |||||
success, | success, | ||||
exceptionMessage, | exceptionMessage, | ||||
}); | }); | ||||
if (!success || !encryptedData || !key) { | if (encryptedData && !sha256Hash) { | ||||
return { steps, result: { success: false, reason: 'digest_failed' } }; | |||||
} | |||||
if (!success || !encryptedData || !key || !sha256Hash) { | |||||
return { | return { | ||||
steps, | steps, | ||||
result: { success: false, reason: 'encryption_failed' }, | result: { success: false, reason: 'encryption_failed' }, | ||||
}; | }; | ||||
} | } | ||||
// Step 3. Write the encrypted file | // Step 3. Write the encrypted file | ||||
const startWriteFile = Date.now(); | const startWriteFile = Date.now(); | ||||
Show All 18 Lines | async function encryptFile(uri: string): Promise<{ | ||||
} | } | ||||
return { | return { | ||||
steps, | steps, | ||||
result: { | result: { | ||||
success: true, | success: true, | ||||
uri: destinationURI, | uri: destinationURI, | ||||
encryptionKey: uintArrayToHexString(key), | encryptionKey: uintArrayToHexString(key), | ||||
sha256Hash, | |||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
/** | /** | ||||
* Encrypts a single photo or video. Replaces the uploadURI with the encrypted | * Encrypts a single photo or video. Replaces the uploadURI with the encrypted | ||||
* file URI. Attaches `encryptionKey` to the result. Changes the mediaType to | * file URI. Attaches `encryptionKey` to the result. Changes the mediaType to | ||||
* `encrypted_photo` or `encrypted_video`. | * `encrypted_photo` or `encrypted_video`. | ||||
Show All 25 Lines | async function encryptMedia(preprocessedMedia: MediaResult): Promise<{ | ||||
if (preprocessedMedia.mediaType === 'photo') { | if (preprocessedMedia.mediaType === 'photo') { | ||||
return { | return { | ||||
steps, | steps, | ||||
result: { | result: { | ||||
...preprocessedMedia, | ...preprocessedMedia, | ||||
mediaType: 'encrypted_photo', | mediaType: 'encrypted_photo', | ||||
uploadURI: encryptionResult.uri, | uploadURI: encryptionResult.uri, | ||||
blobHash: encryptionResult.sha256Hash, | |||||
encryptionKey: encryptionResult.encryptionKey, | encryptionKey: encryptionResult.encryptionKey, | ||||
shouldDisposePath: pathFromURI(encryptionResult.uri), | shouldDisposePath: pathFromURI(encryptionResult.uri), | ||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
// For videos, we also need to encrypt the thumbnail | // For videos, we also need to encrypt the thumbnail | ||||
const { steps: thumbnailEncryptionSteps, result: thumbnailEncryptionResult } = | const { steps: thumbnailEncryptionSteps, result: thumbnailEncryptionResult } = | ||||
await encryptFile(preprocessedMedia.uploadThumbnailURI); | await encryptFile(preprocessedMedia.uploadThumbnailURI); | ||||
steps.push(...thumbnailEncryptionSteps); | steps.push(...thumbnailEncryptionSteps); | ||||
if (!thumbnailEncryptionResult.success) { | if (!thumbnailEncryptionResult.success) { | ||||
return { steps, result: thumbnailEncryptionResult }; | return { steps, result: thumbnailEncryptionResult }; | ||||
} | } | ||||
return { | return { | ||||
steps, | steps, | ||||
result: { | result: { | ||||
...preprocessedMedia, | ...preprocessedMedia, | ||||
mediaType: 'encrypted_video', | mediaType: 'encrypted_video', | ||||
uploadURI: encryptionResult.uri, | uploadURI: encryptionResult.uri, | ||||
blobHash: encryptionResult.sha256Hash, | |||||
encryptionKey: encryptionResult.encryptionKey, | encryptionKey: encryptionResult.encryptionKey, | ||||
uploadThumbnailURI: thumbnailEncryptionResult.uri, | uploadThumbnailURI: thumbnailEncryptionResult.uri, | ||||
thumbnailBlobHash: thumbnailEncryptionResult.sha256Hash, | |||||
thumbnailEncryptionKey: thumbnailEncryptionResult.encryptionKey, | thumbnailEncryptionKey: thumbnailEncryptionResult.encryptionKey, | ||||
shouldDisposePath: pathFromURI(encryptionResult.uri), | shouldDisposePath: pathFromURI(encryptionResult.uri), | ||||
}, | }, | ||||
}; | }; | ||||
} | } | ||||
type DecryptFileStep = | type DecryptFileStep = | ||||
| { | | { | ||||
▲ Show 20 Lines • Show All 176 Lines • Show Last 20 Lines |