Page MenuHomePhabricator

D12213.id41763.diff
No OneTemporary

D12213.id41763.diff

diff --git a/keyserver/src/keyserver.js b/keyserver/src/keyserver.js
--- a/keyserver/src/keyserver.js
+++ b/keyserver/src/keyserver.js
@@ -35,10 +35,7 @@
} from './responders/website-responders.js';
import { webWorkerResponder } from './responders/webworker-responders.js';
import { onConnection } from './socket/socket.js';
-import {
- createAndMaintainTunnelbrokerWebsocket,
- createAndMaintainAnonymousTunnelbrokerWebsocket,
-} from './socket/tunnelbroker.js';
+import { createAndMaintainTunnelbrokerWebsocket } from './socket/tunnelbroker.js';
import {
multerProcessor,
multimediaUploadResponder,
@@ -105,7 +102,7 @@
const aes256Key = crypto.randomBytes(32).toString('hex');
const ed25519Key = await getContentSigningKey();
- await createAndMaintainAnonymousTunnelbrokerWebsocket(aes256Key);
+ await createAndMaintainTunnelbrokerWebsocket(aes256Key);
console.log(
'\nOpen the Comm app on your phone and scan the QR code below\n',
@@ -130,9 +127,7 @@
// 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),
- );
+ ignorePromiseRejections(createAndMaintainTunnelbrokerWebsocket(null));
if (process.env.NODE_ENV === 'development') {
await createAuthoritativeKeyserverConfigFiles(identityInfo.userId);
}
diff --git a/keyserver/src/socket/tunnelbroker-socket.js b/keyserver/src/socket/tunnelbroker-socket.js
deleted file mode 100644
--- a/keyserver/src/socket/tunnelbroker-socket.js
+++ /dev/null
@@ -1,306 +0,0 @@
-// @flow
-
-import _debounce from 'lodash/debounce.js';
-import { getRustAPI } from 'rust-node-addon';
-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';
-import type { MessageSentStatus } from 'lib/types/tunnelbroker/message-to-device-request-status-types.js';
-import type { MessageToDeviceRequest } from 'lib/types/tunnelbroker/message-to-device-request-types.js';
-import {
- type TunnelbrokerMessage,
- tunnelbrokerMessageTypes,
- 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 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 { fetchOlmAccount } from '../updaters/olm-account-updater.js';
-import { decrypt } from '../utils/aes-crypto-utils.js';
-import {
- uploadNewOneTimeKeys,
- getNewDeviceKeyUpload,
-} from '../utils/olm-utils.js';
-
-type PromiseCallbacks = {
- +resolve: () => void,
- +reject: (error: string) => void,
-};
-type Promises = { [clientMessageID: string]: PromiseCallbacks };
-
-class TunnelbrokerSocket {
- ws: WebSocket;
- connected: boolean = false;
- closed: boolean = false;
- promises: Promises = {};
- heartbeatTimeoutID: ?TimeoutID;
- oneTimeKeysPromise: ?Promise<void>;
- anonymous: boolean = false;
- qrAuthEncryptionKey: ?string;
-
- constructor(
- socketURL: string,
- initMessage:
- | ConnectionInitializationMessage
- | AnonymousInitializationMessage,
- onClose: () => mixed,
- qrAuthEncryptionKey?: string,
- ) {
- const socket = new WebSocket(socketURL);
-
- socket.on('open', () => {
- if (!this.closed) {
- socket.send(JSON.stringify(initMessage));
- }
- });
-
- socket.on('close', async () => {
- if (this.closed) {
- return;
- }
- this.closed = true;
- this.connected = false;
- this.stopHeartbeatTimeout();
- console.error('Connection to Tunnelbroker closed');
- onClose();
- });
-
- socket.on('error', (error: Error) => {
- console.error('Tunnelbroker socket error:', error.message);
- });
-
- socket.on('message', this.onMessage);
-
- this.ws = socket;
- this.anonymous = !initMessage.accessToken;
- if (qrAuthEncryptionKey) {
- this.qrAuthEncryptionKey = qrAuthEncryptionKey;
- }
- }
-
- onMessage: (event: ArrayBuffer) => Promise<void> = async (
- event: ArrayBuffer,
- ) => {
- let rawMessage;
- try {
- rawMessage = JSON.parse(event.toString());
- } catch (e) {
- console.error('error while parsing Tunnelbroker message:', e.message);
- return;
- }
-
- if (!tunnelbrokerMessageValidator.is(rawMessage)) {
- console.error('invalid TunnelbrokerMessage: ', rawMessage.toString());
- return;
- }
- const message: TunnelbrokerMessage = rawMessage;
-
- this.resetHeartbeatTimeout();
-
- if (
- message.type ===
- tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE
- ) {
- if (message.status.type === 'Success' && !this.connected) {
- this.connected = true;
- 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',
- );
- } else {
- this.connected = false;
- console.error(
- 'creating session with Tunnelbroker error:',
- message.status.data,
- );
- }
- } else if (message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE) {
- const confirmation: MessageReceiveConfirmation = {
- type: tunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION,
- messageIDs: [message.messageID],
- };
- this.ws.send(JSON.stringify(confirmation));
-
- const { payload } = message;
- try {
- const messageToKeyserver = JSON.parse(payload);
- if (qrCodeAuthMessageValidator.is(messageToKeyserver)) {
- const request: QRCodeAuthMessage = messageToKeyserver;
- const [qrCodeAuthMessage, rustAPI, accountInfo] = await Promise.all([
- this.parseQRCodeAuthMessage(request),
- getRustAPI(),
- fetchOlmAccount('content'),
- ]);
- if (
- !qrCodeAuthMessage ||
- qrCodeAuthMessage.type !==
- qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
- ) {
- return;
- }
- // eslint-disable-next-line no-unused-vars
- const { primaryDeviceID, userID } = qrCodeAuthMessage;
- const [nonce, deviceKeyUpload] = await Promise.all([
- rustAPI.generateNonce(),
- getNewDeviceKeyUpload(),
- ]);
- const signedIdentityKeysBlob = {
- payload: deviceKeyUpload.keyPayload,
- signature: deviceKeyUpload.keyPayloadSignature,
- };
- const nonceSignature = accountInfo.account.sign(nonce);
-
- await rustAPI.uploadSecondaryDeviceKeysAndLogIn(
- userID,
- nonce,
- nonceSignature,
- signedIdentityKeysBlob,
- deviceKeyUpload.contentPrekey,
- deviceKeyUpload.contentPrekeySignature,
- deviceKeyUpload.notifPrekey,
- deviceKeyUpload.notifPrekeySignature,
- deviceKeyUpload.contentOneTimeKeys,
- deviceKeyUpload.notifOneTimeKeys,
- );
- } else if (refreshKeysRequestValidator.is(messageToKeyserver)) {
- const request: RefreshKeyRequest = messageToKeyserver;
- this.debouncedRefreshOneTimeKeys(request.numberOfKeys);
- }
- } catch (e) {
- console.error(
- 'error while processing message to keyserver:',
- e.message,
- );
- }
- } else if (
- message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS
- ) {
- for (const status: MessageSentStatus of message.clientMessageIDs) {
- if (status.type === 'Success') {
- if (this.promises[status.data]) {
- this.promises[status.data].resolve();
- delete this.promises[status.data];
- } else {
- console.log(
- 'received successful response for a non-existent request',
- );
- }
- } else if (status.type === 'Error') {
- if (this.promises[status.data.id]) {
- this.promises[status.data.id].reject(status.data.error);
- delete this.promises[status.data.id];
- } else {
- console.log('received error response for a non-existent request');
- }
- } else if (status.type === 'SerializationError') {
- console.error('SerializationError for message: ', status.data);
- } else if (status.type === 'InvalidRequest') {
- console.log('Tunnelbroker recorded InvalidRequest');
- }
- }
- } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) {
- const heartbeat: Heartbeat = {
- type: tunnelbrokerMessageTypes.HEARTBEAT,
- };
- this.ws.send(JSON.stringify(heartbeat));
- }
- };
-
- refreshOneTimeKeys: (numberOfKeys: number) => void = numberOfKeys => {
- const oldOneTimeKeysPromise = this.oneTimeKeysPromise;
- this.oneTimeKeysPromise = (async () => {
- await oldOneTimeKeysPromise;
- await uploadNewOneTimeKeys(numberOfKeys);
- })();
- };
-
- debouncedRefreshOneTimeKeys: (numberOfKeys: number) => void = _debounce(
- this.refreshOneTimeKeys,
- 100,
- { leading: true, trailing: true },
- );
-
- sendMessage: (message: TunnelbrokerClientMessageToDevice) => Promise<void> = (
- message: TunnelbrokerClientMessageToDevice,
- ) => {
- if (!this.connected) {
- throw new Error('Tunnelbroker not connected');
- }
- const clientMessageID = uuid.v4();
- const messageToDevice: MessageToDeviceRequest = {
- type: tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST,
- clientMessageID,
- deviceID: message.deviceID,
- payload: message.payload,
- };
-
- return new Promise((resolve, reject) => {
- this.promises[clientMessageID] = {
- resolve,
- reject,
- };
- this.ws.send(JSON.stringify(messageToDevice));
- });
- };
-
- stopHeartbeatTimeout() {
- if (this.heartbeatTimeoutID) {
- clearTimeout(this.heartbeatTimeoutID);
- this.heartbeatTimeoutID = null;
- }
- }
-
- resetHeartbeatTimeout() {
- this.stopHeartbeatTimeout();
- this.heartbeatTimeoutID = setTimeout(() => {
- this.ws.close();
- 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,16 +1,60 @@
// @flow
-import { clientTunnelbrokerSocketReconnectDelay } from 'lib/shared/timeouts.js';
+import invariant from 'invariant';
+import _debounce from 'lodash/debounce.js';
+import { getRustAPI } from 'rust-node-addon';
+import uuid from 'uuid';
+import WebSocket from 'ws';
+
+import { hexToUintArray } from 'lib/media/data-utils.js';
+import {
+ clientTunnelbrokerSocketReconnectDelay,
+ 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';
+import type { MessageSentStatus } from 'lib/types/tunnelbroker/message-to-device-request-status-types.js';
+import type { MessageToDeviceRequest } from 'lib/types/tunnelbroker/message-to-device-request-types.js';
+import {
+ type TunnelbrokerMessage,
+ tunnelbrokerMessageTypes,
+ 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 { peerToPeerMessageTypes } from 'lib/types/tunnelbroker/peer-to-peer-message-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 { getCommConfig } from 'lib/utils/comm-config.js';
+import {
+ convertBytesToObj,
+ convertObjToBytes,
+} from 'lib/utils/conversion-utils.js';
+import { getMessageForException } from 'lib/utils/errors.js';
import sleep from 'lib/utils/sleep.js';
-import TunnelbrokerSocket from './tunnelbroker-socket.js';
-import { type IdentityInfo } from '../user/identity.js';
-import { getContentSigningKey } from '../utils/olm-utils.js';
+import { fetchOlmAccount } from '../updaters/olm-account-updater.js';
+import { fetchIdentityInfo, saveIdentityInfo } from '../user/identity.js';
+import type { IdentityInfo } from '../user/identity.js';
+import { encrypt, decrypt } from '../utils/aes-crypto-utils.js';
+import {
+ getContentSigningKey,
+ uploadNewOneTimeKeys,
+ getNewDeviceKeyUpload,
+ markPrekeysAsPublished,
+} from '../utils/olm-utils.js';
type TBConnectionInfo = {
+url: string,
@@ -32,66 +76,398 @@
};
}
-async function createAndMaintainTunnelbrokerWebsocket(
- identityInfo: IdentityInfo,
-) {
+async function createAndMaintainTunnelbrokerWebsocket(encryptionKey: ?string) {
const [deviceID, tbConnectionInfo] = await Promise.all([
getContentSigningKey(),
getTBConnectionInfo(),
]);
+ const createNewTunnelbrokerSocket = async (
+ shouldNotifyPrimaryAfterReopening: boolean,
+ primaryDeviceID: ?string,
+ ) => {
+ const identityInfo = await fetchIdentityInfo();
+ new TunnelbrokerSocket({
+ socketURL: tbConnectionInfo.url,
+ onClose: async (successfullyAuthed: boolean, primaryID: ?string) => {
+ await sleep(clientTunnelbrokerSocketReconnectDelay);
+ await createNewTunnelbrokerSocket(successfullyAuthed, primaryID);
+ },
+ identityInfo,
+ deviceID,
+ qrAuthEncryptionKey: encryptionKey,
+ primaryDeviceID,
+ shouldNotifyPrimaryAfterReopening,
+ });
+ };
+ await createNewTunnelbrokerSocket(false, null);
+}
+
+type TunnelbrokerSocketParams = {
+ +socketURL: string,
+ +onClose: (boolean, ?string) => mixed,
+ +identityInfo: ?IdentityInfo,
+ +deviceID: string,
+ +qrAuthEncryptionKey: ?string,
+ +primaryDeviceID: ?string,
+ +shouldNotifyPrimaryAfterReopening: boolean,
+};
+
+type PromiseCallbacks = {
+ +resolve: () => void,
+ +reject: (error: string) => void,
+};
+type Promises = { [clientMessageID: string]: PromiseCallbacks };
+
+class TunnelbrokerSocket {
+ ws: WebSocket;
+ connected: boolean = false;
+ closed: boolean = false;
+ promises: Promises = {};
+ heartbeatTimeoutID: ?TimeoutID;
+ oneTimeKeysPromise: ?Promise<void>;
+ identityInfo: ?IdentityInfo;
+ qrAuthEncryptionKey: ?string;
+ primaryDeviceID: ?string;
+ shouldNotifyPrimaryAfterReopening: boolean = false;
+ shouldNotifyPrimary: boolean = false;
+
+ constructor(tunnelbrokerSocketParams: TunnelbrokerSocketParams) {
+ const {
+ socketURL,
+ onClose,
+ identityInfo,
+ deviceID,
+ qrAuthEncryptionKey,
+ primaryDeviceID,
+ shouldNotifyPrimaryAfterReopening,
+ } = tunnelbrokerSocketParams;
+
+ this.identityInfo = identityInfo;
+ this.qrAuthEncryptionKey = qrAuthEncryptionKey;
+ this.primaryDeviceID = primaryDeviceID;
+
+ if (shouldNotifyPrimaryAfterReopening) {
+ this.shouldNotifyPrimary = true;
+ }
- const initMessage: ConnectionInitializationMessage = {
- type: 'ConnectionInitializationMessage',
- deviceID: deviceID,
- accessToken: identityInfo.accessToken,
- userID: identityInfo.userId,
- deviceType: 'keyserver',
+ const socket = new WebSocket(socketURL);
+
+ socket.on('open', () => {
+ this.onOpen(socket, deviceID);
+ });
+
+ socket.on('close', async () => {
+ if (this.closed) {
+ return;
+ }
+ this.closed = true;
+ this.connected = false;
+ this.stopHeartbeatTimeout();
+ console.error('Connection to Tunnelbroker closed');
+ onClose(this.shouldNotifyPrimaryAfterReopening, this.primaryDeviceID);
+ });
+
+ socket.on('error', (error: Error) => {
+ console.error('Tunnelbroker socket error:', error.message);
+ });
+
+ socket.on('message', this.onMessage);
+
+ this.ws = socket;
+ }
+
+ onOpen: (socket: WebSocket, deviceID: string) => void = (
+ socket,
+ deviceID,
+ ) => {
+ if (this.closed) {
+ return;
+ }
+
+ if (this.identityInfo) {
+ const initMessage: ConnectionInitializationMessage = {
+ type: 'ConnectionInitializationMessage',
+ deviceID,
+ accessToken: this.identityInfo.accessToken,
+ userID: this.identityInfo.userId,
+ deviceType: 'keyserver',
+ };
+ socket.send(JSON.stringify(initMessage));
+ } else {
+ const initMessage: AnonymousInitializationMessage = {
+ type: 'AnonymousInitializationMessage',
+ deviceID,
+ deviceType: 'keyserver',
+ };
+ socket.send(JSON.stringify(initMessage));
+ }
};
- createAndMaintainTunnelbrokerWebsocketBase(tbConnectionInfo.url, initMessage);
-}
+ onMessage: (event: ArrayBuffer) => Promise<void> = async (
+ event: ArrayBuffer,
+ ) => {
+ let rawMessage;
+ try {
+ rawMessage = JSON.parse(event.toString());
+ } catch (e) {
+ console.error('error while parsing Tunnelbroker message:', e.message);
+ return;
+ }
-async function createAndMaintainAnonymousTunnelbrokerWebsocket(
- encryptionKey: string,
-) {
- const [deviceID, tbConnectionInfo] = await Promise.all([
- getContentSigningKey(),
- getTBConnectionInfo(),
- ]);
+ if (!tunnelbrokerMessageValidator.is(rawMessage)) {
+ console.error('invalid TunnelbrokerMessage: ', rawMessage.toString());
+ return;
+ }
+ const message: TunnelbrokerMessage = rawMessage;
+
+ this.resetHeartbeatTimeout();
+
+ if (
+ message.type ===
+ tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE
+ ) {
+ if (message.status.type === 'Success' && !this.connected) {
+ this.connected = true;
+ console.info(
+ this.identityInfo
+ ? 'session with Tunnelbroker created'
+ : 'anonymous session with Tunnelbroker created',
+ );
+ if (!this.shouldNotifyPrimary) {
+ return;
+ }
+ const { primaryDeviceID } = this;
+ invariant(
+ primaryDeviceID,
+ 'Primary device ID is not set but should be',
+ );
+ const payload = await this.encodeQRAuthMessage({
+ type: qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS,
+ requestBackupKeys: false,
+ });
+ if (!payload) {
+ this.closeConnection();
+ return;
+ }
+ await this.sendMessage({
+ deviceID: primaryDeviceID,
+ payload: JSON.stringify(payload),
+ });
+ } else if (message.status.type === 'Success' && this.connected) {
+ console.info(
+ 'received ConnectionInitializationResponse with status: Success for already connected socket',
+ );
+ } else {
+ this.connected = false;
+ console.error(
+ 'creating session with Tunnelbroker error:',
+ message.status.data,
+ );
+ }
+ } else if (message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE) {
+ const confirmation: MessageReceiveConfirmation = {
+ type: tunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION,
+ messageIDs: [message.messageID],
+ };
+ this.ws.send(JSON.stringify(confirmation));
+
+ const { payload } = message;
+ try {
+ const messageToKeyserver = JSON.parse(payload);
+ if (qrCodeAuthMessageValidator.is(messageToKeyserver)) {
+ const request: QRCodeAuthMessage = messageToKeyserver;
+ const [qrCodeAuthMessage, rustAPI, accountInfo] = await Promise.all([
+ this.parseQRCodeAuthMessage(request),
+ getRustAPI(),
+ fetchOlmAccount('content'),
+ ]);
+ if (
+ !qrCodeAuthMessage ||
+ qrCodeAuthMessage.type !==
+ qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS
+ ) {
+ return;
+ }
+ const { primaryDeviceID, userID } = qrCodeAuthMessage;
+ this.primaryDeviceID = primaryDeviceID;
+
+ const [nonce, deviceKeyUpload] = await Promise.all([
+ rustAPI.generateNonce(),
+ getNewDeviceKeyUpload(),
+ ]);
+ const signedIdentityKeysBlob = {
+ payload: deviceKeyUpload.keyPayload,
+ signature: deviceKeyUpload.keyPayloadSignature,
+ };
+ const nonceSignature = accountInfo.account.sign(nonce);
+
+ const identityInfo = await rustAPI.uploadSecondaryDeviceKeysAndLogIn(
+ userID,
+ nonce,
+ nonceSignature,
+ signedIdentityKeysBlob,
+ deviceKeyUpload.contentPrekey,
+ deviceKeyUpload.contentPrekeySignature,
+ deviceKeyUpload.notifPrekey,
+ deviceKeyUpload.notifPrekeySignature,
+ deviceKeyUpload.contentOneTimeKeys,
+ deviceKeyUpload.notifOneTimeKeys,
+ );
+ await Promise.all([
+ markPrekeysAsPublished(),
+ saveIdentityInfo(identityInfo),
+ ]);
+ this.shouldNotifyPrimaryAfterReopening = true;
+ this.closeConnection();
+ } else if (refreshKeysRequestValidator.is(messageToKeyserver)) {
+ const request: RefreshKeyRequest = messageToKeyserver;
+ this.debouncedRefreshOneTimeKeys(request.numberOfKeys);
+ }
+ } catch (e) {
+ console.error(
+ 'error while processing message to keyserver:',
+ e.message,
+ );
+ }
+ } else if (
+ message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS
+ ) {
+ for (const status: MessageSentStatus of message.clientMessageIDs) {
+ if (status.type === 'Success') {
+ if (this.promises[status.data]) {
+ this.promises[status.data].resolve();
+ delete this.promises[status.data];
+ } else {
+ console.log(
+ 'received successful response for a non-existent request',
+ );
+ }
+ } else if (status.type === 'Error') {
+ if (this.promises[status.data.id]) {
+ this.promises[status.data.id].reject(status.data.error);
+ delete this.promises[status.data.id];
+ } else {
+ console.log('received error response for a non-existent request');
+ }
+ } else if (status.type === 'SerializationError') {
+ console.error('SerializationError for message: ', status.data);
+ } else if (status.type === 'InvalidRequest') {
+ console.log('Tunnelbroker recorded InvalidRequest');
+ }
+ }
+ } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) {
+ const heartbeat: Heartbeat = {
+ type: tunnelbrokerMessageTypes.HEARTBEAT,
+ };
+ this.ws.send(JSON.stringify(heartbeat));
+ }
+ };
- const initMessage: AnonymousInitializationMessage = {
- type: 'AnonymousInitializationMessage',
- deviceID: deviceID,
- deviceType: 'keyserver',
+ refreshOneTimeKeys: (numberOfKeys: number) => void = numberOfKeys => {
+ const oldOneTimeKeysPromise = this.oneTimeKeysPromise;
+ this.oneTimeKeysPromise = (async () => {
+ await oldOneTimeKeysPromise;
+ await uploadNewOneTimeKeys(numberOfKeys);
+ })();
};
- createAndMaintainTunnelbrokerWebsocketBase(
- tbConnectionInfo.url,
- initMessage,
- encryptionKey,
+ debouncedRefreshOneTimeKeys: (numberOfKeys: number) => void = _debounce(
+ this.refreshOneTimeKeys,
+ 100,
+ { leading: true, trailing: true },
);
-}
-function createAndMaintainTunnelbrokerWebsocketBase(
- url: string,
- initMessage: ConnectionInitializationMessage | AnonymousInitializationMessage,
- encryptionKey?: string,
-) {
- const createNewTunnelbrokerSocket = () => {
- new TunnelbrokerSocket(
- url,
- initMessage,
- async () => {
- await sleep(clientTunnelbrokerSocketReconnectDelay);
- createNewTunnelbrokerSocket();
- },
- encryptionKey,
+ sendMessage: (message: TunnelbrokerClientMessageToDevice) => Promise<void> = (
+ message: TunnelbrokerClientMessageToDevice,
+ ) => {
+ if (!this.connected) {
+ throw new Error('Tunnelbroker not connected');
+ }
+ const clientMessageID = uuid.v4();
+ const messageToDevice: MessageToDeviceRequest = {
+ type: tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST,
+ clientMessageID,
+ deviceID: message.deviceID,
+ payload: message.payload,
+ };
+
+ return new Promise((resolve, reject) => {
+ this.promises[clientMessageID] = {
+ resolve,
+ reject,
+ };
+ this.ws.send(JSON.stringify(messageToDevice));
+ });
+ };
+
+ stopHeartbeatTimeout() {
+ if (this.heartbeatTimeoutID) {
+ clearTimeout(this.heartbeatTimeoutID);
+ this.heartbeatTimeoutID = null;
+ }
+ }
+
+ resetHeartbeatTimeout() {
+ this.stopHeartbeatTimeout();
+ this.heartbeatTimeoutID = setTimeout(() => {
+ this.ws.close();
+ this.connected = false;
+ }, tunnelbrokerHeartbeatTimeout);
+ }
+
+ closeConnection() {
+ this.ws.close();
+ this.connected = false;
+ }
+
+ 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;
+ };
+
+ encodeQRAuthMessage: (
+ payload: QRCodeAuthMessagePayload,
+ ) => Promise<?QRCodeAuthMessage> = async payload => {
+ const encryptionKey = this.qrAuthEncryptionKey;
+ if (!encryptionKey) {
+ console.error('Encryption key missing - cannot send QR auth message.');
+ return null;
+ }
+
+ let encryptedContent;
+ try {
+ const payloadBytes = convertObjToBytes(payload);
+ const keyBytes = hexToUintArray(encryptionKey);
+ const encryptedBytes = await encrypt(keyBytes, payloadBytes);
+ encryptedContent = Buffer.from(encryptedBytes).toString('base64');
+ } catch (e) {
+ console.error(
+ 'Error encoding QRCodeAuthMessagePayload:',
+ getMessageForException(e),
+ );
+ return null;
+ }
+
+ return {
+ type: peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE,
+ encryptedContent,
+ };
};
- createNewTunnelbrokerSocket();
}
-export {
- createAndMaintainTunnelbrokerWebsocket,
- createAndMaintainAnonymousTunnelbrokerWebsocket,
-};
+export { createAndMaintainTunnelbrokerWebsocket };
diff --git a/keyserver/src/utils/olm-utils.js b/keyserver/src/utils/olm-utils.js
--- a/keyserver/src/utils/olm-utils.js
+++ b/keyserver/src/utils/olm-utils.js
@@ -103,6 +103,17 @@
return cachedOLMUtility;
}
+async function markPrekeysAsPublished(): Promise<void> {
+ await Promise.all([
+ fetchCallUpdateOlmAccount('content', (contentAccount: OlmAccount) => {
+ contentAccount.mark_prekey_as_published();
+ }),
+ fetchCallUpdateOlmAccount('notifications', (notifAccount: OlmAccount) => {
+ notifAccount.mark_prekey_as_published();
+ }),
+ ]);
+}
+
async function getNewDeviceKeyUpload(): Promise<IdentityNewDeviceKeyUpload> {
let contentIdentityKeys: string;
let contentOneTimeKeys: $ReadOnlyArray<string>;
@@ -324,4 +335,5 @@
validateAndUploadAccountPrekeys,
publishPrekeysToIdentity,
getNewDeviceKeyUpload,
+ markPrekeysAsPublished,
};

File Metadata

Mime Type
text/plain
Expires
Fri, Nov 22, 2:02 AM (5 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2559356
Default Alt Text
D12213.id41763.diff (29 KB)

Event Timeline