Page MenuHomePhabricator

D12146.id40646.diff
No OneTemporary

D12146.id40646.diff

diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js
--- a/keyserver/src/keyserver.js
+++ b/keyserver/src/keyserver.js
@@ -35,7 +35,10 @@
} from './responders/website-responders.js';
import { webWorkerResponder } from './responders/webworker-responders.js';
import { onConnection } from './socket/socket.js';
-import { createAndMaintainTunnelbrokerWebsocket } from './socket/tunnelbroker.js';
+import {
+ createAndMaintainTunnelbrokerWebsocket,
+ createAndMaintainAnonymousTunnelbrokerWebsocket,
+} from './socket/tunnelbroker.js';
import {
multerProcessor,
multimediaUploadResponder,
@@ -97,32 +100,13 @@
process.exit(2);
}
- // Allow login to be optional until staging environment is available
- try {
- // We await here to ensure that the keyserver has been provisioned a
- // commServicesAccessToken. In the future, this will be necessary for
- // many keyserver operations.
- const identityInfo = await verifyUserLoggedIn();
- // We don't await here, as Tunnelbroker communication is not needed for
- // normal keyserver behavior yet. In addition, this doesn't return
- // information useful for other keyserver functions.
- ignorePromiseRejections(
- createAndMaintainTunnelbrokerWebsocket(identityInfo),
- );
- if (process.env.NODE_ENV === 'development') {
- await createAuthoritativeKeyserverConfigFiles(identityInfo.userId);
- }
- } catch (e) {
- console.warn(
- 'Failed identity login. Login optional until staging environment is available',
- );
- }
-
if (shouldDisplayQRCodeInTerminal) {
try {
const aes256Key = crypto.randomBytes(32).toString('hex');
const ed25519Key = await getContentSigningKey();
+ await createAndMaintainAnonymousTunnelbrokerWebsocket(aes256Key);
+
console.log(
'\nOpen the Comm app on your phone and scan the QR code below\n',
);
@@ -136,6 +120,27 @@
} catch (e) {
console.log('Error generating QR code', e);
}
+ } else {
+ // Allow login to be optional until staging environment is available
+ try {
+ // We await here to ensure that the keyserver has been provisioned a
+ // commServicesAccessToken. In the future, this will be necessary for
+ // many keyserver operations.
+ const identityInfo = await verifyUserLoggedIn();
+ // We don't await here, as Tunnelbroker communication is not needed for
+ // normal keyserver behavior yet. In addition, this doesn't return
+ // information useful for other keyserver functions.
+ ignorePromiseRejections(
+ createAndMaintainTunnelbrokerWebsocket(identityInfo),
+ );
+ if (process.env.NODE_ENV === 'development') {
+ await createAuthoritativeKeyserverConfigFiles(identityInfo.userId);
+ }
+ } catch (e) {
+ console.warn(
+ 'Failed identity login. Login optional until staging environment is available',
+ );
+ }
}
if (!isCPUProfilingEnabled) {
diff --git a/keyserver/src/socket/tunnelbroker-socket.js b/keyserver/src/socket/tunnelbroker-socket.js
--- a/keyserver/src/socket/tunnelbroker-socket.js
+++ b/keyserver/src/socket/tunnelbroker-socket.js
@@ -4,6 +4,7 @@
import uuid from 'uuid';
import WebSocket from 'ws';
+import { hexToUintArray } from 'lib/media/data-utils.js';
import { tunnelbrokerHeartbeatTimeout } from 'lib/shared/timeouts.js';
import type { TunnelbrokerClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js';
import type { MessageReceiveConfirmation } from 'lib/types/tunnelbroker/message-receive-confirmation-types.js';
@@ -15,12 +16,24 @@
tunnelbrokerMessageValidator,
} from 'lib/types/tunnelbroker/messages.js';
import {
+ qrCodeAuthMessageValidator,
type RefreshKeyRequest,
refreshKeysRequestValidator,
+ type QRCodeAuthMessage,
} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
-import type { ConnectionInitializationMessage } from 'lib/types/tunnelbroker/session-types.js';
+import {
+ type QRCodeAuthMessagePayload,
+ qrCodeAuthMessagePayloadValidator,
+ qrCodeAuthMessageTypes,
+} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
+import type {
+ ConnectionInitializationMessage,
+ AnonymousInitializationMessage,
+} from 'lib/types/tunnelbroker/session-types.js';
import type { Heartbeat } from 'lib/types/websocket/heartbeat-types.js';
+import { convertBytesToObj } from 'lib/utils/conversion-utils.js';
+import { decrypt } from '../utils/aes-crypto-utils.js';
import { uploadNewOneTimeKeys } from '../utils/olm-utils.js';
type PromiseCallbacks = {
@@ -36,11 +49,16 @@
promises: Promises = {};
heartbeatTimeoutID: ?TimeoutID;
oneTimeKeysPromise: ?Promise<void>;
+ anonymous: boolean = false;
+ qrAuthEncryptionKey: ?string;
constructor(
socketURL: string,
- initMessage: ConnectionInitializationMessage,
+ initMessage:
+ | ConnectionInitializationMessage
+ | AnonymousInitializationMessage,
onClose: () => mixed,
+ qrAuthEncryptionKey?: string,
) {
const socket = new WebSocket(socketURL);
@@ -68,6 +86,10 @@
socket.on('message', this.onMessage);
this.ws = socket;
+ this.anonymous = !initMessage.accessToken;
+ if (qrAuthEncryptionKey) {
+ this.qrAuthEncryptionKey = qrAuthEncryptionKey;
+ }
}
onMessage: (event: ArrayBuffer) => Promise<void> = async (
@@ -95,7 +117,11 @@
) {
if (message.status.type === 'Success' && !this.connected) {
this.connected = true;
- console.info('session with Tunnelbroker created');
+ console.info(
+ this.anonymous
+ ? 'anonymous session with Tunnelbroker created'
+ : 'session with Tunnelbroker created',
+ );
} else if (message.status.type === 'Success' && this.connected) {
console.info(
'received ConnectionInitializationResponse with status: Success for already connected socket',
@@ -117,7 +143,17 @@
const { payload } = message;
try {
const messageToKeyserver = JSON.parse(payload);
- if (refreshKeysRequestValidator.is(messageToKeyserver)) {
+ if (qrCodeAuthMessageValidator.is(messageToKeyserver)) {
+ const request: QRCodeAuthMessage = messageToKeyserver;
+ const qrCodeAuthMessage = await this.parseQRCodeAuthMessage(request);
+ if (
+ !qrCodeAuthMessage ||
+ qrCodeAuthMessage.type !==
+ qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
+ ) {
+ return;
+ }
+ } else if (refreshKeysRequestValidator.is(messageToKeyserver)) {
const request: RefreshKeyRequest = messageToKeyserver;
this.debouncedRefreshOneTimeKeys(request.numberOfKeys);
}
@@ -212,6 +248,26 @@
this.connected = false;
}, tunnelbrokerHeartbeatTimeout);
}
+
+ parseQRCodeAuthMessage: (
+ message: QRCodeAuthMessage,
+ ) => Promise<?QRCodeAuthMessagePayload> = async message => {
+ const encryptionKey = this.qrAuthEncryptionKey;
+ if (!encryptionKey) {
+ return null;
+ }
+ const encryptedData = Buffer.from(message.encryptedContent, 'base64');
+ const decryptedData = await decrypt(
+ hexToUintArray(encryptionKey),
+ new Uint8Array(encryptedData),
+ );
+ const payload = convertBytesToObj<QRCodeAuthMessagePayload>(decryptedData);
+ if (!qrCodeAuthMessagePayloadValidator.is(payload)) {
+ return null;
+ }
+
+ return payload;
+ };
}
export default TunnelbrokerSocket;
diff --git a/keyserver/src/socket/tunnelbroker.js b/keyserver/src/socket/tunnelbroker.js
--- a/keyserver/src/socket/tunnelbroker.js
+++ b/keyserver/src/socket/tunnelbroker.js
@@ -1,7 +1,10 @@
// @flow
import { clientTunnelbrokerSocketReconnectDelay } from 'lib/shared/timeouts.js';
-import type { ConnectionInitializationMessage } from 'lib/types/tunnelbroker/session-types.js';
+import type {
+ ConnectionInitializationMessage,
+ AnonymousInitializationMessage,
+} from 'lib/types/tunnelbroker/session-types.js';
import { getCommConfig } from 'lib/utils/comm-config.js';
import sleep from 'lib/utils/sleep.js';
@@ -45,13 +48,50 @@
deviceType: 'keyserver',
};
+ createAndMaintainTunnelbrokerWebsocketBase(tbConnectionInfo.url, initMessage);
+}
+
+async function createAndMaintainAnonymousTunnelbrokerWebsocket(
+ encryptionKey: string,
+) {
+ const [deviceID, tbConnectionInfo] = await Promise.all([
+ getContentSigningKey(),
+ getTBConnectionInfo(),
+ ]);
+
+ const initMessage: AnonymousInitializationMessage = {
+ type: 'AnonymousInitializationMessage',
+ deviceID: deviceID,
+ deviceType: 'keyserver',
+ };
+
+ createAndMaintainTunnelbrokerWebsocketBase(
+ tbConnectionInfo.url,
+ initMessage,
+ encryptionKey,
+ );
+}
+
+function createAndMaintainTunnelbrokerWebsocketBase(
+ url: string,
+ initMessage: ConnectionInitializationMessage | AnonymousInitializationMessage,
+ encryptionKey?: string,
+) {
const createNewTunnelbrokerSocket = () => {
- new TunnelbrokerSocket(tbConnectionInfo.url, initMessage, async () => {
- await sleep(clientTunnelbrokerSocketReconnectDelay);
- createNewTunnelbrokerSocket();
- });
+ new TunnelbrokerSocket(
+ url,
+ initMessage,
+ async () => {
+ await sleep(clientTunnelbrokerSocketReconnectDelay);
+ createNewTunnelbrokerSocket();
+ },
+ encryptionKey,
+ );
};
createNewTunnelbrokerSocket();
}
-export { createAndMaintainTunnelbrokerWebsocket };
+export {
+ createAndMaintainTunnelbrokerWebsocket,
+ createAndMaintainAnonymousTunnelbrokerWebsocket,
+};
diff --git a/lib/utils/conversion-utils.js b/lib/utils/conversion-utils.js
--- a/lib/utils/conversion-utils.js
+++ b/lib/utils/conversion-utils.js
@@ -153,9 +153,25 @@
return input;
}
+// NOTE: This function should not be called from native. On native, we should
+// use `convertObjToBytes` in native/backup/conversion-utils.js instead.
+function convertObjToBytes<T>(obj: T): Uint8Array {
+ const objStr = JSON.stringify(obj);
+ return new TextEncoder().encode(objStr ?? '');
+}
+
+// NOTE: This function should not be called from native. On native, we should
+// use `convertBytesToObj` in native/backup/conversion-utils.js instead.
+function convertBytesToObj<T>(bytes: Uint8Array): T {
+ const str = new TextDecoder().decode(bytes.buffer);
+ return JSON.parse(str);
+}
+
export {
convertClientIDsToServerIDs,
convertServerIDsToClientIDs,
extractUserIDsFromPayload,
convertObject,
+ convertObjToBytes,
+ convertBytesToObj,
};
diff --git a/lib/utils/conversion-utils.test.js b/lib/utils/conversion-utils.test.js
--- a/lib/utils/conversion-utils.test.js
+++ b/lib/utils/conversion-utils.test.js
@@ -7,6 +7,8 @@
extractUserIDsFromPayload,
convertServerIDsToClientIDs,
convertClientIDsToServerIDs,
+ convertBytesToObj,
+ convertObjToBytes,
} from './conversion-utils.js';
import { tShape, tID, idSchemaRegex } from './validation-utils.js';
import { fetchMessageInfosResponseValidator } from '../types/validators/message-validators.js';
@@ -119,3 +121,14 @@
).toEqual(['0', '1', '100', '200']);
});
});
+
+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);
+ });
+});
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
@@ -19,6 +19,10 @@
qrCodeAuthMessagePayloadValidator,
type QRCodeAuthMessagePayload,
} from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
+import {
+ convertBytesToObj,
+ convertObjToBytes,
+} from 'lib/utils/conversion-utils.js';
import { getContentSigningKey } from 'lib/utils/crypto-utils.js';
import { getMessageForException } from 'lib/utils/errors.js';
@@ -29,10 +33,6 @@
base64DecodeBuffer,
base64EncodeBuffer,
} from '../utils/base64-utils.js';
-import {
- convertBytesToObj,
- convertObjToBytes,
-} from '../utils/conversion-utils.js';
async function composeTunnelbrokerMessage(
encryptionKey: string,
diff --git a/web/utils/conversion-utils.js b/web/utils/conversion-utils.js
deleted file mode 100644
--- a/web/utils/conversion-utils.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// @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
deleted file mode 100644
--- a/web/utils/conversion-utils.test.js
+++ /dev/null
@@ -1,14 +0,0 @@
-// @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
Sat, Nov 30, 6:00 PM (21 h, 9 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2601869
Default Alt Text
D12146.id40646.diff (13 KB)

Event Timeline