Page MenuHomePhabricator

D11332.id38255.diff
No OneTemporary

D11332.id38255.diff

diff --git a/lib/components/qr-auth-handler.react.js b/lib/components/qr-auth-handler.react.js
--- a/lib/components/qr-auth-handler.react.js
+++ b/lib/components/qr-auth-handler.react.js
@@ -12,22 +12,35 @@
import {
peerToPeerMessageTypes,
peerToPeerMessageValidator,
+ type QRCodeAuthMessage,
} from '../types/tunnelbroker/peer-to-peer-message-types.js';
-import { qrCodeAuthMessageTypes } from '../types/tunnelbroker/qr-code-auth-message-types.js';
import {
- createQRAuthTunnelbrokerMessage,
- parseQRAuthTunnelbrokerMessage,
-} from '../utils/qr-code-auth.js';
+ qrCodeAuthMessageTypes,
+ type QRCodeAuthMessagePayload,
+} from '../types/tunnelbroker/qr-code-auth-message-types.js';
type QRAuthHandlerProps = {
+secondaryDeviceID: ?string,
+aesKey: ?string,
+performSecondaryDeviceRegistration: (userID: string) => Promise<void>,
+ +composeMessage: (
+ encryptionKey: string,
+ payload: QRCodeAuthMessagePayload,
+ ) => Promise<QRCodeAuthMessage>,
+ +processMessage: (
+ encryptionKey: string,
+ message: QRCodeAuthMessage,
+ ) => Promise<?QRCodeAuthMessagePayload>,
};
function QRAuthHandler(props: QRAuthHandlerProps): React.Node {
- const { secondaryDeviceID, aesKey, performSecondaryDeviceRegistration } =
- props;
+ const {
+ secondaryDeviceID,
+ aesKey,
+ processMessage,
+ composeMessage,
+ performSecondaryDeviceRegistration,
+ } = props;
const [primaryDeviceID, setPrimaryDeviceID] = React.useState<?string>();
const {
setUnauthorizedDeviceID,
@@ -53,7 +66,7 @@
}
void (async () => {
- const message = createQRAuthTunnelbrokerMessage(aesKey, {
+ const message = await composeMessage(aesKey, {
type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
});
await sendMessage({
@@ -68,6 +81,7 @@
primaryDeviceID,
aesKey,
secondaryDeviceID,
+ composeMessage,
]);
const tunnelbrokerMessageListener = React.useCallback(
@@ -92,10 +106,7 @@
) {
return;
}
- const qrCodeAuthMessage = parseQRAuthTunnelbrokerMessage(
- aesKey,
- innerMessage,
- );
+ const qrCodeAuthMessage = await processMessage(aesKey, innerMessage);
if (
qrCodeAuthMessage?.type ===
@@ -123,6 +134,7 @@
identityClient,
aesKey,
performSecondaryDeviceRegistration,
+ processMessage,
],
);
diff --git a/lib/utils/qr-code-auth.js b/lib/utils/qr-code-auth.js
deleted file mode 100644
--- a/lib/utils/qr-code-auth.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// @flow
-
-import {
- peerToPeerMessageTypes,
- type QRCodeAuthMessage,
-} from '../types/tunnelbroker/peer-to-peer-message-types.js';
-import {
- qrCodeAuthMessagePayloadValidator,
- type QRCodeAuthMessagePayload,
-} from '../types/tunnelbroker/qr-code-auth-message-types.js';
-
-function createQRAuthTunnelbrokerMessage(
- encryptionKey: string,
- payload: QRCodeAuthMessagePayload,
-): QRCodeAuthMessage {
- return {
- type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE,
- encryptedContent: JSON.stringify(payload),
- };
-}
-
-function parseQRAuthTunnelbrokerMessage(
- encryptionKey: string,
- message: QRCodeAuthMessage,
-): ?QRCodeAuthMessagePayload {
- const payload = JSON.parse(message.encryptedContent);
- if (!qrCodeAuthMessagePayloadValidator.is(payload)) {
- return null;
- }
-
- return payload;
-}
-
-export { createQRAuthTunnelbrokerMessage, parseQRAuthTunnelbrokerMessage };
diff --git a/native/profile/secondary-device-qr-code-scanner.react.js b/native/profile/secondary-device-qr-code-scanner.react.js
--- a/native/profile/secondary-device-qr-code-scanner.react.js
+++ b/native/profile/secondary-device-qr-code-scanner.react.js
@@ -20,13 +20,13 @@
type PeerToPeerMessage,
} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
import { qrCodeAuthMessageTypes } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
-import {
- createQRAuthTunnelbrokerMessage,
- parseQRAuthTunnelbrokerMessage,
-} from 'lib/utils/qr-code-auth.js';
import type { ProfileNavigationProp } from './profile.react.js';
import type { NavigationRoute } from '../navigation/route-names.js';
+import {
+ composeTunnelbrokerQRAuthMessage,
+ parseTunnelbrokerQRAuthMessage,
+} from '../qr-code/qr-code-utils.js';
import { useStyles } from '../themes/colors.js';
import Alert from '../utils/alert.js';
@@ -145,7 +145,7 @@
return;
}
- const payload = parseQRAuthTunnelbrokerMessage(
+ const payload = await parseTunnelbrokerQRAuthMessage(
encryptionKey,
innerMessage,
);
@@ -158,12 +158,15 @@
void broadcastDeviceListUpdate();
- const backupKeyMessage = createQRAuthTunnelbrokerMessage(encryptionKey, {
- type: qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE,
- backupID: 'stub',
- backupDataKey: 'stub',
- backupLogDataKey: 'stub',
- });
+ const backupKeyMessage = await composeTunnelbrokerQRAuthMessage(
+ encryptionKey,
+ {
+ type: qrCodeAuthMessageTypes.BACKUP_DATA_KEY_MESSAGE,
+ backupID: 'stub',
+ backupDataKey: 'stub',
+ backupLogDataKey: 'stub',
+ },
+ );
await tunnelbrokerContext.sendMessage({
deviceID: targetDeviceID,
payload: JSON.stringify(backupKeyMessage),
@@ -228,7 +231,7 @@
throw new Error('missing auth metadata');
}
await addDeviceToList(ed25519);
- const message = createQRAuthTunnelbrokerMessage(aes256, {
+ const message = await composeTunnelbrokerQRAuthMessage(aes256, {
type: qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS,
userID,
primaryDeviceID,
diff --git a/native/qr-code/qr-code-screen.react.js b/native/qr-code/qr-code-screen.react.js
--- a/native/qr-code/qr-code-screen.react.js
+++ b/native/qr-code/qr-code-screen.react.js
@@ -16,6 +16,10 @@
} from 'lib/types/identity-service-types.js';
import type { QRCodeSignInNavigationProp } from './qr-code-sign-in-navigator.react.js';
+import {
+ composeTunnelbrokerQRAuthMessage,
+ parseTunnelbrokerQRAuthMessage,
+} from './qr-code-utils.js';
import { commCoreModule } from '../native-modules.js';
import type { NavigationRoute } from '../navigation/route-names.js';
import { useStyles } from '../themes/colors.js';
@@ -93,6 +97,8 @@
secondaryDeviceID={qrData?.deviceID}
aesKey={qrData?.aesKey}
performSecondaryDeviceRegistration={performRegistration}
+ composeMessage={composeTunnelbrokerQRAuthMessage}
+ processMessage={parseTunnelbrokerQRAuthMessage}
/>
<View style={styles.container}>
<Text style={styles.heading}>Log in to Comm</Text>
diff --git a/native/qr-code/qr-code-utils.js b/native/qr-code/qr-code-utils.js
new file mode 100644
--- /dev/null
+++ b/native/qr-code/qr-code-utils.js
@@ -0,0 +1,55 @@
+// @flow
+
+import { hexToUintArray } from 'lib/media/data-utils.js';
+import {
+ peerToPeerMessageTypes,
+ type QRCodeAuthMessage,
+} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
+import {
+ qrCodeAuthMessagePayloadValidator,
+ type QRCodeAuthMessagePayload,
+} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
+
+import {
+ convertBytesToObj,
+ convertObjToBytes,
+} from '../backup/conversion-utils.js';
+import { commUtilsModule } from '../native-modules.js';
+import * as AES from '../utils/aes-crypto-module.js';
+
+function composeTunnelbrokerQRAuthMessage(
+ encryptionKey: string,
+ obj: QRCodeAuthMessagePayload,
+): Promise<QRCodeAuthMessage> {
+ const objBytes = convertObjToBytes(obj);
+ const keyBytes = hexToUintArray(encryptionKey);
+ const encryptedBytes = AES.encrypt(keyBytes, objBytes);
+ const encryptedContent = commUtilsModule.base64EncodeBuffer(
+ encryptedBytes.buffer,
+ );
+ return Promise.resolve({
+ type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE,
+ encryptedContent,
+ });
+}
+
+function parseTunnelbrokerQRAuthMessage(
+ encryptionKey: string,
+ message: QRCodeAuthMessage,
+): Promise<?QRCodeAuthMessagePayload> {
+ const encryptedData = commUtilsModule.base64DecodeBuffer(
+ message.encryptedContent,
+ );
+ const decryptedData = AES.decrypt(
+ hexToUintArray(encryptionKey),
+ new Uint8Array(encryptedData),
+ );
+ const payload = convertBytesToObj<QRCodeAuthMessagePayload>(decryptedData);
+ if (!qrCodeAuthMessagePayloadValidator.is(payload)) {
+ return Promise.resolve(null);
+ }
+
+ return Promise.resolve(payload);
+}
+
+export { composeTunnelbrokerQRAuthMessage, parseTunnelbrokerQRAuthMessage };
diff --git a/web/account/qr-code-login.react.js b/web/account/qr-code-login.react.js
--- a/web/account/qr-code-login.react.js
+++ b/web/account/qr-code-login.react.js
@@ -10,7 +10,8 @@
import { QRAuthHandler } from 'lib/components/qr-auth-handler.react.js';
import { qrCodeLinkURL } from 'lib/facts/links.js';
import { generateKeyCommon } from 'lib/media/aes-crypto-utils-common.js';
-import { uintArrayToHexString } from 'lib/media/data-utils.js';
+import * as AES from 'lib/media/aes-crypto-utils-common.js';
+import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js';
import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js';
import type { CryptoStore, PickledOLMAccount } from 'lib/types/crypto-types.js';
@@ -19,11 +20,27 @@
SignedMessage,
} from 'lib/types/identity-service-types.js';
import type { WebAppState } from 'lib/types/redux-types.js';
+import {
+ peerToPeerMessageTypes,
+ type QRCodeAuthMessage,
+} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
+import {
+ qrCodeAuthMessagePayloadValidator,
+ type QRCodeAuthMessagePayload,
+} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js';
import css from './qr-code-login.css';
import { initOlm } from '../olm/olm-utils.js';
import { useSelector } from '../redux/redux-utils.js';
+import {
+ base64DecodeBuffer,
+ base64EncodeBuffer,
+} from '../utils/base64-utils.js';
+import {
+ convertBytesToObj,
+ convertObjToBytes,
+} from '../utils/conversion-utils.js';
const deviceIDAndPrimaryAccountSelector: (state: WebAppState) => {
ed25519Key: ?string,
@@ -36,6 +53,38 @@
}),
);
+async function composeTunnelbrokerMessage(
+ encryptionKey: string,
+ obj: QRCodeAuthMessagePayload,
+): Promise<QRCodeAuthMessage> {
+ const objBytes = convertObjToBytes(obj);
+ const keyBytes = hexToUintArray(encryptionKey);
+ const encryptedBytes = await AES.encryptCommon(crypto, keyBytes, objBytes);
+ const encryptedContent = base64EncodeBuffer(encryptedBytes);
+ return {
+ type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE,
+ encryptedContent,
+ };
+}
+
+async function parseTunnelbrokerMessage(
+ encryptionKey: string,
+ message: QRCodeAuthMessage,
+): Promise<?QRCodeAuthMessagePayload> {
+ const encryptedData = base64DecodeBuffer(message.encryptedContent);
+ const decryptedData = await AES.decryptCommon(
+ crypto,
+ hexToUintArray(encryptionKey),
+ new Uint8Array(encryptedData),
+ );
+ const payload = convertBytesToObj<QRCodeAuthMessagePayload>(decryptedData);
+ if (!qrCodeAuthMessagePayloadValidator.is(payload)) {
+ return null;
+ }
+
+ return payload;
+}
+
function QrCodeLogin(): React.Node {
const [qrCodeValue, setQrCodeValue] = React.useState<?string>();
const { ed25519Key, primaryAccount } = useSelector(
@@ -120,6 +169,8 @@
secondaryDeviceID={deviceKeys?.deviceID}
aesKey={deviceKeys?.aesKey}
performSecondaryDeviceRegistration={performRegistration}
+ composeMessage={composeTunnelbrokerMessage}
+ processMessage={parseTunnelbrokerMessage}
/>
<div className={css.qrContainer}>
<div className={css.title}>Log in to Comm</div>
diff --git a/web/jest-setup.js b/web/jest-setup.js
--- a/web/jest-setup.js
+++ b/web/jest-setup.js
@@ -1,8 +1,12 @@
// @flow
+/* eslint-disable no-undef -- "global is not defined" */
import crypto from 'crypto';
+import util from 'util';
// crypto.webcrypto was introduced in Node 15.10.0.
// It is not defined in Flow so we need a cast
-// eslint-disable-next-line no-undef -- "global is not defined"
global.crypto = (crypto: any).webcrypto;
+
+global.TextEncoder = util.TextEncoder;
+global.TextDecoder = util.TextDecoder;
diff --git a/web/utils/conversion-utils.js b/web/utils/conversion-utils.js
new file mode 100644
--- /dev/null
+++ b/web/utils/conversion-utils.js
@@ -0,0 +1,13 @@
+// @flow
+
+function convertObjToBytes<T>(obj: T): Uint8Array {
+ const objStr = JSON.stringify(obj);
+ return new TextEncoder().encode(objStr ?? '');
+}
+
+function convertBytesToObj<T>(bytes: Uint8Array): T {
+ const str = new TextDecoder().decode(bytes.buffer);
+ return JSON.parse(str);
+}
+
+export { convertObjToBytes, convertBytesToObj };
diff --git a/web/utils/conversion-utils.test.js b/web/utils/conversion-utils.test.js
new file mode 100644
--- /dev/null
+++ b/web/utils/conversion-utils.test.js
@@ -0,0 +1,14 @@
+// @flow
+
+import { convertBytesToObj, convertObjToBytes } from './conversion-utils.js';
+
+describe('convertObjToBytes and convertBytesToObj', () => {
+ it('should convert object to byte array and back', () => {
+ const obj = { hello: 'world', foo: 'bar', a: 2, b: false };
+
+ const bytes = convertObjToBytes(obj);
+ const restored = convertBytesToObj<typeof obj>(bytes);
+
+ expect(restored).toStrictEqual(obj);
+ });
+});

File Metadata

Mime Type
text/plain
Expires
Mon, Dec 2, 11:02 AM (20 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2607297
Default Alt Text
D11332.id38255.diff (13 KB)

Event Timeline