Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 1 addition & 2 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,15 +685,14 @@ Does not enforce fixed viewport, allows resizing window in the headed mode.
- `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).
- `noCertificate` ?<[boolean]> Explicitly send no client certificate for this origin. Must be the only field set besides `origin`.

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 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.

Client certificate authentication is only active when at least one client certificate is provided. If you want to reject all client certificates sent by the server for an origin you visit, set `noCertificate` to `true` for that origin instead of omitting it: omitting the origin entirely leaves the connection unintercepted, so the server's own certificate request still reaches the browser and may trigger a native certificate-selection prompt on some platforms. `noCertificate` forces interception for that origin while still presenting no client certificate.
Client certificate authentication is only active when at least one client certificate is provided. If you want to reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that does not match any of the domains you plan to visit.

:::note
When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it work by replacing `localhost` with `local.playwright`.
Expand Down
44 changes: 8 additions & 36 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11343,10 +11343,8 @@ export interface Browser {
* with an exact match to the request origin that the certificate is valid for.
*
* Client certificate authentication is only active when at least one client certificate is provided. If you want to
* reject all client certificates sent by the server for an origin you visit, set `noCertificate` to `true` for that
* origin instead of omitting it: omitting the origin entirely leaves the connection unintercepted, so the server's
* own certificate request still reaches the browser and may trigger a native certificate-selection prompt on some
* platforms. `noCertificate` forces interception for that origin while still presenting no client certificate.
* reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
* does not match any of the domains you plan to visit.
*
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* work by replacing `localhost` with `local.playwright`.
Expand Down Expand Up @@ -11392,11 +11390,6 @@ export interface Browser {
* Passphrase for the private key (PEM or PFX).
*/
passphrase?: string;

/**
* Explicitly send no client certificate for this origin. Must be the only field set besides `origin`.
*/
noCertificate?: boolean;
}>;

/**
Expand Down Expand Up @@ -17688,10 +17681,8 @@ export interface BrowserType<Unused = {}> {
* with an exact match to the request origin that the certificate is valid for.
*
* Client certificate authentication is only active when at least one client certificate is provided. If you want to
* reject all client certificates sent by the server for an origin you visit, set `noCertificate` to `true` for that
* origin instead of omitting it: omitting the origin entirely leaves the connection unintercepted, so the server's
* own certificate request still reaches the browser and may trigger a native certificate-selection prompt on some
* platforms. `noCertificate` forces interception for that origin while still presenting no client certificate.
* reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
* does not match any of the domains you plan to visit.
*
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* work by replacing `localhost` with `local.playwright`.
Expand Down Expand Up @@ -17737,11 +17728,6 @@ export interface BrowserType<Unused = {}> {
* Passphrase for the private key (PEM or PFX).
*/
passphrase?: string;

/**
* Explicitly send no client certificate for this origin. Must be the only field set besides `origin`.
*/
noCertificate?: boolean;
}>;

/**
Expand Down Expand Up @@ -19486,10 +19472,8 @@ export interface APIRequest {
* with an exact match to the request origin that the certificate is valid for.
*
* Client certificate authentication is only active when at least one client certificate is provided. If you want to
* reject all client certificates sent by the server for an origin you visit, set `noCertificate` to `true` for that
* origin instead of omitting it: omitting the origin entirely leaves the connection unintercepted, so the server's
* own certificate request still reaches the browser and may trigger a native certificate-selection prompt on some
* platforms. `noCertificate` forces interception for that origin while still presenting no client certificate.
* reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
* does not match any of the domains you plan to visit.
*
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* work by replacing `localhost` with `local.playwright`.
Expand Down Expand Up @@ -19535,11 +19519,6 @@ export interface APIRequest {
* Passphrase for the private key (PEM or PFX).
*/
passphrase?: string;

/**
* Explicitly send no client certificate for this origin. Must be the only field set besides `origin`.
*/
noCertificate?: boolean;
}>;

/**
Expand Down Expand Up @@ -26692,10 +26671,8 @@ export interface BrowserContextOptions {
* with an exact match to the request origin that the certificate is valid for.
*
* Client certificate authentication is only active when at least one client certificate is provided. If you want to
* reject all client certificates sent by the server for an origin you visit, set `noCertificate` to `true` for that
* origin instead of omitting it: omitting the origin entirely leaves the connection unintercepted, so the server's
* own certificate request still reaches the browser and may trigger a native certificate-selection prompt on some
* platforms. `noCertificate` forces interception for that origin while still presenting no client certificate.
* reject all client certificates sent by the server, you need to provide a client certificate with an `origin` that
* does not match any of the domains you plan to visit.
*
* **NOTE** When using WebKit on macOS, accessing `localhost` will not pick up client certificates. You can make it
* work by replacing `localhost` with `local.playwright`.
Expand Down Expand Up @@ -26741,11 +26718,6 @@ export interface BrowserContextOptions {
* Passphrase for the private key (PEM or PFX).
*/
passphrase?: string;

/**
* Explicitly send no client certificate for this origin. Must be the only field set besides `origin`.
*/
noCertificate?: boolean;
}>;

/**
Expand Down
1 change: 0 additions & 1 deletion packages/playwright-core/src/client/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,5 @@ export async function toClientCertificatesProtocol(certs?: BrowserContextOptions
key: await bufferizeContent(cert.key, cert.keyPath),
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
passphrase: cert.passphrase,
noCertificate: cert.noCertificate,
})));
}
11 changes: 0 additions & 11 deletions packages/playwright-core/src/client/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,6 @@ export type AndroidDeviceLaunchBrowserParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -464,7 +463,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -895,7 +893,6 @@ export type BrowserNewContextParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -972,7 +969,6 @@ export type BrowserNewContextOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1052,7 +1048,6 @@ export type BrowserNewContextForReuseParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1129,7 +1124,6 @@ export type BrowserNewContextForReuseOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1248,7 +1242,6 @@ export type BrowserContextInitializer = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1908,7 +1901,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1998,7 +1990,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -4804,7 +4795,6 @@ export type PlaywrightNewRequestParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
maxRedirects?: number,
httpCredentials?: HttpCredentials[],
Expand Down Expand Up @@ -4832,7 +4822,6 @@ export type PlaywrightNewRequestOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
maxRedirects?: number,
httpCredentials?: HttpCredentials[],
Expand Down
1 change: 0 additions & 1 deletion packages/playwright-core/src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ export type ClientCertificate = {
pfx?: Buffer;
pfxPath?: string;
passphrase?: string;
noCertificate?: boolean;
};

export type BrowserContextOptions = Omit<channels.BrowserNewContextOptions, 'viewport' | 'noDefaultViewport' | 'extraHTTPHeaders' | 'clientCertificates' | 'storageState' | 'recordHar' | 'colorScheme' | 'reducedMotion' | 'forcedColors' | 'acceptDownloads' | 'contrast' | 'agent' | 'httpCredentials'> & {
Expand Down
5 changes: 0 additions & 5 deletions packages/playwright-core/src/server/browserContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -798,11 +798,6 @@ export function verifyClientCertificates(clientCertificates?: types.BrowserConte
for (const cert of clientCertificates) {
if (!cert.origin)
throw new Error(`clientCertificates.origin is required`);
if (cert.noCertificate) {
if (cert.cert || cert.key || cert.passphrase || cert.pfx)
throw new Error('noCertificate is set together with cert, key, passphrase or pfx');
continue;
}
if (!cert.cert && !cert.key && !cert.passphrase && !cert.pfx)
throw new Error('None of cert, key, passphrase or pfx is specified');
if (cert.cert && !cert.key)
Expand Down
11 changes: 0 additions & 11 deletions packages/playwright-core/src/server/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ export type AndroidDeviceLaunchBrowserParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -465,7 +464,6 @@ export type AndroidDeviceLaunchBrowserOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -896,7 +894,6 @@ export type BrowserNewContextParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -973,7 +970,6 @@ export type BrowserNewContextOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1053,7 +1049,6 @@ export type BrowserNewContextForReuseParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1130,7 +1125,6 @@ export type BrowserNewContextForReuseOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1249,7 +1243,6 @@ export type BrowserContextInitializer = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1909,7 +1902,6 @@ export type BrowserTypeLaunchPersistentContextParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -1999,7 +1991,6 @@ export type BrowserTypeLaunchPersistentContextOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
javaScriptEnabled?: boolean,
bypassCSP?: boolean,
Expand Down Expand Up @@ -4805,7 +4796,6 @@ export type PlaywrightNewRequestParams = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
maxRedirects?: number,
httpCredentials?: HttpCredentials[],
Expand Down Expand Up @@ -4833,7 +4823,6 @@ export type PlaywrightNewRequestOptions = {
key?: Binary,
passphrase?: string,
pfx?: Binary,
noCertificate?: boolean,
}[],
maxRedirects?: number,
httpCredentials?: HttpCredentials[],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ class SocksProxyConnection {
this._firstPackageReceived = true;
// 0x16 is the TLS "handshake" content type. Only intercept it when the origin has a client
// certificate; otherwise pass the connection through so the browser talks TLS to the server directly.
if (data[0] === 0x16 && this.socksProxy.secureContextMap.has(normalizeOrigin(`https://${this.host}:${this.port}`)))
this._establishTlsTunnel(this._browserEncrypted, data, this.socksProxy.secureContextMap.get(normalizeOrigin(`https://${this.host}:${this.port}`)));
const secureContext = data[0] === 0x16 ? this.socksProxy.secureContextMap.get(normalizeOrigin(`https://${this.host}:${this.port}`)) : undefined;
if (secureContext)
this._establishTlsTunnel(this._browserEncrypted, data, secureContext);
else
this._establishPlaintextTunnel(this._browserEncrypted);
}
Expand All @@ -176,7 +177,7 @@ class SocksProxyConnection {
this._serverEncrypted.pipe(browserEncrypted);
}

