Skip to content

Commit ed9b4d9

Browse files
committed
cherry-pick(#32007): fix(client-certificates): report error to the browser if incorrect passphrase
1 parent fca1fa0 commit ed9b4d9

File tree

4 files changed

+66
-23
lines changed

4 files changed

+66
-23
lines changed

packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -142,34 +142,14 @@ class SocksProxyConnection {
142142
dummyServer.emit('connection', this.internal);
143143
dummyServer.on('secureConnection', internalTLS => {
144144
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} chooses ALPN ${internalTLS.alpnProtocol}`);
145-
const tlsOptions: tls.ConnectionOptions = {
146-
socket: this.target,
147-
host: this.host,
148-
port: this.port,
149-
rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors,
150-
ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'],
151-
...clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, new URL(`https://${this.host}:${this.port}`).origin),
152-
};
153-
if (!net.isIP(this.host))
154-
tlsOptions.servername = this.host;
155-
const targetTLS = tls.connect(tlsOptions);
156-
157-
targetTLS.on('secureConnect', () => {
158-
internalTLS.pipe(targetTLS);
159-
targetTLS.pipe(internalTLS);
160-
});
161145

162-
// Handle close and errors
146+
let targetTLS: tls.TLSSocket | undefined = undefined;
163147
const closeBothSockets = () => {
164148
internalTLS.end();
165-
targetTLS.end();
149+
targetTLS?.end();
166150
};
167151

168-
internalTLS.on('end', () => closeBothSockets());
169-
targetTLS.on('end', () => closeBothSockets());
170-
171-
internalTLS.on('error', () => closeBothSockets());
172-
targetTLS.on('error', error => {
152+
const handleError = (error: Error) => {
173153
debugLogger.log('client-certificates', `error when connecting to target: ${error.message}`);
174154
const responseBody = 'Playwright client-certificate error: ' + error.message;
175155
if (internalTLS?.alpnProtocol === 'h2') {
@@ -204,7 +184,38 @@ class SocksProxyConnection {
204184
].join('\r\n'));
205185
closeBothSockets();
206186
}
187+
};
188+
189+
let secureContext: tls.SecureContext;
190+
try {
191+
secureContext = tls.createSecureContext(clientCertificatesToTLSOptions(this.socksProxy.clientCertificates, new URL(`https://${this.host}:${this.port}`).origin));
192+
} catch (error) {
193+
handleError(error);
194+
return;
195+
}
196+
197+
const tlsOptions: tls.ConnectionOptions = {
198+
socket: this.target,
199+
host: this.host,
200+
port: this.port,
201+
rejectUnauthorized: !this.socksProxy.ignoreHTTPSErrors,
202+
ALPNProtocols: [internalTLS.alpnProtocol || 'http/1.1'],
203+
servername: !net.isIP(this.host) ? this.host : undefined,
204+
secureContext,
205+
};
206+
207+
targetTLS = tls.connect(tlsOptions);
208+
209+
targetTLS.on('secureConnect', () => {
210+
internalTLS.pipe(targetTLS);
211+
targetTLS.pipe(internalTLS);
207212
});
213+
214+
internalTLS.on('end', () => closeBothSockets());
215+
targetTLS.on('end', () => closeBothSockets());
216+
217+
internalTLS.on('error', () => closeBothSockets());
218+
targetTLS.on('error', handleError);
208219
});
209220
});
210221
}

tests/assets/client-certificates/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ openssl x509 \
3636
-out client/trusted/cert.pem \
3737
-set_serial 01 \
3838
-days 365
39+
# create pfx
40+
openssl pkcs12 -export -out client/trusted/cert.pfx -inkey client/trusted/key.pem -in client/trusted/cert.pem -passout pass:secure
3941
```
4042

4143
## Self-signed certificate (invalid)
4.1 KB
Binary file not shown.

tests/library/client-certificates.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,36 @@ test.describe('browser', () => {
257257
await page.close();
258258
});
259259

260+
test('should pass with matching certificates in pfx format', async ({ browser, startCCServer, asset, browserName }) => {
261+
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' });
262+
const page = await browser.newPage({
263+
ignoreHTTPSErrors: true,
264+
clientCertificates: [{
265+
origin: new URL(serverURL).origin,
266+
pfxPath: asset('client-certificates/client/trusted/cert.pfx'),
267+
passphrase: 'secure'
268+
}],
269+
});
270+
await page.goto(serverURL);
271+
await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!');
272+
await page.close();
273+
});
274+
275+
test('should throw a http error if the pfx passphrase is incorect', async ({ browser, startCCServer, asset, browserName }) => {
276+
const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' });
277+
const page = await browser.newPage({
278+
ignoreHTTPSErrors: true,
279+
clientCertificates: [{
280+
origin: new URL(serverURL).origin,
281+
pfxPath: asset('client-certificates/client/trusted/cert.pfx'),
282+
passphrase: 'this-password-is-incorrect'
283+
}],
284+
});
285+
await page.goto(serverURL);
286+
await expect(page.getByText('Playwright client-certificate error: mac verify failure')).toBeVisible();
287+
await page.close();
288+
});
289+
260290
test('should pass with matching certificates on context APIRequestContext instance', async ({ browser, startCCServer, asset, browserName }) => {
261291
const serverURL = await startCCServer({ host: '127.0.0.1' });
262292
const baseOptions = {

0 commit comments

Comments
 (0)