bug(network): Use proxySocket to determine alpnProtocol
This commit is contained in:
parent
55333c6f28
commit
1d85f5b72c
|
@ -42,7 +42,7 @@ function loadDummyServerCertsIfNeeded() {
|
|||
class ALPNCache {
|
||||
private _cache = new Map<string, ManualPromise<string>>();
|
||||
|
||||
get(host: string, port: number, success: (protocol: string) => void) {
|
||||
get(host: string, port: number, secureContext: tls.SecureContext | undefined, proxySocket: stream.Duplex | undefined, success: (protocol: string) => void) {
|
||||
const cacheKey = `${host}:${port}`;
|
||||
{
|
||||
const result = this._cache.get(cacheKey);
|
||||
|
@ -54,20 +54,45 @@ class ALPNCache {
|
|||
const result = new ManualPromise<string>();
|
||||
this._cache.set(cacheKey, result);
|
||||
result.then(success);
|
||||
createTLSSocket({
|
||||
host,
|
||||
port,
|
||||
servername: net.isIP(host) ? undefined : host,
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
rejectUnauthorized: false,
|
||||
}).then(socket => {
|
||||
// The server may not respond with ALPN, in which case we default to http/1.1.
|
||||
result.resolve(socket.alpnProtocol || 'http/1.1');
|
||||
socket.end();
|
||||
}).catch(error => {
|
||||
debugLogger.log('client-certificates', `ALPN error: ${error.message}`);
|
||||
result.resolve('http/1.1');
|
||||
});
|
||||
if(!proxySocket) {
|
||||
createTLSSocket({
|
||||
host,
|
||||
port,
|
||||
servername: net.isIP(host) ? undefined : host,
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
rejectUnauthorized: false,
|
||||
secureContext
|
||||
}).then(socket => {
|
||||
// The server may not respond with ALPN, in which case we default to http/1.1.
|
||||
result.resolve(socket.alpnProtocol || 'http/1.1');
|
||||
socket.end();
|
||||
}).catch(error => {
|
||||
debugLogger.log('client-certificates', `ALPN error: ${error.message}`);
|
||||
result.resolve('http/1.1');
|
||||
});
|
||||
} else {
|
||||
const socket = tls.connect({
|
||||
socket: proxySocket,
|
||||
port: port,
|
||||
host: host,
|
||||
ALPNProtocols: ['h2', 'http/1.1'],
|
||||
rejectUnauthorized: false,
|
||||
secureContext: secureContext,
|
||||
servername: net.isIP(host) ? undefined : host
|
||||
});
|
||||
socket.on('secureConnect', () => {
|
||||
result.resolve(socket.alpnProtocol || 'http/1.1');
|
||||
socket.end();
|
||||
});
|
||||
socket.on('error', error => {
|
||||
debugLogger.log('client-certificates', `ALPN error: ${error.message}`);
|
||||
result.resolve('http/1.1');
|
||||
});
|
||||
socket.on('timeout', () => {
|
||||
result.resolve('http/1.1');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +167,7 @@ class SocksProxyConnection {
|
|||
this.target.write(data);
|
||||
}
|
||||
|
||||
private _attachTLSListeners() {
|
||||
private async _attachTLSListeners() {
|
||||
this.internal = new stream.Duplex({
|
||||
read: () => {},
|
||||
write: (data, encoding, callback) => {
|
||||
|
@ -150,7 +175,13 @@ class SocksProxyConnection {
|
|||
callback();
|
||||
}
|
||||
});
|
||||
this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, alpnProtocolChosenByServer => {
|
||||
const secureContext = this.socksProxy.secureContextMap.get(new URL(`https://${this.host}:${this.port}`).origin);
|
||||
let proxySocket: stream.Duplex | undefined = undefined;
|
||||
if (this.socksProxy.proxyAgentFromOptions)
|
||||
proxySocket = await this.socksProxy.proxyAgentFromOptions.callback(new EventEmitter() as any, { host: rewriteToLocalhostIfNeeded(this.host), port: this.port, secureEndpoint: false });
|
||||
|
||||
this.socksProxy.alpnCache.get(rewriteToLocalhostIfNeeded(this.host), this.port, secureContext, proxySocket, alpnProtocolChosenByServer => {
|
||||
proxySocket?.destroy();
|
||||
debugLogger.log('client-certificates', `Proxy->Target ${this.host}:${this.port} chooses ALPN ${alpnProtocolChosenByServer}`);
|
||||
if (this._closed)
|
||||
return;
|
||||
|
@ -221,7 +252,7 @@ class SocksProxyConnection {
|
|||
rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors,
|
||||
ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'],
|
||||
servername: !net.isIP(this.host) ? this.host : undefined,
|
||||
secureContext: this.socksProxy.secureContextMap.get(new URL(`https://${this.host}:${this.port}`).origin),
|
||||
secureContext: secureContext,
|
||||
});
|
||||
|
||||
targetTLS.once('secureConnect', () => {
|
||||
|
|
|
@ -371,6 +371,26 @@ test.describe('browser', () => {
|
|||
await page.close();
|
||||
});
|
||||
|
||||
test('should fail with non-matching certificates and when a http proxy is used', async ({ browser, startCCServer, asset, browserName, proxyServer, isMac }) => {
|
||||
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
|
||||
proxyServer.forwardTo(parseInt(new URL(serverURL).port, 10), { allowConnectRequests: true });
|
||||
const page = await browser.newPage({
|
||||
ignoreHTTPSErrors: true,
|
||||
clientCertificates: [{
|
||||
origin: new URL("https://abcd.efgh").origin,
|
||||
certPath: asset('client-certificates/client/trusted/cert.pem'),
|
||||
keyPath: asset('client-certificates/client/trusted/key.pem'),
|
||||
}],
|
||||
proxy: { server: `localhost:${proxyServer.PORT}` }
|
||||
});
|
||||
expect(proxyServer.connectHosts).toEqual([]);
|
||||
await page.goto(serverURL);
|
||||
const host = browserName === 'webkit' && isMac ? 'localhost' : '127.0.0.1';
|
||||
expect([...new Set(proxyServer.connectHosts)]).toEqual([`${host}:${new URL(serverURL).port}`]);
|
||||
await expect(page.getByTestId('message')).toHaveText('Sorry, but you need to provide a client certificate to continue.');
|
||||
await page.close();
|
||||
});
|
||||
|
||||
test('should pass with matching certificates and when a socks proxy is used', async ({ browser, startCCServer, asset, browserName, isMac }) => {
|
||||
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && isMac });
|
||||
const serverPort = parseInt(new URL(serverURL).port, 10);
|
||||
|
|
Loading…
Reference in New Issue