A browser extension to verify the authenticity (PGP signature) of web pages.
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.
The official extensions represent the current stable release.
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).
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.
- A page with a good signature: https://stosb.com/~tom/signed-pages/good.html
- A page with a bad signature: https://stosb.com/~tom/signed-pages/bad.html
- A page with a missing (but expected) signature: https://stosb.com/~tom/signed-pages/missing.html
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
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.
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.
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.
List of websites that support Signed Pages:
Setup the environment needed for this extension and
To build the extension for development run:
npm run-script build
To build it for deployment run:
npm run-script package
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.
- 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
Other browsers are implemented slightly differently and may be exposed to similar attacks.
- On Firefox, you may need to refresh a page for the first time after installing the extension if the page was already in cache.
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.
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.
Icons are based on the following icons: