Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F3372383
D12146.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
D12146.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,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
Details
Attached
Mime Type
text/plain
Expires
Wed, Nov 27, 6:24 AM (21 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2589140
Default Alt Text
D12146.diff (13 KB)
Attached To
Mode
D12146: [keyserver][lib][web] set up anonymous connection with tunnelbroker on keyserver and handle DEVICE_LIST_UPDATE_SUCCESS
Attached
Detach File
Event Timeline
Log In to Comment