signal-desktop/ts/textsecure/CDSSocketManager.ts

110 lines
3.1 KiB
TypeScript
Raw Normal View History

2021-11-08 23:32:31 +00:00
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import ProxyAgent from 'proxy-agent';
import { HsmEnclaveClient } from '@signalapp/libsignal-client';
2021-11-08 23:32:31 +00:00
import type { connection as WebSocket } from 'websocket';
import * as Bytes from '../Bytes';
import type { AbortableProcess } from '../util/AbortableProcess';
2021-12-06 22:54:20 +00:00
import * as durations from '../util/durations';
2022-03-09 19:28:40 +00:00
import { getBasicAuth } from '../util/getBasicAuth';
2021-12-06 22:54:20 +00:00
import { sleep } from '../util/sleep';
2021-11-08 23:32:31 +00:00
import * as log from '../logging/log';
import { CDSSocket } from './CDSSocket';
2021-12-06 22:54:20 +00:00
import type {
2022-03-09 19:28:40 +00:00
CDSAuthType,
2021-12-06 22:54:20 +00:00
CDSRequestOptionsType,
CDSSocketDictionaryType,
} from './CDSSocket';
2021-11-08 23:32:31 +00:00
import { connect as connectWebSocket } from './WebSocket';
export type CDSSocketManagerOptionsType = Readonly<{
url: string;
publicKey: string;
2022-03-09 19:28:40 +00:00
codeHashes: ReadonlyArray<string>;
2021-11-08 23:32:31 +00:00
certificateAuthority: string;
proxyUrl?: string;
version: string;
}>;
2021-12-06 22:54:20 +00:00
export type CDSResponseType = CDSSocketDictionaryType;
2021-11-08 23:32:31 +00:00
export class CDSSocketManager {
private readonly publicKey: Buffer;
2021-11-08 23:32:31 +00:00
2022-03-09 19:28:40 +00:00
private readonly codeHashes: Array<Buffer>;
2021-11-08 23:32:31 +00:00
private readonly proxyAgent?: ReturnType<typeof ProxyAgent>;
2021-12-06 22:54:20 +00:00
private retryAfter?: number;
2021-11-08 23:32:31 +00:00
constructor(private readonly options: CDSSocketManagerOptionsType) {
this.publicKey = Buffer.from(Bytes.fromHex(options.publicKey));
2022-03-09 19:28:40 +00:00
this.codeHashes = options.codeHashes.map(hash =>
Buffer.from(Bytes.fromHex(hash))
);
2021-11-08 23:32:31 +00:00
if (options.proxyUrl) {
this.proxyAgent = new ProxyAgent(options.proxyUrl);
}
}
2021-11-12 20:45:30 +00:00
public async request(
options: CDSRequestOptionsType
2021-12-06 22:54:20 +00:00
): Promise<CDSResponseType> {
if (this.retryAfter !== undefined) {
const delay = Math.max(0, this.retryAfter - Date.now());
log.info(`CDSSocketManager: waiting ${delay}ms before retrying`);
await sleep(delay);
}
2022-03-09 19:28:40 +00:00
const { auth } = options;
2021-11-08 23:32:31 +00:00
log.info('CDSSocketManager: connecting socket');
2022-03-09 19:28:40 +00:00
const socket = await this.connect(auth).getResult();
2021-11-08 23:32:31 +00:00
log.info('CDSSocketManager: connected socket');
try {
2021-12-06 22:54:20 +00:00
const { dictionary, retryAfterSecs = 0 } = await socket.request(options);
if (retryAfterSecs > 0) {
this.retryAfter = Math.max(
this.retryAfter ?? Date.now(),
Date.now() + retryAfterSecs * durations.SECOND
);
}
return dictionary;
2021-11-08 23:32:31 +00:00
} finally {
log.info('CDSSocketManager: closing socket');
socket.close(3000, 'Normal');
}
}
2022-03-09 19:28:40 +00:00
private connect(auth: CDSAuthType): AbortableProcess<CDSSocket> {
const enclaveClient = HsmEnclaveClient.new(this.publicKey, this.codeHashes);
2021-11-08 23:32:31 +00:00
2022-03-09 19:28:40 +00:00
const { publicKey: publicKeyHex, codeHashes, version } = this.options;
2021-11-08 23:32:31 +00:00
2022-03-09 19:28:40 +00:00
const url = `${
this.options.url
}/discovery/${publicKeyHex}/${codeHashes.join(',')}`;
2021-11-08 23:32:31 +00:00
return connectWebSocket<CDSSocket>({
name: 'CDSSocket',
2021-11-08 23:32:31 +00:00
url,
version,
proxyAgent: this.proxyAgent,
certificateAuthority: this.options.certificateAuthority,
2022-03-09 19:28:40 +00:00
extraHeaders: {
authorization: getBasicAuth(auth),
},
2021-11-08 23:32:31 +00:00
createResource: (socket: WebSocket): CDSSocket => {
return new CDSSocket(socket, enclaveClient);
},
});
}
}