Additional protocol changes for CDS v2

This commit is contained in:
Fedor Indutny 2021-11-12 21:45:30 +01:00 committed by GitHub
parent b35d330c0a
commit bb15cfc622
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 20 deletions

View file

@ -91,6 +91,13 @@ export function createKeyPair(incomingKey: Uint8Array): KeyPairType {
};
}
export function prefixPublicKey(pubKey: Uint8Array): Uint8Array {
return Bytes.concatenate([
new Uint8Array([0x05]),
validatePubKeyFormat(pubKey),
]);
}
export function calculateAgreement(
pubKey: Uint8Array,
privKey: Uint8Array
@ -98,9 +105,7 @@ export function calculateAgreement(
const privKeyBuffer = Buffer.from(privKey);
const pubKeyObj = client.PublicKey.deserialize(
Buffer.from(
Bytes.concatenate([new Uint8Array([0x05]), validatePubKeyFormat(pubKey)])
)
Buffer.from(prefixPublicKey(pubKey))
);
const privKeyObj = client.PrivateKey.deserialize(privKeyBuffer);
const sharedSecret = privKeyObj.agree(pubKeyObj);

View file

@ -20,8 +20,22 @@ enum State {
Closed,
}
export type CDSRequestOptionsType = Readonly<{
e164s: ReadonlyArray<string>;
auth: CDSAuthType;
timeout?: number;
}>;
export type CDSAuthType = Readonly<{
username: string;
password: string;
}>;
const HANDSHAKE_TIMEOUT = 10 * durations.SECOND;
const REQUEST_TIMEOUT = 10 * durations.SECOND;
const VERSION = new Uint8Array([0x01]);
const USERNAME_LENGTH = 32;
const PASSWORD_LENGTH = 31;
export class CDSSocket extends EventEmitter {
private state = State.Handshake;
@ -82,19 +96,30 @@ export class CDSSocket extends EventEmitter {
public async request({
e164s,
auth,
timeout = REQUEST_TIMEOUT,
}: {
e164s: ReadonlyArray<string>;
timeout?: number;
}): Promise<ReadonlyArray<UUIDStringType | null>> {
}: CDSRequestOptionsType): Promise<ReadonlyArray<UUIDStringType | null>> {
await this.finishedHandshake;
strictAssert(
this.state === State.Established,
'Connection not established'
);
const username = Bytes.fromString(auth.username);
const password = Bytes.fromString(auth.password);
strictAssert(
username.length === USERNAME_LENGTH,
'Invalid username length'
);
strictAssert(
password.length === PASSWORD_LENGTH,
'Invalid password length'
);
const request = Bytes.concatenate([
new Uint8Array([0x01]),
VERSION,
username,
password,
...e164s.map(e164 => {
// Long.fromString handles numbers with or without a leading '+'
return new Uint8Array(Long.fromString(e164).toBytesBE());

View file

@ -6,10 +6,12 @@ import { HsmEnclaveClient, PublicKey } from '@signalapp/signal-client';
import type { connection as WebSocket } from 'websocket';
import * as Bytes from '../Bytes';
import { prefixPublicKey } from '../Curve';
import type { AbortableProcess } from '../util/AbortableProcess';
import * as log from '../logging/log';
import type { UUIDStringType } from '../types/UUID';
import { CDSSocket } from './CDSSocket';
import type { CDSRequestOptionsType } from './CDSSocket';
import { connect as connectWebSocket } from './WebSocket';
export type CDSSocketManagerOptionsType = Readonly<{
@ -30,7 +32,7 @@ export class CDSSocketManager {
constructor(private readonly options: CDSSocketManagerOptionsType) {
this.publicKey = PublicKey.deserialize(
Buffer.from(Bytes.fromHex(options.publicKey))
Buffer.from(prefixPublicKey(Bytes.fromHex(options.publicKey)))
);
this.codeHash = Buffer.from(Bytes.fromHex(options.codeHash));
if (options.proxyUrl) {
@ -38,19 +40,15 @@ export class CDSSocketManager {
}
}
public async request({
e164s,
timeout,
}: {
e164s: ReadonlyArray<string>;
timeout?: number;
}): Promise<ReadonlyArray<UUIDStringType | null>> {
public async request(
options: CDSRequestOptionsType
): Promise<ReadonlyArray<UUIDStringType | null>> {
log.info('CDSSocketManager: connecting socket');
const socket = await this.connect().getResult();
log.info('CDSSocketManager: connected socket');
try {
return await socket.request({ e164s, timeout });
return await socket.request(options);
} finally {
log.info('CDSSocketManager: closing socket');
socket.close(3000, 'Normal');

View file

@ -504,6 +504,7 @@ const URL_CALLS = {
deliveryCert: 'v1/certificate/delivery',
devices: 'v1/devices',
directoryAuth: 'v1/directory/auth',
directoryAuthV2: 'v2/directory/auth',
discovery: 'v1/discovery',
getGroupAvatarUpload: 'v1/groups/avatar/form',
getGroupCredentials: 'v1/certificate/group',
@ -556,6 +557,7 @@ const WEBSOCKET_CALLS = new Set<keyof typeof URL_CALLS>([
// Directory
'directoryAuth',
'directoryAuthV2',
// Storage
'storageToken',
@ -2475,6 +2477,17 @@ export function initialize({
})) as { username: string; password: string };
}
async function getDirectoryAuthV2(): Promise<{
username: string;
password: string;
}> {
return (await _ajax({
call: 'directoryAuthV2',
httpType: 'GET',
responseType: 'json',
})) as { username: string; password: string };
}
function validateAttestationQuote({
serverStaticPublic,
quote: quoteBytes,
@ -2864,7 +2877,12 @@ export function initialize({
async function getUuidsForE164sV2(
e164s: ReadonlyArray<string>
): Promise<Dictionary<UUIDStringType | null>> {
const uuids = await cdsSocketManager.request({ e164s });
const auth = await getDirectoryAuthV2();
const uuids = await cdsSocketManager.request({
auth,
e164s,
});
return zipObject(e164s, uuids);
}