mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-03-03 05:55:03 +01:00
329 lines
12 KiB
TypeScript
329 lines
12 KiB
TypeScript
import http from 'http';
|
|
import path from 'path';
|
|
import WebSocket from 'ws';
|
|
|
|
import { TorController, createInterceptor } from '../src';
|
|
import { torRunner } from './torRunner';
|
|
import { TorIdentities } from '../src/torIdentities';
|
|
import { InterceptorOptions } from '../src/types';
|
|
|
|
const hostIp = '127.0.0.1';
|
|
const port = 38835;
|
|
const controlPort = 35527;
|
|
const processId = process.pid;
|
|
|
|
// 1 minute before timeout, because Tor might be slow to start.
|
|
jest.setTimeout(60000);
|
|
|
|
// Because tmp/control_auth_cookie is shared by other tests, this test should not run in parallel
|
|
// using `--runInBand` option with jest.
|
|
const torDataDir = path.join(__dirname, 'tmp');
|
|
const ipRegex = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
|
|
|
|
const testGetUrlHttp = 'http://check.torproject.org/';
|
|
const testGetUrlHttps = 'https://check.torproject.org/';
|
|
const testPostUrlHttps = 'https://httpbin.org/post';
|
|
|
|
describe('Interceptor', () => {
|
|
let torProcess: ReturnType<typeof torRunner> | null;
|
|
let torController: TorController;
|
|
let torIdentities: TorIdentities;
|
|
|
|
const torSettings = { running: true, host: hostIp, port };
|
|
|
|
const interceptorOptions: InterceptorOptions = {
|
|
getWhitelistedDomains: () => [
|
|
'check.torproject.org',
|
|
'httpbin.org',
|
|
'tbtc1.trezor.io',
|
|
'localhost',
|
|
'127.0.0.1',
|
|
],
|
|
handler: () => {},
|
|
getTorSettings: () => torSettings,
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
// Callback in createInterceptor should return true in order for the request to use Tor.
|
|
torIdentities = createInterceptor(interceptorOptions).torIdentities;
|
|
// Starting Tor controller to make sure that Tor is running.
|
|
torController = new TorController({
|
|
host: hostIp,
|
|
port,
|
|
controlPort,
|
|
torDataDir,
|
|
});
|
|
const torParams = torController.getTorConfiguration(processId);
|
|
// Starting Tor process from binary.
|
|
torProcess = torRunner({
|
|
torParams,
|
|
});
|
|
|
|
// Waiting for Tor to be ready to accept successful connections.
|
|
await torController.waitUntilAlive();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (torProcess) {
|
|
await torProcess.kill();
|
|
torProcess = null;
|
|
}
|
|
});
|
|
|
|
describe('GET method', () => {
|
|
it('HTTP - Each identity has different ip address', async () => {
|
|
const identityDefault = await fetch(testGetUrlHttp, {
|
|
headers: { 'Proxy-Authorization': 'Basic default' },
|
|
});
|
|
const identityDefault2 = await fetch(testGetUrlHttp, {
|
|
headers: { 'Proxy-Authorization': 'Basic user' },
|
|
});
|
|
const iPIdentitieA = ((await identityDefault.text()) as any).match(ipRegex)[0];
|
|
const iPIdentitieB = ((await identityDefault2.text()) as any).match(ipRegex)[0];
|
|
expect(iPIdentitieA).not.toEqual(iPIdentitieB);
|
|
});
|
|
|
|
it('HTTPS - Each identity has different ip address', async () => {
|
|
const identityA = await fetch(testGetUrlHttps, {
|
|
headers: { 'Proxy-Authorization': 'Basic default' },
|
|
});
|
|
const identityB = await fetch(testGetUrlHttps, {
|
|
headers: { 'Proxy-Authorization': 'Basic user' },
|
|
});
|
|
// Parsing IP address from html provided by check.torproject.org.
|
|
const iPIdentitieA = ((await identityA.text()) as any).match(ipRegex)[0];
|
|
const iPIdentitieB = ((await identityB.text()) as any).match(ipRegex)[0];
|
|
// Check if identities are different when using different identity.
|
|
expect(iPIdentitieA).not.toEqual(iPIdentitieB);
|
|
|
|
// reset existing circuit using identity with password
|
|
const identityB2 = await fetch(testGetUrlHttps, {
|
|
headers: { 'Proxy-Authorization': 'Basic user:password' },
|
|
});
|
|
const iPIdentitieB2 = ((await identityB2.text()) as any).match(ipRegex)[0];
|
|
// ip for "user" did change
|
|
expect(iPIdentitieB2).not.toEqual(iPIdentitieB);
|
|
});
|
|
});
|
|
|
|
describe('POST method', () => {
|
|
it('HTTPS - Each identity has different ip address', async () => {
|
|
const identityA = await fetch(testPostUrlHttps, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'test' }),
|
|
headers: { 'Proxy-Authorization': 'Basic default' },
|
|
});
|
|
const identityB = await fetch(testPostUrlHttps, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'test' }),
|
|
headers: { 'Proxy-Authorization': 'Basic user' },
|
|
});
|
|
|
|
const iPIdentitieA = ((await identityA.json()) as any).origin;
|
|
const iPIdentitieB = ((await identityB.json()) as any).origin;
|
|
|
|
// Check if identities are different when using different identity.
|
|
expect(iPIdentitieA).not.toEqual(iPIdentitieB);
|
|
});
|
|
});
|
|
|
|
describe('WebSocket', () => {
|
|
const createWebSocket = (options: any = {}) =>
|
|
new Promise<void>((resolve, reject) => {
|
|
const ws = new WebSocket('wss://tbtc1.trezor.io/websocket', {
|
|
headers: {
|
|
'User-Agent': 'Trezor Suite',
|
|
...options,
|
|
},
|
|
});
|
|
ws.on('open', () => {
|
|
ws.close();
|
|
resolve();
|
|
});
|
|
ws.on('error', reject);
|
|
});
|
|
|
|
it('WebSocket - Each connection creates new identity', async () => {
|
|
await createWebSocket();
|
|
await createWebSocket();
|
|
|
|
const identities = Object.keys((torIdentities as any).identities).filter(name =>
|
|
name.includes('WebSocket'),
|
|
);
|
|
|
|
expect(identities.length).toBe(2);
|
|
});
|
|
|
|
it('WebSocket - Using Proxy-Authorization header', async () => {
|
|
await createWebSocket({
|
|
'Proxy-Authorization': 'Basic WebSocket-Identity',
|
|
});
|
|
await createWebSocket({
|
|
'Proxy-Authorization': 'Basic WebSocket-Identity',
|
|
});
|
|
|
|
const identities = Object.keys((torIdentities as any).identities).filter(name =>
|
|
name.includes('WebSocket-Identity'),
|
|
);
|
|
|
|
expect(identities.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
// TODO: Skipping this for now, since I want to get the most critical tests to run in CI.
|
|
describe.skip('TorControl', () => {
|
|
it('closing circuits', async () => {
|
|
await fetch(testGetUrlHttps, {
|
|
headers: { 'Proxy-Authorization': 'Basic user-circuit-1' },
|
|
});
|
|
|
|
await fetch(testGetUrlHttps, {
|
|
headers: { 'Proxy-Authorization': 'Basic user-circuit-2' },
|
|
});
|
|
|
|
const circuits1 = await torController.controlPort.getCircuits();
|
|
// there should be at least 2 circuits
|
|
expect(circuits1.length).toBeGreaterThanOrEqual(2);
|
|
// there should be circuits with requested username
|
|
expect(circuits1.map(c => c.username)).toEqual(
|
|
expect.arrayContaining(['user-circuit-1', 'user-circuit-2']),
|
|
);
|
|
|
|
// close specific circuit
|
|
await torController.controlPort.closeCircuit('user-circuit-1');
|
|
|
|
// and validate state afterward
|
|
const circuits2 = await torController.controlPort.getCircuits();
|
|
expect(circuits2.map(c => c.username)).not.toEqual(
|
|
expect.arrayContaining(['user-circuit-1']),
|
|
);
|
|
expect(circuits2.map(c => c.username)).toEqual(
|
|
expect.arrayContaining(['user-circuit-2']),
|
|
);
|
|
|
|
// close remaining circuits
|
|
await torController.controlPort.closeActiveCircuits();
|
|
|
|
// and validate state afterward
|
|
const circuits3 = await torController.controlPort.getCircuits();
|
|
expect(circuits3.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('Allowed-Headers', () => {
|
|
// create simple http server and respond with received headers
|
|
const createHttpServer = () =>
|
|
new Promise<{ server: http.Server; serverUrl: string; host: string }>(
|
|
(resolve, reject) => {
|
|
const server = http.createServer((request, response) => {
|
|
response.setHeader('Content-Type', 'application/json');
|
|
response.write(JSON.stringify(request.headers));
|
|
response.end();
|
|
});
|
|
server.unref();
|
|
server.on('error', reject);
|
|
server.listen(0, () => {
|
|
const addr = server.address() as any; // as net.AddressInfo
|
|
resolve({
|
|
server,
|
|
serverUrl: `http://localhost:${addr.port}`,
|
|
host: `localhost:${addr.port}`,
|
|
});
|
|
});
|
|
},
|
|
);
|
|
|
|
let serverInit: Awaited<ReturnType<typeof createHttpServer>>;
|
|
beforeAll(async () => {
|
|
serverInit = await createHttpServer();
|
|
});
|
|
|
|
afterAll(() => new Promise(resolve => serverInit.server.close(resolve)));
|
|
|
|
const fetchHeaders = (url: string, options: RequestInit) =>
|
|
fetch(url, options).then(r => r.json());
|
|
|
|
it('POST request headers', async () => {
|
|
const { serverUrl, host } = serverInit;
|
|
|
|
// default headers
|
|
await expect(
|
|
fetchHeaders(serverUrl, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'test' }),
|
|
headers: { 'User-Agent': 'TrezorSuite' },
|
|
}),
|
|
).resolves.toEqual({
|
|
host,
|
|
accept: '*/*',
|
|
'accept-encoding': 'gzip,deflate',
|
|
connection: 'keep-alive',
|
|
'content-length': '15',
|
|
'content-type': 'text/plain;charset=UTF-8',
|
|
'user-agent': 'TrezorSuite',
|
|
});
|
|
|
|
// restricted headers
|
|
await expect(
|
|
fetchHeaders(serverUrl, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'test' }),
|
|
headers: {
|
|
'User-Agent': 'TrezorSuite',
|
|
'Allowed-Headers': 'AcCePt-EnCoDiNg;content-type;Content-Length;HOST', // case insensitive
|
|
},
|
|
}),
|
|
).resolves.toEqual({
|
|
host,
|
|
'accept-encoding': 'gzip,deflate',
|
|
'content-length': '15',
|
|
'content-type': 'text/plain;charset=UTF-8',
|
|
});
|
|
});
|
|
|
|
it('GET request headers', async () => {
|
|
const { serverUrl, host } = serverInit;
|
|
|
|
// default headers
|
|
await expect(
|
|
fetchHeaders(serverUrl, {
|
|
method: 'GET',
|
|
headers: { 'User-Agent': 'TrezorSuite' },
|
|
}),
|
|
).resolves.toEqual({
|
|
host,
|
|
accept: '*/*',
|
|
'accept-encoding': 'gzip,deflate',
|
|
connection: 'keep-alive',
|
|
'user-agent': 'TrezorSuite',
|
|
});
|
|
|
|
// restricted headers
|
|
await expect(
|
|
fetchHeaders(serverUrl, {
|
|
method: 'GET',
|
|
headers: {
|
|
'User-Agent': 'TrezorSuite',
|
|
'Allowed-Headers': 'Accept-Encoding;Content-Type;Content-Length;Host',
|
|
},
|
|
}),
|
|
).resolves.toEqual({
|
|
host,
|
|
'accept-encoding': 'gzip,deflate',
|
|
});
|
|
});
|
|
});
|
|
|
|
it('Block unauthorized requests', async () => {
|
|
torSettings.running = false;
|
|
|
|
await expect(
|
|
fetch(testPostUrlHttps, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ test: 'test' }),
|
|
headers: { 'Proxy-Authorization': 'Basic default' },
|
|
}),
|
|
).rejects.toThrow('Blocked request with Proxy-Authorization');
|
|
});
|
|
});
|