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

[Feature]: Support client certificates signed with a private certificate authority #33414

Open
mbucc opened this issue Nov 3, 2024 · 7 comments

Comments

@mbucc
Copy link

mbucc commented Nov 3, 2024

🚀 Feature Request

In our dev environment, we use client certificates that are signed by an in-house CA.

When I try to use one of these client certificates with Playwright , it fails with the error:

2024-11-01T23:56:56.904Z pw:client-certificates Proxy->Target dev.example.com:443 chooses ALPN http/1.1
2024-11-01T23:56:56.914Z pw:client-certificates Proxy->Target dev.example.com:443 chooses ALPN http/1.1
2024-11-01T23:56:56.918Z pw:client-certificates Browser->Proxy dev.example.com:443 chooses ALPN http/1.1
2024-11-01T23:56:56.933Z pw:client-certificates error when connecting to target: 540D0000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:c:\ws\deps\openssl\openssl\ssl\record\rec_layer_s3.c:1590:SSL alert number 40 
2024-11-01T23:56:56.953Z pw:client-certificates Proxy->Target dev.example.com:443 chooses ALPN http/1.1
2024-11-01T23:56:56.961Z pw:client-certificates Proxy->Target dev.example.com:443 chooses ALPN http/1.1
2024-11-01T23:56:56.965Z pw:client-certificates Browser->Proxy dev.example.com:443 chooses ALPN http/1.1

My best guess is that the proxy rejects our client cert because it is not signed by one of the "well-known CAs curated by Mozilla".

If this guess is correct, and Playwright allowed us to override the trusted CA certificates (by passing the ca argument from the client certificate to the tls.createSecureContext call), then I believe our setup should work.

FWIW these certs did work with Cypress.

Example

(We are using the Java wrapper around Playwright.)

package com.example.playwright.java.poc;


import com.microsoft.playwright.*;
import com.microsoft.playwright.options.ClientCertificate;

import java.nio.file.Paths;
import java.util.Collections;

public class Main {
	public static void main(String[] args) {
		
		Playwright playwright = Playwright.create();
		BrowserType.LaunchOptions lop= new BrowserType.LaunchOptions();
		lop.setHeadless(false);
		BrowserType browsertype = playwright.chromium();
		Browser browser = browsertype.launch(lop);
		
		String origin = "https://dev.example.com:443";
		
		ClientCertificate cert = new ClientCertificate(origin);
		cert.setPfxPath(Paths.get("client-cert.p12"));
		cert.setPassphrase("foo");
		
		var browserOptions = new Browser
			.NewContextOptions()
			.setClientCertificates(Collections.singletonList(cert))
			.setIgnoreHTTPSErrors(true);
		
		BrowserContext context = browser.newContext(browserOptions);
		
		Page page = context.newPage();
		page.navigate(origin + "/foo/");
			
	}
}

Motivation

Improve support for running tests against servers that require client certificates.

@mbucc
Copy link
Author

mbucc commented Nov 3, 2024

A simpler solution might be to turn off validation of client certs on the proxy. Why does the Playwright proxy care if I use an invalid client certificate?

@mxschmitt
Copy link
Member

2024-11-01T23:56:56.933Z pw:client-certificates error when connecting to target: 540D0000:error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:c:\ws\deps\openssl\openssl\ssl\record\rec_layer_s3.c:1590:SSL alert number 40

alert number 40 usually because the client or the server is way too old, only supporting removed protocols/ciphers. The error also suggests that something around SNI goes wrong.

What http server are you using?

I saw you are navigating to https://dev.example.com:443 which is interesting - have you considered navigating to just https://dev.example.com?

I think it only supports TLSv1.2 and TLSv13. Not entirely sure about ciphers. Usually we align with modern Node.js which aligns with recent OpenSSL - so this worth checking if your server is e.g. using old ciphers. Using cURL might yield something useful for debugging as well:

curl --cert client.crt --key client.key --cacert ca.crt https://myserver.internal.net:443

@mbucc
Copy link
Author

mbucc commented Nov 4, 2024

What http server are you using?

Apache reverse proxy, which is configured with

SSLProtocol -all +TLSv1.2
SSLCipherSuite TLSv1.2:+HIGH:+MEDIUM:!LOW:!aNULL

Which I understand to mean TLS1.2 or higher, and at least a 128-bit cipher.

When we use the client certificate from a browser, it works fine. Apache logs the message

ssl_engine_kernel.c(806): [client 10.145.180.127:54310] "AH02226: Awaiting re-negotiation handshake"

and then the handshake succeeds.

When using Playwright, the Apache logs report the same message as above, and then

 ssl_engine_kernel.c(824): [client "SSL Library Error: error:140890C7:SSL routines:ssl3_get_client_certificate:peer did not return a certificate -- No CAs known to server for verification?"

Which made me think the certificate was not making it to Apache.

Does Playwright inject a forward proxy between the test code and Apache? Based on the Browser->Proxy Playwright debug output just before the error, I was guessing it was the handshake between the browser and the injected proxy that was failing the TLS handshake, not Apache. Is this not the correct understanding?

But to your point, the client certificates are using older encryption. First I'll try curl and then try creating a newer client certificate.

@mbucc
Copy link
Author

mbucc commented Nov 4, 2024

I decided to start fresh, using your notes in tests/assets/client-certificates/README.md as a guide.

  1. Created new certificate authority.
  2. Created new client certificate.
  3. Signed the new client cert with the new CA.
  4. Added public key for the CA to Apache and restarted.
  5. Imported the client cert to Windows.
  6. Opened a new Chrome incognito window, opened our app URL, and picked the new client cert.
  7. Verified the request made it past Apache to the application.

After all that, when I tried the POC code above this time using the new client certificate, I get the same error.

@mxschmitt
Copy link
Member

Great debugging! Your Apache configuration does not include TLSv1.3, you'd need to do:

SSLProtocol -all +TLSv1.2 +TLSv1.3

The + means enable the protocol rather than disable.

 ssl_engine_kernel.c(824): [client "SSL Library Error: error:140890C7:SSL routines:ssl3_get_client_certificate:peer did not return a certificate -- No CAs known to server for verification?"

This suggests that no certificates were sent along - maybe the origin does not match? What origin do you specify?

@mbucc
Copy link
Author

mbucc commented Nov 14, 2024

Your Apache configuration does not include TLSv1.3, you'd need to do:

Hi @mxschmitt ,

Thanks for your attention.

Unfortunately, our dev infra is so old that Apache won't start with +TLSv1.3.

Our current workaround is to override tls.createSecureContext() in globalhooks.ts :

tls.createSecureContext = function (...args: any) {
    return originalSecureContext({ ...args, cert,key, passphrase: pass, ciphers: "DEFAULT:@SECLEVEL=0" })
}

Is it correct to say that Playwright won't work unless the server under test supports TLSv1.3?

@mxschmitt
Copy link
Member

mxschmitt commented Nov 14, 2024

Is it correct to say that Playwright won't work unless the server under test supports TLSv1.3?

Playwright aka. Node.js works also with TLSv1.2 - just OpenSSL which Node.js uses under the hood doesn't consider your certificate as secure anymore, since it relies on older non-secure ciphers.


An easier workaround is to put this into your playwright.config.ts:

import tls from "tls";
tls.DEFAULT_CIPHERS = "DEFAULT:@SECLEVEL=0";

Linking #33563 since it seems similar.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants