Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(client-certificates): allow passing certificates from memory #32210

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` ?<[string]> Direct value of the certificate in PEM format.
mxschmitt marked this conversation as resolved.
Show resolved Hide resolved
- `keyPath` ?<[path]> Path to the file with the private key in PEM format.
- `key` ?<[string]> 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.
Expand Down
24 changes: 15 additions & 9 deletions packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,13 +552,19 @@ function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
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?: string | Buffer, path?: string): Promise<Buffer | undefined> => {
if (value)
return Buffer.from(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,
})));
}
3 changes: 3 additions & 0 deletions packages/playwright-core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ export const kLifecycleEvents: Set<LifecycleEvent> = new Set(['load', 'domconten

export type ClientCertificate = {
origin: string;
cert?: string;
certPath?: string;
key?: string;
keyPath?: string;
pfx?: Buffer;
pfxPath?: string;
passphrase?: string;
};
Expand Down
92 changes: 76 additions & 16 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -9159,16 +9159,31 @@ export interface Browser {
*/
certPath?: string;

/**
* Direct value of the certificate in PEM format.
*/
cert?: string;

/**
* Path to the file with the private key in PEM format.
*/
keyPath?: string;

/**
* Direct value of the private key in PEM format.
*/
key?: string;

/**
* 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).
*/
Expand Down Expand Up @@ -13850,10 +13865,10 @@ export interface BrowserType<Unused = {}> {
*
* **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.
*
Expand All @@ -13871,16 +13886,31 @@ export interface BrowserType<Unused = {}> {
*/
certPath?: string;

/**
* Direct value of the certificate in PEM format.
*/
cert?: string;

/**
* Path to the file with the private key in PEM format.
*/
keyPath?: string;

/**
* Direct value of the private key in PEM format.
*/
key?: string;

/**
* 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).
*/
Expand Down Expand Up @@ -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.
*
Expand All @@ -16280,16 +16310,31 @@ export interface APIRequest {
*/
certPath?: string;

/**
* Direct value of the certificate in PEM format.
*/
cert?: string;

/**
* Path to the file with the private key in PEM format.
*/
keyPath?: string;

/**
* Direct value of the private key in PEM format.
*/
key?: string;

/**
* 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).
*/
Expand Down Expand Up @@ -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.
*
Expand All @@ -20621,16 +20666,31 @@ export interface BrowserContextOptions {
*/
certPath?: string;

/**
* Direct value of the certificate in PEM format.
*/
cert?: string;

/**
* Path to the file with the private key in PEM format.
*/
keyPath?: string;

/**
* Direct value of the private key in PEM format.
*/
key?: string;

/**
* 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).
*/
Expand Down
8 changes: 4 additions & 4 deletions packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
30 changes: 30 additions & 0 deletions tests/library/client-certificates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'))).toString(),
key: (await fs.promises.readFile(asset('client-certificates/client/trusted/key.pem'))).toString(),
}],
});
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 () => {
Expand Down Expand Up @@ -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' });
const page = await browser.newPage({
Expand Down
Loading