Skip to content

A browser extension to verify the authenticity (PGP signature) of web pages

License

Notifications You must be signed in to change notification settings

tasn/webext-signed-pages

Repository files navigation

Signed Pages

A browser extension to verify the authenticity (PGP signature) of web pages.

GitHub tag Mozilla Add-on Chrome Web Store

Overview

Why?

This extension was originally created to improve the security of the EteSync web app. One of the biggest issues with securing web applications is the fact that the app (JavaScript) is delivered to you every time you open the page. This means that a malicious (or compromised) web server could change the code to steal your supposedly client-side-only and secure data.

This extension solves this by verifying the code really came from the developer. While this doesn't protect you from a malicious developer, it at least brings the security of the web app to a similar level to that of native apps.

How does it work?

Developers sign their web pages using their secure PGP key before uploading the pages to the server (for example, on their development machine). Users add a website's configuration (paths and matching publickey), if not already present. Then, every time the users access the website, the extension will indicate if the HTML pages are correctly signed, and thanks to subresource-integrity, also verify the integrity of external resources.

Usage

Installation

The official extensions represent the current stable release.

Opera users can enable Chrome extensions and then install the Chrome extension.

As a user

All you need to do is install the extension, and from its settings page, add patterns to match pages you'd like to verify, and their corresponding publisher's public key. The developers of those websites must have their pages signed for this extension to work.

Users with the browser extension configured will then see a green shield icon for verified pages, and a red one for pages with a bad or missing signature (assuming they were expected to have one).

For example:

Good signature

Example pages

You can try the following example pages to see how the extension behaves:

Install the extension and add the pattern and pubkey shown in the page from the extension's settings.

As a developer

You need to add a comment at the top of the html file (right after the doctype if exists) that contains the detached PGP signature of the content of the <html> tag after it has been minified with minimized with a specific set of settings.

As you can see, it's a bit involved, so we created a script that does all of this for you. All you need to do is make sure you have a comment at the top of the file that contains the special replace tag like in example.html.

And then just run, on a secure machine, preferably with a PGP key on a separate hardware token:

# Print the signed page to stdout
$ ./page-signer.js input.html

# Print the signed page to a file (can be the same as the input file)
$ ./page-signer.js input.html output.html

It's important that all of the external resources to the page (JS and CSS, whether hosted on the same server, or not) will have subresource integrity correctly set. This way you only need to sign the html page, and the rest will be automatically validated by the browser, ensuring that all of the scripts and styles used in the page are indeed what you expect.

A note on dynamic websites

The page-signer.js tool above was designed to work with static html files, meaning html files that are not generated on the fly by the server. The reason for that is that for the signing to be most effective, pages need to be signed by the author in advance, and can't be done dynamically by the server.

This is perfect for statically generated websites and blogs using tools such as Pelican and Jekyll, or web apps like that draw their dynamic content through JavaScript such as applications created with React, VueJS, Angular and Ember.

There are some workaronuds to dynamic websites work, by for example including dynamic content that doesn't matter like comments in an <iframe>, but those are quite involved and out of scope for this document.

Supported Sites

Adding support is easy. If you are a user and would like a website to be supported, please contact the site's owner and point them to this readme.

If your site already supports Signed Pages please consider adding the following badge (as a link to your settings) to let your users know about it.

Signed Pages Badge

List of websites that support Signed Pages:

Building

Setup the environment needed for this extension and page-signer.js:

npm install

To build the extension for development run:

npm run-script build

To build it for deployment run:

npm run-script package

Technical details

On Firefox, this extension relies on webRequest.filterResponseData which lets it intercept the request and sign the page as transferred by the server, so it can verify the page exactly as sent.

Unfortunately, other browsers don't support this API yet, which means we have to resort to a less clean way of doing it. On other browsers, the extension waits until the DOM has loaded, and just before scripts have been executed to get document.documentElement.outerHTML. This means that on these browsers it only has the ability to verify the <html> tag and its contents.

What makes matters even worse is that browsers don't return the html as delivered, but may mangle it a bit, which means we have to transform the content into a canonical form before signing (and verifying). This forces us to use a minifier on the html.

Be aware that the minifier may have bugs that can cause a page to pass verification while being different! Unlikely, but possible, so watch out for minifier bugs.

Since the same signature needs to work on all browsers, we unfortunately have to minimise the html on Firefox too. This workaround will be removed once the aforementioned filterResponseData is implemented across browsers.

Potential attacks

  • This extension rejects pages with <script> tags outside of the <html> tag, so while this could have potentially been an issue, it has been mitigated.

The whole page, other than the doctype is validated in Firefox since it implements browser.webRequest.filterResponseData.

Other browsers are implemented slightly differently and may be exposed to similar attacks.

Known issues

  • On Firefox, you may need to refresh a page for the first time after installing the extension if the page was already in cache.

FAQ

Can I sign only parts of the page? Or only external JavaScript?

No you can't. Verifying only part of a page would be very useful. One could, for example, automatically verify authorship of blog posts. Unfortunately, because of HTML's flexibility, it's not possible.

Let's first take a look at verifying only external JS. The main problem would be that the page itself could have javascript there (or additional unverified external javascript) that can run and do whatever it wants, so this is obviously not safe. Let's continue with this use case, and just disallow any embedded scripts in the page, or external, unverified javascript. We now have a problem that a malicious server could for example have a div overlay that when clicked triggers javascript. Let's assume for the sake of discussion that we don't require any inline JS and that, so we can just block all of the inline JS using CSP.

Even with the above solved, an attacker can still for example, modify buttons to be forms, rather than AJAX requests (of if already a form, change the target), which means the data will be sent to an attacker controlled server. This is obviously not good. Another thing an attacker could do, is change your announcements, bitcoin addresses, PGP keys, and a variety of other parts. OK, so allowing changing the HTML is a bad idea.

What about CSS? This can also be problematic! An attacker can hide important text, replace text with malicious text (think again, bitcoin, PGP keys and etc) and probably more issues that I haven't considered.

This is why I verify the whole page an suggest using SRI even for CSS. HTML is very complex, so the attack surface is very wide.

Attribution

Icons are based on the following icons: