mirror of
https://github.com/signalapp/libsignal.git
synced 2026-04-26 01:35:22 +02:00
266 lines
8.4 KiB
TypeScript
266 lines
8.4 KiB
TypeScript
//
|
|
// Copyright 2025 Signal Messenger, LLC.
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import { assert, config, expect, use } from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import { Buffer } from 'node:buffer';
|
|
|
|
import * as Native from '../../Native.js';
|
|
import * as util from '../util.js';
|
|
import { TokioAsyncContext, UnauthUsernamesService } from '../../net.js';
|
|
import { connectUnauth } from './ServiceTestUtils.js';
|
|
import { ErrorCode, LibSignalErrorBase } from '../../Errors.js';
|
|
import { Aci } from '../../Address.js';
|
|
import * as uuid from '../../uuid.js';
|
|
|
|
use(chaiAsPromised);
|
|
|
|
util.initLogger();
|
|
config.truncateThreshold = 0;
|
|
|
|
describe('UnauthUsernamesService', () => {
|
|
describe('lookUpUsernameHash', () => {
|
|
it('can look up hashes', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const hash = Uint8Array.of(1, 2, 3, 4);
|
|
const responseFuture = chat.lookUpUsernameHash({ hash });
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
`/v1/accounts/username_hash/${Buffer.from(hash).toString('base64url')}`
|
|
);
|
|
|
|
const aci = '4fcfe887-a600-40cd-9ab7-fd2a695e9981';
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 200,
|
|
message: 'OK',
|
|
headers: ['content-type: application/json'],
|
|
body: Buffer.from(
|
|
JSON.stringify({
|
|
uuid: aci,
|
|
})
|
|
),
|
|
});
|
|
|
|
const responseFromServer = await responseFuture;
|
|
assert(responseFromServer !== null);
|
|
assert(Aci.fromUuid(aci).isEqual(responseFromServer));
|
|
});
|
|
|
|
it('can look up unknown hashes', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const hash = Uint8Array.of(1, 2, 3, 4);
|
|
const responseFuture = chat.lookUpUsernameHash({ hash });
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
`/v1/accounts/username_hash/${Buffer.from(hash).toString('base64url')}`
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 404,
|
|
message: 'Not Found',
|
|
});
|
|
|
|
const responseFromServer = await responseFuture;
|
|
assert.isNull(responseFromServer);
|
|
});
|
|
|
|
it('can handle server errors', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const hash = Uint8Array.of(1, 2, 3, 4);
|
|
const responseFuture = chat.lookUpUsernameHash({ hash });
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
`/v1/accounts/username_hash/${Buffer.from(hash).toString('base64url')}`
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 500,
|
|
message: 'Internal Server Error',
|
|
});
|
|
|
|
await expect(responseFuture)
|
|
.to.eventually.be.rejectedWith(LibSignalErrorBase)
|
|
.and.deep.include({
|
|
code: ErrorCode.IoError,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('lookUpUsernameLink', () => {
|
|
const EXPECTED_USERNAME = 'moxie.01';
|
|
const ENCRYPTED_USERNAME =
|
|
'kj5ah-VbEgjpfJsNt-Wto2H626DRmJSVpYPy0yPOXA8kiSFkBCD8ysFlJ-Z3MhiAnt_R3Nm7ZY0W5fiRDLVbhaE2z-KO2xdf5NcVbkewCzhvveecS3hHskDp1aSfbvwTZNNGPmAuKWvJ1MPdHzsF0w';
|
|
const ENCRYPTED_USERNAME_ENTROPY = Buffer.from(
|
|
'4302c613c092a51c5394becffeb6f697300a605348e93f03c3db95e0b03d28f1',
|
|
'hex'
|
|
);
|
|
|
|
it('can look up links', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: uuid.NIL,
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY,
|
|
});
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
'/v1/accounts/username_link/00000000-0000-0000-0000-000000000000'
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 200,
|
|
message: 'OK',
|
|
headers: ['content-type: application/json'],
|
|
body: Buffer.from(
|
|
JSON.stringify({
|
|
usernameLinkEncryptedValue: ENCRYPTED_USERNAME,
|
|
})
|
|
),
|
|
});
|
|
|
|
const responseFromServer = await responseFuture;
|
|
assert.isNotNull(responseFromServer);
|
|
assert.equal(responseFromServer.username, EXPECTED_USERNAME);
|
|
assert.isNotEmpty(responseFromServer.hash);
|
|
});
|
|
|
|
it('can look up unknown links', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: uuid.NIL,
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY,
|
|
});
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
'/v1/accounts/username_link/00000000-0000-0000-0000-000000000000'
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 404,
|
|
message: 'Not Found',
|
|
});
|
|
|
|
const responseFromServer = await responseFuture;
|
|
assert.isNull(responseFromServer);
|
|
});
|
|
|
|
it('can handle garbage ciphertexts', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: uuid.NIL,
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY,
|
|
});
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
'/v1/accounts/username_link/00000000-0000-0000-0000-000000000000'
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 200,
|
|
message: 'OK',
|
|
headers: ['content-type: application/json'],
|
|
body: Buffer.from(
|
|
JSON.stringify({
|
|
usernameLinkEncryptedValue: `${ENCRYPTED_USERNAME}A`,
|
|
})
|
|
),
|
|
});
|
|
|
|
await expect(responseFuture)
|
|
.to.eventually.be.rejectedWith(LibSignalErrorBase)
|
|
.and.deep.include({
|
|
code: ErrorCode.InvalidUsernameLinkEncryptedData,
|
|
});
|
|
});
|
|
|
|
it('can handle server errors', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: uuid.NIL,
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY,
|
|
});
|
|
|
|
const request = await fakeRemote.assertReceiveIncomingRequest();
|
|
assert(request !== null);
|
|
|
|
expect(request.verb).to.eq('GET');
|
|
expect(request.path).to.eq(
|
|
'/v1/accounts/username_link/00000000-0000-0000-0000-000000000000'
|
|
);
|
|
|
|
fakeRemote.sendReplyTo(request, {
|
|
status: 500,
|
|
message: 'Internal Server Error',
|
|
});
|
|
|
|
await expect(responseFuture)
|
|
.to.eventually.be.rejectedWith(LibSignalErrorBase)
|
|
.and.deep.include({
|
|
code: ErrorCode.IoError,
|
|
});
|
|
});
|
|
|
|
it('can handle bad UUIDs', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, _fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: 'not',
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY,
|
|
});
|
|
|
|
await expect(responseFuture).to.eventually.be.rejectedWith(TypeError);
|
|
});
|
|
|
|
it('can handle bad entropy', async () => {
|
|
const tokio = new TokioAsyncContext(Native.TokioAsyncContext_new());
|
|
const [chat, _fakeRemote] = connectUnauth<UnauthUsernamesService>(tokio);
|
|
|
|
const responseFuture = chat.lookUpUsernameLink({
|
|
uuid: uuid.NIL,
|
|
entropy: ENCRYPTED_USERNAME_ENTROPY.subarray(1),
|
|
});
|
|
|
|
await expect(responseFuture)
|
|
.to.eventually.be.rejectedWith(LibSignalErrorBase)
|
|
.and.deep.include({
|
|
code: ErrorCode.InvalidEntropyDataLength,
|
|
});
|
|
});
|
|
});
|
|
});
|