Search code examples
javaseleniumselenium-webdriverssl-certificatebrowsermob-proxy

Add ssl certificate to selenium-webdriver


I use selenium for end-to-end test with chromeDriver. The websites to test require an ssl certificate. When I manually open the browser, there is a popup that lets me select an installed certificate. Different tests access different URLs and also need different certificates. However, if I run the tests in headless mode, there is no popup. So I need a way to programatically set a certificate (eg. set a .pem file) to be used for the current test.

How can I achieve this? I tried setting up a browserMob proxy which I then configured as a proxy in selenium - however, this does not seem to do anything... Are there better approaches? What am I doing wrong? Here's what I tried:

PemFileCertificateSource pemFileCertificateSource = new PemFileCertificateSource(
        new File("myCertificate.pem"),
        new File("myPrivateKey.pem"),
        "myPrivateKeyPassword");

ImpersonatingMitmManager mitmManager = ImpersonatingMitmManager.builder()
        .rootCertificateSource(pemFileCertificateSource)
        .build();

BrowserMobProxy browserMobProxy = new BrowserMobProxyServer();
browserMobProxy.setTrustAllServers(true);
browserMobProxy.setMitmManager(mitmManager);

browserMobProxy.start(8080);


ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.setProxy(ClientUtil.createSeleniumProxy(browserMobProxy));

WebDriver webDriver = new ChromeDriver(chromeOptions);

// use the webdriver for tests, e.g. assertEquals("foo", webDriver.findElement(...))

Solution

  • So apparantly this is not possible with BrowserMob out of the box. I therefore wrote a proxy extension SeleniumSslProxy that can be plugged into Selenium and adds certificate based authentication to create a HTTPS connection.

    This is how it works:

    • intercept Selenium HTTP requests with BrowserMob
    • setup an SSLContext given a certificate (.pfx file) and password
    • use okhttp to forward the request to the target URL
    • convert the okhttp Response to a netty FullHttpResponse so it can be handled by Selenium

    You can find the code on github. Here's an example how it can be used in Selenium end-to-end tests (also works in headless mode):

    @Before
    public void setup() {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        File clientSslCertificate = new File(
            classLoader.getResource("certificates/some-certificate.pfx").getFile());
        String certificatePassword = "superSecret";
    
        this.proxy = new SeleniumSslProxy(clientSslCertificate, certificatePassword);
        this.proxy.start();
    
        ChromeOptions chromeOptions = new ChromeOptions();
        chromeOptions.setProxy(proxy);
        this.webDriver = new ChromeDriver(chromeOptions);
    }
    
    @Test
    public void pageTitleIsFoo() {
        // given
        String url = "http://myurl.lol";
        // NOTE: do not use https in the URL here. It will be converted to https by the proxy.
    
        // when
        this.webDriver.get(url);
        this.webDriver.manage().timeouts().implicitlyWait(5, SECONDS);
    
        // then
        WebElement title = this.webDriver.findElement(By.className("title"));
        assertEquals("Foo", title.getText());
    }
    
    @After
    public void teardown() {
        this.webDriver.quit();
        this.proxy.stop();
    }
    

    Note that I only used chromeDriver and never tested it with other drivers. Minor adjustments to the SeleniumSslProxy might be necessary to be used with other drivers.