private _establishTlsTunnel(browserEncrypted: stream.Duplex, clientHello: Buffer, secureContext?: tls.SecureContext) {
private _establishTlsTunnel(browserEncrypted: stream.Duplex, clientHello: Buffer, secureContext: tls.SecureContext) {
const browserALPNProtocols = parseALPNFromClientHello(clientHello) || ['http/1.1'];
debugLogger.log('client-certificates', `Browser->Proxy ${this.host}:${this.port} offers ALPN ${browserALPNProtocols}`);

Expand Down Expand Up @@ -279,7 +280,7 @@ export class ClientCertificatesProxy {
_socksProxy: SocksProxy;
private _connections: Map<string, SocksProxyConnection> = new Map();
ignoreHTTPSErrors: boolean | undefined;
secureContextMap: Map<string, tls.SecureContext | undefined> = new Map();
secureContextMap: Map<string, tls.SecureContext> = new Map();
private _proxy: types.ProxySettings | undefined;

private constructor(
Expand Down Expand Up @@ -322,7 +323,7 @@ export class ClientCertificatesProxy {

_initSecureContexts(clientCertificates: types.BrowserContextOptions['clientCertificates']) {
// Step 1. Group certificates by origin.
const origin2certs = new Map<string, NonNullable<types.BrowserContextOptions['clientCertificates']>>();
const origin2certs = new Map<string, types.BrowserContextOptions['clientCertificates']>();
for (const cert of clientCertificates || []) {
const origin = normalizeOrigin(cert.origin);
const certs = origin2certs.get(origin) || [];
Expand All @@ -332,13 +333,6 @@ export class ClientCertificatesProxy {

// Step 2. Create secure contexts for each origin.
for (const [origin, certs] of origin2certs) {
const noCertificateCount = certs.filter(cert => cert.noCertificate).length;
if (noCertificateCount > 0 && noCertificateCount < certs.length)
throw new Error(`clientCertificates for origin "${origin}" mix noCertificate with a real certificate`);
if (noCertificateCount > 0) {
this.secureContextMap.set(origin, undefined);
continue;
}
try {
this.secureContextMap.set(origin, tls.createSecureContext(convertClientCertificatesToTLSOptions(certs)));
} catch (error) {
Expand Down
Loading
Loading