From 7570c25b3da02b94e8d72ba5843a6f116e8197c4 Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Wed, 24 Jul 2024 19:13:03 +0200 Subject: [PATCH] chore: remove glob from client-certificate matching (#31846) Signed-off-by: Max Schmitt Co-authored-by: Dmitry Gozman --- docs/src/api/params.md | 4 ++-- .../socksClientCertificatesInterceptor.ts | 14 ++++------- packages/playwright-core/types/types.d.ts | 24 +++++++++---------- packages/playwright/types/test.d.ts | 4 ++-- tests/library/client-certificates.spec.ts | 14 +++++++++++ 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 487270d79e..78d62060cc 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -523,7 +523,7 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. ## context-option-clientCertificates - `clientCertificates` <[Array]<[Object]>> - - `origin` <[string]> Glob pattern to match against the request origin that the certificate is valid for. + - `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. - `certPath` ?<[string]> Path to the file with the certificate in PEM format. - `keyPath` ?<[string]> Path to the file with the private key in PEM format. - `pfxPath` ?<[string]> Path to the PFX or PKCS12 encoded private key and certificate chain. @@ -533,7 +533,7 @@ TLS Client Authentication allows the server to request a client certificate and **Details** -An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be provided with a glob pattern to match the origins that the certificate is valid for. +An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. :::note Using Client Certificates in combination with Proxy Servers is not supported. diff --git a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts index 87b0f11c16..ce8086c821 100644 --- a/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts +++ b/packages/playwright-core/src/server/socksClientCertificatesInterceptor.ts @@ -21,7 +21,7 @@ import fs from 'fs'; import tls from 'tls'; import stream from 'stream'; import { createSocket } from '../utils/happy-eyeballs'; -import { globToRegex, isUnderTest, ManualPromise } from '../utils'; +import { isUnderTest, ManualPromise } from '../utils'; import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy'; import { SocksProxy } from '../common/socksProxy'; import type * as channels from '@protocol/channels'; @@ -224,20 +224,16 @@ export class ClientCertificatesProxy { } } -const kClientCertificatesGlobRegex = Symbol('kClientCertificatesGlobRegex'); - export function clientCertificatesToTLSOptions( clientCertificates: channels.BrowserNewContextOptions['clientCertificates'], origin: string ): Pick | undefined { const matchingCerts = clientCertificates?.filter(c => { - let regex: RegExp | undefined = (c as any)[kClientCertificatesGlobRegex]; - if (!regex) { - regex = globToRegex(c.origin); - (c as any)[kClientCertificatesGlobRegex] = regex; + try { + return new URL(c.origin).origin === origin; + } catch (error) { + return c.origin === origin; } - regex.lastIndex = 0; - return regex.test(origin); }); if (!matchingCerts || !matchingCerts.length) return; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index d207756459..47e263cdb6 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -13172,8 +13172,8 @@ export interface BrowserType { * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be - * provided with a glob pattern to match the origins that the certificate is valid for. + * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that + * the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -13182,7 +13182,7 @@ export interface BrowserType { */ clientCertificates?: Array<{ /** - * Glob pattern to match against the request origin that the certificate is valid for. + * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. */ origin: string; @@ -15583,8 +15583,8 @@ export interface APIRequest { * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be - * provided with a glob pattern to match the origins that the certificate is valid for. + * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that + * the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -15593,7 +15593,7 @@ export interface APIRequest { */ clientCertificates?: Array<{ /** - * Glob pattern to match against the request origin that the certificate is valid for. + * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. */ origin: string; @@ -16776,8 +16776,8 @@ export interface Browser extends EventEmitter { * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be - * provided with a glob pattern to match the origins that the certificate is valid for. + * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that + * the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -16786,7 +16786,7 @@ export interface Browser extends EventEmitter { */ clientCertificates?: Array<{ /** - * Glob pattern to match against the request origin that the certificate is valid for. + * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. */ origin: string; @@ -20226,8 +20226,8 @@ export interface BrowserContextOptions { * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be - * provided with a glob pattern to match the origins that the certificate is valid for. + * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that + * the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * @@ -20236,7 +20236,7 @@ export interface BrowserContextOptions { */ clientCertificates?: Array<{ /** - * Glob pattern to match against the request origin that the certificate is valid for. + * Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. */ origin: string; diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index f1f9ee32dd..c5a3f5d9dd 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5208,8 +5208,8 @@ export interface PlaywrightTestOptions { * * An array of client certificates to be used. Each certificate object must have both `certPath` and `keyPath` or a * single `pfxPath` to load the client certificate. Optionally, `passphrase` property should be provided if the - * certficiate is encrypted. If the certificate is valid only for specific origins, the `origin` property should be - * provided with a glob pattern to match the origins that the certificate is valid for. + * certficiate is encrypted. The `origin` property should be provided with an exact match to the request origin that + * the certificate is valid for. * * **NOTE** Using Client Certificates in combination with Proxy Servers is not supported. * diff --git a/tests/library/client-certificates.spec.ts b/tests/library/client-certificates.spec.ts index d1b15e4658..c5fc523925 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -248,6 +248,20 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates and trailing slash', async ({ browser, startCCServer, asset, browserName }) => { + const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); + const page = await browser.newPage({ + clientCertificates: [{ + origin: serverURL, + certPath: asset('client-certificates/client/trusted/cert.pem'), + keyPath: asset('client-certificates/client/trusted/key.pem'), + }], + }); + await page.goto(serverURL); + await expect(page.getByText('Hello Alice, your certificate was issued by localhost!')).toBeVisible(); + await page.close(); + }); + test('should have ignoreHTTPSErrors=false by default', async ({ browser, httpsServer, asset, browserName, platform }) => { const page = await browser.newPage({ clientCertificates: [{