Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/mitm-socket/test/proxy.test.ts
1030 views
1
import { Helpers } from '@secret-agent/testing';
2
import * as Proxy from 'proxy';
3
import * as http from 'http';
4
import {
5
getTlsConnection,
6
httpGetWithSocket,
7
readableToBuffer,
8
} from '@secret-agent/testing/helpers';
9
import * as WebSocket from 'ws';
10
import * as socks5 from 'simple-socks';
11
import { createPromise } from '@secret-agent/commons/utils';
12
import * as http2 from 'http2';
13
import MitmSocket from '../index';
14
import MitmSocketSession from '../lib/MitmSocketSession';
15
16
afterAll(Helpers.afterAll);
17
afterEach(Helpers.afterEach);
18
19
let sessionId = 0;
20
21
let mitmSocketSession: MitmSocketSession;
22
beforeAll(() => {
23
mitmSocketSession = new MitmSocketSession('proxy.test', {
24
clientHelloId: 'chrome-83',
25
rejectUnauthorized: false,
26
});
27
Helpers.onClose(() => mitmSocketSession.close(), true);
28
});
29
30
test('should be able to send a request through a proxy', async () => {
31
const htmlString = 'Proxy proxy echo echo';
32
const proxy = await startProxy();
33
const proxyPort = proxy.address().port;
34
const connect = jest.fn();
35
proxy.once('connect', connect);
36
37
const server = await Helpers.runHttpsServer((req, res) => res.end(htmlString));
38
const tlsConnection = new MitmSocket(`${(sessionId += 1)}`, {
39
host: 'localhost',
40
port: String(server.port),
41
servername: 'localhost',
42
proxyUrl: `http://localhost:${proxyPort}`,
43
isSsl: true,
44
});
45
Helpers.onClose(async () => tlsConnection.close());
46
await tlsConnection.connect(mitmSocketSession);
47
48
const httpResponse = await httpGetWithSocket(`${server.baseUrl}/any`, {}, tlsConnection.socket);
49
expect(httpResponse).toBe(htmlString);
50
expect(connect).toHaveBeenCalledTimes(1);
51
});
52
53
test('should be able to send a request through a secure proxy with auth', async () => {
54
const htmlString = 'Proxy secure proxy echo echo';
55
const password = `u:password`;
56
const pass64 = Buffer.from(password).toString('base64');
57
const proxyServer = await Helpers.runHttpsServer((req, res) => res.end(htmlString));
58
const proxy = new Proxy(proxyServer.server);
59
proxy.authenticate = (
60
req: http.IncomingMessage,
61
cb: (req: http.IncomingMessage, success: boolean) => any,
62
) => {
63
const auth = req.headers['proxy-authorization'];
64
const isValid = auth === `Basic ${pass64}`;
65
if (!isValid) {
66
return cb(null, false);
67
}
68
cb(null, true);
69
};
70
const connect = jest.fn();
71
proxy.once('connect', connect);
72
73
const server = await Helpers.runHttpsServer((req, res) => res.end(htmlString));
74
const tlsConnection = getTlsConnection(server.port);
75
tlsConnection.setProxyUrl(`https://${password}@localhost:${proxyServer.port}`);
76
77
await tlsConnection.connect(mitmSocketSession);
78
const httpResponse = await httpGetWithSocket(`${server.baseUrl}/any`, {}, tlsConnection.socket);
79
expect(httpResponse).toBe(htmlString);
80
expect(connect).toHaveBeenCalledTimes(1);
81
});
82
83
test('should be able to use a socks5 proxy', async () => {
84
const proxy = socks5.createServer();
85
await new Promise(resolve => proxy.listen(0, resolve));
86
Helpers.needsClosing.push(proxy);
87
88
const proxyPort = proxy.address().port;
89
const htmlString = 'Proxy proxy echo echo';
90
const connect = jest.fn();
91
proxy.once('proxyConnect', connect);
92
93
const server = await Helpers.runHttpsServer((req, res) => res.end(htmlString));
94
const tlsConnection = new MitmSocket(`${(sessionId += 1)}`, {
95
host: 'localhost',
96
port: String(server.port),
97
servername: 'localhost',
98
proxyUrl: `socks5://localhost:${proxyPort}`,
99
isSsl: true,
100
});
101
Helpers.onClose(async () => tlsConnection.close());
102
await tlsConnection.connect(mitmSocketSession);
103
104
const httpResponse = await httpGetWithSocket(`${server.baseUrl}/any`, {}, tlsConnection.socket);
105
expect(httpResponse).toBe(htmlString);
106
expect(connect).toHaveBeenCalledTimes(1);
107
});
108
109
test('should be able to use a socks5 proxy with auth', async () => {
110
const proxy = socks5.createServer({
111
authenticate(username, password, socket, callback) {
112
// verify username/password
113
if (username !== 'foo' || password !== 'bar') {
114
// respond with auth failure (can be any error)
115
return setImmediate(callback, new Error('invalid credentials'));
116
}
117
118
// return successful authentication
119
return setImmediate(callback);
120
},
121
});
122
await new Promise(resolve => proxy.listen(0, resolve));
123
Helpers.needsClosing.push(proxy);
124
125
const proxyPort = proxy.address().port;
126
const connect = jest.fn();
127
const auth = jest.fn();
128
proxy.once('proxyConnect', connect);
129
proxy.once('authenticate', auth);
130
131
const htmlString = 'Proxy proxy echo auth';
132
const server = await Helpers.runHttp2Server((req, res) => res.end(htmlString));
133
const tlsConnection = new MitmSocket(`${(sessionId += 1)}`, {
134
host: 'localhost',
135
port: String(server.port),
136
servername: 'localhost',
137
proxyUrl: `socks5://foo:bar@localhost:${proxyPort}`,
138
isSsl: true,
139
});
140
Helpers.onClose(async () => tlsConnection.close());
141
await tlsConnection.connect(mitmSocketSession);
142
143
const client = http2.connect(server.baseUrl, {
144
createConnection: () => tlsConnection.socket,
145
});
146
Helpers.onClose(async () => client.close());
147
148
const h2stream = client.request({ ':path': '/' });
149
const httpResponse = await readableToBuffer(h2stream);
150
151
expect(httpResponse.toString()).toBe(htmlString);
152
expect(connect).toHaveBeenCalledTimes(1);
153
expect(auth).toHaveBeenCalledTimes(1);
154
});
155
156
test('should handle websockets over proxies', async () => {
157
const proxy = await startProxy();
158
const proxyPort = proxy.address().port;
159
const connect = jest.fn();
160
proxy.once('connect', connect);
161
162
const server = await Helpers.runHttpsServer((req, res) => res.end(''));
163
const serverPort = server.port;
164
165
const wsServer = new WebSocket.Server({ server: server.server });
166
wsServer.on('connection', async (ws: WebSocket) => {
167
ws.send('ola');
168
});
169
170
const tlsConnection = getTlsConnection(serverPort, undefined, true);
171
tlsConnection.connectOpts.keepAlive = true;
172
tlsConnection.setProxyUrl(`http://localhost:${proxyPort}`);
173
await tlsConnection.connect(mitmSocketSession);
174
175
const wsClient = new WebSocket(`wss://localhost:${serverPort}`, {
176
rejectUnauthorized: false,
177
createConnection: () => tlsConnection.socket,
178
});
179
180
Helpers.onClose(async () => wsClient.close());
181
await new Promise<void>(resolve => {
182
wsClient.once('message', msg => {
183
expect(msg).toBe('ola');
184
resolve();
185
});
186
});
187
expect(connect).toHaveBeenCalledTimes(1);
188
});
189
190
async function startProxy() {
191
const proxyPromise = createPromise();
192
const proxy = new Proxy(http.createServer());
193
proxy.listen(0, () => {
194
proxyPromise.resolve();
195
});
196
proxy.unref();
197
198
closeAfterTest(proxy);
199
await proxyPromise.promise;
200
return proxy;
201
}
202
203
function closeAfterTest(closable: { close: (...args: any[]) => any }) {
204
Helpers.onClose(() => new Promise(resolve => closable.close(() => process.nextTick(resolve))));
205
}
206
207