Skip to content

Sitekey signature validation results from core are being misread in webext

In frame-state.js, we call verifySignature from core like this:

if (!verifySignature(key, signature, data))
    return null;

However this is wrong, because verifySignature returns a Promise.

Background

I discovered this while working on https://gitlab.com/eyeo/adblockplus/abc/webext-sdk/-/issues/221. The two bugs are not related, even though they both happen to touch sitekeys.

Unfortunately, if sitekey verification is async, then our webRequest callbacks need to be async, which is not supported by Chrome (see the notes for onHeadersReceived on the MDN browser compatibility table), so this may require extra effort or rethinking sitekeys to fix (which is why I'm not just fixing it as part of https://gitlab.com/eyeo/adblockplus/abc/webext-sdk/-/issues/221). This is especially a problem for CSP filters, which need to use the sitekey from an onHeadersReceived event inside that same onHeadersReceived event.

Environment

  • EWE 0.4.0

Steps to reproduce

  1. Load the test extension
  2. Add a sitekey filter
  3. Open a page which returns that sitekey, but with an invalid signature
  4. Observe if the sitekey filter is applied

Actual behavior

The sitekey filter is applied.

Expected behavior

The sitekey filter is not applied because the website presented an invalid signature.

Extra information

The following middleware can be added to start-server.js to make it return badly signed sitekeys when you add ?invalid-sitekey=1 to the url.

async function invalidSitekeyHeader(req, res, next) {
  if (req.query["invalid-sitekey"]) {
    let pem = await fs.promises.readFile(path.join(dirname, "sitekey.pem"));
    let privateKey = crypto.createPrivateKey(pem);
    let publicKey = crypto.createPublicKey(privateKey);
    let spki = publicKey.export({type: "spki", format: "der"});
    let data = "this data to sign isn't the right data to sign";
    let signature = crypto.sign("rsa-sha1", Buffer.from(data), privateKey);
    let value = `${spki.toString("base64")}_${signature.toString("base64")}`;
    res.header("X-Adblock-Key", value);
  }

  next();
}

...

app.use(invalidSitekeyHeader);

And this sitekey test can be added to test/request-filter.js

    it("blocks a request if sitekey signature is invalild", async function() {
      await addFilter("/image.png^$image");
      await addFilter(`@@$sitekey=${SITEKEY}`);
      let page = new Page("image.html?invalid-sitekey=1");
      await page.expectResource("image.png").toBeBlocked();
    });