Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3396196
D11332.id38255.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D11332.id38255.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D11332: [web][native] Encrypt QR auth messages with AES
Attached
Detach File
Event Timeline
Log In to Comment