Page MenuHomePhabricator

D7233.id24330.diff
No OneTemporary

D7233.id24330.diff

diff --git a/lib/types/media-types.js b/lib/types/media-types.js
--- a/lib/types/media-types.js
+++ b/lib/types/media-types.js
@@ -237,6 +237,30 @@
+orientation: ?number,
};
+export type EncryptFileMediaMissionStep =
+ | {
+ +step: 'read_plaintext_file',
+ +file: string,
+ +time: number, // ms
+ +success: boolean,
+ +exceptionMessage: ?string,
+ }
+ | {
+ +step: 'encrypt_data',
+ +dataSize: number,
+ +time: number, // ms
+ +isPadded: boolean,
+ +success: boolean,
+ +exceptionMessage: ?string,
+ }
+ | {
+ +step: 'write_encrypted_file',
+ +file: string,
+ +time: number, // ms
+ +success: boolean,
+ +exceptionMessage: ?string,
+ };
+
export type MediaLibrarySelection =
| {
+step: 'photo_library',
@@ -391,6 +415,7 @@
}
| FetchFileHashMediaMissionStep
| CopyFileMediaMissionStep
+ | EncryptFileMediaMissionStep
| GetOrientationMediaMissionStep
| {
+step: 'preload_image',
@@ -543,6 +568,10 @@
| {
+success: false,
+reason: 'web_sibling_validation_failed',
+ }
+ | {
+ +success: false,
+ +reason: 'encryption_failed',
};
export type MediaMissionResult = MediaMissionFailure | { +success: true };
diff --git a/native/media/encryption-utils.js b/native/media/encryption-utils.js
--- a/native/media/encryption-utils.js
+++ b/native/media/encryption-utils.js
@@ -2,11 +2,22 @@
import filesystem from 'react-native-fs';
-import { base64FromIntArray, hexToUintArray } from 'lib/media/data-utils.js';
-import { fileInfoFromData, readableFilename } from 'lib/media/file-utils.js';
-import type { MediaMissionFailure } from 'lib/types/media-types.js';
+import {
+ base64FromIntArray,
+ uintArrayToHexString,
+ hexToUintArray,
+} from 'lib/media/data-utils.js';
+import {
+ replaceExtension,
+ fileInfoFromData,
+ readableFilename,
+} from 'lib/media/file-utils.js';
+import type {
+ MediaMissionFailure,
+ EncryptFileMediaMissionStep,
+} from 'lib/types/media-types.js';
import { getMessageForException } from 'lib/utils/errors.js';
-import { unpad } from 'lib/utils/pkcs7-padding.js';
+import { pad, unpad, calculatePaddedLength } from 'lib/utils/pkcs7-padding.js';
import { temporaryDirectoryPath } from './file-utils.js';
import { getFetchableURI } from './identifier-utils.js';
@@ -14,6 +25,116 @@
const PADDING_THRESHOLD = 5_000_000; // we don't pad files larger than this
+type EncryptedFileResult = {
+ +success: true,
+ +uri: string,
+ +encryptionKey: string,
+};
+
+/**
+ * Encrypts a single file and returns the encrypted file URI
+ * 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,
+ * with the same name, but with the extension ".dat".
+ *
+ * @param uri uri to the file to encrypt
+ * @returns encryption result along with mission steps
+ */
+async function encryptFile(uri: string): Promise<{
+ steps: $ReadOnlyArray<EncryptFileMediaMissionStep>,
+ result: MediaMissionFailure | EncryptedFileResult,
+}> {
+ let success = true,
+ exceptionMessage;
+ const steps: EncryptFileMediaMissionStep[] = [];
+ const destination = replaceExtension(uri, 'dat');
+
+ // Step 1. Read the file
+ const startOpenFile = Date.now();
+ let data;
+ try {
+ const response = await fetch(getFetchableURI(uri));
+ const buffer = await response.arrayBuffer();
+ data = new Uint8Array(buffer);
+ } catch (e) {
+ success = false;
+ exceptionMessage = getMessageForException(e);
+ }
+ steps.push({
+ step: 'read_plaintext_file',
+ file: uri,
+ time: Date.now() - startOpenFile,
+ success,
+ exceptionMessage,
+ });
+ if (!success || !data) {
+ return {
+ steps,
+ result: { success: false, reason: 'fetch_failed' },
+ };
+ }
+
+ // Step 2. Encrypt the file
+ const startEncrypt = Date.now();
+ const paddedLength = calculatePaddedLength(data.byteLength);
+ const shouldPad = paddedLength <= 5_000_000;
+ let key, encryptedData;
+ try {
+ const plaintextData = shouldPad ? pad(data) : data;
+ key = await AES.generateKey();
+ encryptedData = await AES.encrypt(key, plaintextData);
+ } catch (e) {
+ success = false;
+ exceptionMessage = getMessageForException(e);
+ }
+ steps.push({
+ step: 'encrypt_data',
+ dataSize: encryptedData?.byteLength ?? -1,
+ isPadded: shouldPad,
+ time: Date.now() - startEncrypt,
+ success,
+ exceptionMessage,
+ });
+ if (!success || !encryptedData || !key) {
+ return {
+ steps,
+ result: { success: false, reason: 'encryption_failed' },
+ };
+ }
+
+ // Step 3. Write the encrypted file
+ const startWriteFile = Date.now();
+ try {
+ const targetBase64 = base64FromIntArray(encryptedData);
+ await filesystem.writeFile(destination, targetBase64, 'base64');
+ } catch (e) {
+ success = false;
+ exceptionMessage = getMessageForException(e);
+ }
+ steps.push({
+ step: 'write_encrypted_file',
+ file: destination,
+ time: Date.now() - startWriteFile,
+ success,
+ exceptionMessage,
+ });
+ if (!success) {
+ return {
+ steps,
+ result: { success: false, reason: 'write_file_failed' },
+ };
+ }
+
+ return {
+ steps,
+ result: {
+ success: true,
+ uri: destination,
+ encryptionKey: uintArrayToHexString(key),
+ },
+ };
+}
+
type DecryptFileStep =
| {
+step: 'fetch_file',
@@ -191,4 +312,4 @@
};
}
-export { decryptMedia };
+export { encryptFile, decryptMedia };

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 23, 2:05 PM (19 h, 20 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2694832
Default Alt Text
D7233.id24330.diff (5 KB)

Event Timeline