Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3332830
D12213.id41763.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
29 KB
Referenced Files
None
Subscribers
None
D12213.id41763.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D12213: [keyserver] finish secondary login via qr code
Attached
Detach File
Event Timeline
Log In to Comment