Reproducing Firefox Bug 1888751

Last year, after clearing my Firefox cache on my laptop, I found that I could no longer login to the Bitwarden browser extension against my self-hosted Vaultwarden configured with a client certificate.

The issue was that the client certificate popup no longer appeared when interacting with the extension.

I confirmed that visiting the URL of the Vaultwarden instance directly (i.e. in a browser tab) still triggers the prompt. But this, alongside reinstalls, restarts and clearing the cache had no effect.

After configuring the instance URL for login, the extension will try to reach the config endpoint (which returns information about server configuration) and immediately fail with 403 Forbidden without (the browser) prompting the user to select the client certificate.

As the laptop isn’t my primary device, and the Chromium version of the extension still worked, I tolerated the occasional annoyance of having to switch between browsers to copy passwords.

A workaround

At some point, possibly because it also stopped working on my main device, this became a much bigger annoyance.

After repeating the basic troubleshooting, I eventually tried to downgrade Firefox to an older version from the package cache.

Given that newer profiles cannot be used on older versions, I needed to reimport the certificate and reinstall the extension. This time, the prompt popped up and I was able to log in.

I then upgraded the browser to the latest stable version, and it continued to work in this logged in state with (what I assume to be) the certificate selection cached.

Reproduced

I ended up taking a closer look at the issue in December last year, with the aim of gathering more evidence to create a bug report.

I was able to reproduce the issue consistently on Firefox 120 and 121, while showing that the extension was working on Firefox 118.

I also created a simple test extension, which fetches a page requiring client authentication when visiting example.com:

{
  "manifest_version": 2,
  "name": "Client Cert test",
  "version": "1.0",
  "description": "Test that client cert works properly in extension.",
  "content_scripts": [
    {
      "matches": ["https://example.com/*"],
      "js": ["test.js"]
    }
  ],
  "permissions": [
    "<all_urls>"
  ]
}
// test.js
document.body.style.border = "5px solid red";
async function test() {
    const x = await fetch("https://<redacted>",
        { credentials: 'include' });
    console.log(x);
}
test();

Since the certificate prompt always pops up for this test extension when visiting example.com, I thought that perhaps a browser API has changed and that this somehow caused the extension to break in newer versions.

Believing that this was a bug with the Bitwarden extension, I created bitwarden/clients Issue #7077.

After trying to edit the code of the extension and failing to fix the issue, I set it aside and hoped that there was enough information for someone else to take a look.

Browser Bug

Unfortunately, the issue remained unsolved for a few months. Having this on my todo list for a while, I decided to investigate further on a free weekend.

After digging around in the code again, and having no luck, I browsed to the extension manifest.

Reading through, I found that there was a background key. After looking this up on MDN, it struck me as something which could make a difference, given that it is used to define background scripts and pages.

Since the extension can be used independently of a particular page or tab, perhaps it will be loaded differently compared to an extension which is activated on a page?

After extending the test app with a background page and adding the background key to the manifest:

"background": {
    "page": "background.html",
    "persistent": true
},
<html>
  <head>
    <meta charset="UTF-8" />
  </head>
  <body></body>
  <script src="test.js"></script>
</html>

I was able to reproduce the bug, but this time with the test extension.

This meant that it wasn’t an extension bug like I had previously thought, but a browser bug instead!

Having this minimal reproducible example and after confirming that it was still working on older versions, I reported Firefox Bug 1888751.

Confirmed

After providing a script to create a client certificate setup (upon request), the bug was confirmed by the triager.

I didn’t follow too closely on the discussion or the fix that followed, being busy with the teaching semester.

From reading the comments and code, my understanding is that the dialog failed to pop up because the variable holding the dialog box browserWindow.gDialogBox was undefined, where browserWindow is the “most recent main window”.

if (!browserWindow?.gDialogBox) {
    browserWindow = Services.wm.getMostRecentBrowserWindow();
}
if (browserWindow) {
    browserWindow.gDialogBox
        .open(clientAuthAskURI, { hostname, certArray, retVals })
        ...;
    return;
}

Then, the fix involved adding a further fallback of opening a new window on top of the most recent window, with the client certificate prompt as a modal.

const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml";

// if cannot find window with browserWindow.gDialogBox...
let mostRecentWindow = Services.wm.getMostRecentWindow("");
Services.ww.openWindow(mostRecentWindow, clientAuthAskURI, "_blank",
    "centerscreen,chrome,modal,titlebar", args);

Conclusion

The fix from the Mozilla team made it to Firefox 126 Beta, and then Firefox 126 Stable on May the 14th, 2024.

After confirming that the fix applied to my workflow, I closed the GitHub issue on the Bitwarden clients repository.

Many thanks to those who were involved!

← Previous Post Next Post →