diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 131aa30de9..13590579fe 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -142,34 +142,14 @@ class SocksProxyConnection { dummyServer.emit('connection', this.internal); dummyServer.on('secureConnection', internalTLS => { debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`); - const tlsOptions: tls.ConnectionOptions = { - socket: this.target, - host: this.host, - port: this.port, - rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors, - ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'], - ...clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, new URL(`https://${this.host}:${this.port}`).origin), - }; - if (!net.isIP(this.host)) - tlsOptions.servername = this.host; - const targetTLS = tls.connect(tlsOptions); - targetTLS.on('secureConnect', () => { - internalTLS.pipe(targetTLS); - targetTLS.pipe(internalTLS); - }); - - // Handle close and errors + let targetTLS: tls.TLSSocket | undefined = undefined; const closeBothSockets = () => { internalTLS.end(); - targetTLS.end(); + targetTLS?.end(); }; - internalTLS.on('end', () => closeBothSockets()); - targetTLS.on('end', () => closeBothSockets()); - - internalTLS.on('error', () => closeBothSockets()); - targetTLS.on('error', error => { + const handleError = (error: Error) => { debugLogger.log('client-certificates', `error when connecting to target: ${error.message}`); const responseBody = 'Playwright client-certificate error: ' + error.message; if (internalTLS?.alpnProtocol === 'h2') { @@ -204,7 +184,38 @@ class SocksProxyConnection { ].join('\r\n')); closeBothSockets(); } + }; + + let secureContext: tls.SecureContext; + try { + secureContext = tls.createSecureContext(clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, new URL(`https://${this.host}:${this.port}`).origin)); + } catch (error) { + handleError(error); + return; + } + + const tlsOptions: tls.ConnectionOptions = { + socket: this.target, + host: this.host, + port: this.port, + rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors, + ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'], + servername: !net.isIP(this.host) ? this.host : undefined, + secureContext, + }; + + targetTLS = tls.connect(tlsOptions); + + targetTLS.on('secureConnect', () => { + internalTLS.pipe(targetTLS); + targetTLS.pipe(internalTLS); }); + + internalTLS.on('end', () => closeBothSockets()); + targetTLS.on('end', () => closeBothSockets()); + + internalTLS.on('error', () => closeBothSockets()); + targetTLS.on('error', handleError); }); }); } diff --git a/tests/assets/client-certificates/README.md b/tests/assets/client-certificates/README.md index b0ee78e707..7ee690de52 100644 --- a/tests/assets/client-certificates/README.md +++ b/tests/assets/client-certificates/README.md @@ -36,6 +36,8 @@ openssl x509 \ -out client/trusted/cert.pem \ -set_serial 01 \ -days 365 +# create pfx +openssl pkcs12 -export -out client/trusted/cert.pfx -inkey client/trusted/key.pem -in client/trusted/cert.pem -passout pass:secure ``` ## Self-signed certificate (invalid) diff --git a/tests/assets/client-certificates/client/trusted/cert.pfx b/tests/assets/client-certificates/client/trusted/cert.pfx new file mode 100644 index 0000000000..48726d703a Binary files /dev/null and b/tests/assets/client-certificates/client/trusted/cert.pfx differ diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index 867e3d3965..d54281b143 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -257,6 +257,36 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates in pfx format', async ({ browser, startCCServer, asset, browserName }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); + const page = await browser.newPage({ + ignoreHTTPSErrors: true, + clientCertificates: [{ + origin: new URL(serverURL).origin, + pfxPath: asset('client-certificates/client/trusted/cert.pfx'), + passphrase: 'secure' + }], + }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + await page.close(); + }); + + test('should throw a http error if the pfx passphrase is incorect', async ({ browser, startCCServer, asset, browserName }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); + const page = await browser.newPage({ + ignoreHTTPSErrors: true, + clientCertificates: [{ + origin: new URL(serverURL).origin, + pfxPath: asset('client-certificates/client/trusted/cert.pfx'), + passphrase: 'this-password-is-incorrect' + }], + }); + await page.goto(serverURL); + await expect(page.getByText('Playwright client-certificate error: mac verify failure')).toBeVisible(); + await page.close(); + }); + test('should pass with matching certificates on context APIRequestContext instance', async ({ browser, startCCServer, asset, browserName }) => { const serverURL = await startCCServer({ host: '127.0.0.1' }); const baseOptions = {