CryptPad was designed with a view that privacy should be default and cryptography should be invisible. In order to do this, we made use of the web-app model so people could just go to cryptpad.fr and immediately begin using the app, no installation necessary. However, this model has a known flaw, the server can decide what client-side code it will send to any given user, allowing a compromized server to serve code with a back-door vulnerability.
Insecure, some.website can serve you anything:
Secure, only one possible script can be sent by some.website or else the web browser will throw an error and refuse to run the script:
It contains a JSON tree which mirrors the files that are part of the CryptPad codebase and the hashes of the files for the Subresource Integrity check. Once the
version.txt is loaded using a file called
sboot.js. The hash of
sboot.js was included directly into the html files which are cached, so
sboot.js can never be changed at all.
First, the browser loads the html file, the html file contains a single script tag loading
There is a custom attribute called
sboot.js gets loaded, it downloads and then verifies
version.txt which is a signed message containing the CryptPad version number and the hash of
manifest.js. The content that is signed looks something like this:
The version number (85) is not the CryptPad version but rather an auto-incrementing number which is stored in the browser localStorage and prevents the server from downgrading the version of CryptPad. After the signature/version check completes successfully,
manifest.js like the following:
You will notice that the hash is used also in the URL of
manifest.js, this allows the server to signal that the files are immutable and can be cached by the browser forever which makes CryptPad load faster next time.
sboot.js finds the hash of
require.js in the manifest and then manually loads
require.js in the same way. Once
require.js is loaded,
sboot.js configures require to use the hashes from the manifest for every file it loads, then it uses require to load
This file is not needed for security, but unlike
sboot.js, it can easily be changed from release to release and it contains any code which should be run before the main CryptPad code. Things such as additional requirejs configuration and shims for missing browser APIs are placed here. After
boot2.js is complete, it reads the
data-bootload attribute from the html file and invokes require to load that.
While this system provides excellent security, it is still not perfect. If the root html file is compromized then it can alter the chain of trust, or scrap it completely. With a very long cache header, the browser will store the html file essentially forever, but if the user triggers a hard reload with the F5 key, then the cache will be flushed.
The root html file can be signed using pgp and then verified using the signed pages chrome extension. But signed pages is not able to prevent the loading of the website even if the signature is invalid and it only takes 1 second for the keys in localStorage to be leaked.
If the root html file was generated by the server each load, it could contain a secret key which is used to encrypt the keys in the localStorage, thus rendering them unusable if the html file is re-loaded, and meaning that the user must re-enter their password and would then be able to see that the signature on the html file is invalid, however unless signed pages can ignore the key inside of the html file when verifying the signature, it would have to be re-signed every time, pushing the pgp key onto the server, which we are worried about being compromized.