diff --git a/docs/src/api/params.md b/docs/src/api/params.md index f4adc23a3c..5aa9c6a5ab 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -531,15 +531,18 @@ Does not enforce fixed viewport, allows resizing window in the headed mode. - `clientCertificates` <[Array]<[Object]>> - `origin` <[string]> Exact origin that the certificate is valid for. Origin includes `https` protocol, a hostname and optionally a port. - `certPath` ?<[path]> Path to the file with the certificate in PEM format. + - `cert` ?<[Buffer]> Direct value of the certificate in PEM format. - `keyPath` ?<[path]> Path to the file with the private key in PEM format. + - `key` ?<[Buffer]> Direct value of the private key in PEM format. - `pfxPath` ?<[path]> Path to the PFX or PKCS12 encoded private key and certificate chain. + - `pfx` ?<[Buffer]> Direct value of the PFX or PKCS12 encoded private key and certificate chain. - `passphrase` ?<[string]> Passphrase for the private key (PEM or PFX). TLS Client Authentication allows the server to request a client certificate and verify it. **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. The `origin` property should be provided with an exact match to the request origin that the certificate is valid for. +An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, `passphrase` property should be provided if the certificate 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/client/browserContext.ts b/packages/playwright-core/src/client/browserContext.ts index 5991fcafd9..c4f7827840 100644 --- a/packages/playwright-core/src/client/browserContext.ts +++ b/packages/playwright-core/src/client/browserContext.ts @@ -552,13 +552,19 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) { export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise { if (!certs) return undefined; - return await Promise.all(certs.map(async cert => { - return { - origin: cert.origin, - cert: cert.certPath ? await fs.promises.readFile(cert.certPath) : undefined, - key: cert.keyPath ? await fs.promises.readFile(cert.keyPath) : undefined, - pfx: cert.pfxPath ? await fs.promises.readFile(cert.pfxPath) : undefined, - passphrase: cert.passphrase, - }; - })); + + const bufferizeContent = async (value?: Buffer, path?: string): Promise => { + if (value) + return value; + if (path) + return await fs.promises.readFile(path); + }; + + return await Promise.all(certs.map(async cert => ({ + origin: cert.origin, + cert: await bufferizeContent(cert.cert, cert.certPath), + key: await bufferizeContent(cert.key, cert.keyPath), + pfx: await bufferizeContent(cert.pfx, cert.pfxPath), + passphrase: cert.passphrase, + }))); } diff --git a/packages/playwright-core/src/client/types.ts b/packages/playwright-core/src/client/types.ts index 8101257280..37d374e3ec 100644 --- a/packages/playwright-core/src/client/types.ts +++ b/packages/playwright-core/src/client/types.ts @@ -49,8 +49,11 @@ export const kLifecycleEvents: Set = new Set(['load', 'domconten export type ClientCertificate = { origin: string; + cert?: Buffer; certPath?: string; + key?: Buffer; keyPath?: string; + pfx?: Buffer; pfxPath?: string; passphrase?: string; }; diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index bf4e35a9ca..a4bf9fa812 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -9138,10 +9138,10 @@ export interface Browser { * * **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. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate 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. * @@ -9159,16 +9159,31 @@ export interface Browser { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -13850,10 +13865,10 @@ export interface BrowserType { * * **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. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate 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. * @@ -13871,16 +13886,31 @@ export interface BrowserType { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -16259,10 +16289,10 @@ export interface APIRequest { * * **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. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate 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. * @@ -16280,16 +16310,31 @@ export interface APIRequest { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ @@ -20600,10 +20645,10 @@ export interface BrowserContextOptions { * * **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. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate 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. * @@ -20621,16 +20666,31 @@ export interface BrowserContextOptions { */ certPath?: string; + /** + * Direct value of the certificate in PEM format. + */ + cert?: Buffer; + /** * Path to the file with the private key in PEM format. */ keyPath?: string; + /** + * Direct value of the private key in PEM format. + */ + key?: Buffer; + /** * Path to the PFX or PKCS12 encoded private key and certificate chain. */ pfxPath?: string; + /** + * Direct value of the PFX or PKCS12 encoded private key and certificate chain. + */ + pfx?: Buffer; + /** * Passphrase for the private key (PEM or PFX). */ diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 28bc7798b5..0131cb16c9 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -5206,10 +5206,10 @@ export interface PlaywrightTestOptions { * * **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. The `origin` property should be provided with an exact match to the request origin that - * the certificate is valid for. + * An array of client certificates to be used. Each certificate object must have either both `certPath` and `keyPath`, + * a single `pfxPath`, or their corresponding direct value equivalents (`cert` and `key`, or `pfx`). Optionally, + * `passphrase` property should be provided if the certificate 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 fa7eb8bbbc..49e0dc0dcc 100644 --- a/tests/library/client-certificates.spec.ts +++ b/tests/library/client-certificates.spec.ts @@ -303,6 +303,21 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates when passing as content', 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, + cert: await fs.promises.readFile(asset('client-certificates/client/trusted/cert.pem')), + key: await fs.promises.readFile(asset('client-certificates/client/trusted/key.pem')), + }], + }); + await page.goto(serverURL); + await expect(page.getByTestId('message')).toHaveText('Hello Alice, your certificate was issued by localhost!'); + await page.close(); + }); + test('should not hang on tls errors during TLS 1.2 handshake', async ({ browser, asset, platform, browserName }) => { for (const tlsVersion of ['TLSv1.3', 'TLSv1.2'] as const) { await test.step(`TLS version: ${tlsVersion}`, async () => { @@ -360,6 +375,21 @@ test.describe('browser', () => { await page.close(); }); + test('should pass with matching certificates in pfx format when passing as content', 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, + pfx: await fs.promises.readFile(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 fail with matching certificates in legacy pfx format', async ({ browser, startCCServer, asset, browserName }) => { const serverURL = await startCCServer({ useFakeLocalhost: browserName === 'webkit' && process.platform === 'darwin' }); await expect(browser.newPage({