Skip to content

Commit

Permalink
feat(client-certificates): allow passing certificates from memory (#3…
Browse files Browse the repository at this point in the history
  • Loading branch information
mxschmitt authored Aug 19, 2024
1 parent 74f5ce5 commit 010778f
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 30 deletions.
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` ?<[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.
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?: Buffer, path?: string): Promise<Buffer | undefined> => {
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,
})));
}
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?: Buffer;
certPath?: string;
key?: Buffer;
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?: 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).
*/
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?: 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).
*/
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?: 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).
*/
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?: 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).
*/
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')),
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 () => {
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' });
await expect(browser.newPage({
Expand Down

0 comments on commit 010778f

Please sign in to comment.