Mattermost End-to-End Encryption Plugin


Until summer 2021, Quarkslab was still a hardcore user of IRC. After many years of loyal service, we finally decided to migrate to a more modern system: Mattermost. Mattermost is often described as an open source Slack, and matched many of the new criteria we wanted. Only one feature was missing: end-to-end encryption (E2EE).

End-to-end encryption allows messages to be authenticated, encrypted and decrypted on the users' devices. This means that the server only sees encrypted messages it can't decrypt. It also guarantees the identity of the sender of the messages, so that the server cannot impersonate them. This brings privacy to Quarkslab's employees, and can help prevent information leakage. These principles are widely used in messaging apps like Signal, Olvid or WhatsApp.

This blog post discusses the attack models we consider, some prior art, the design of this plugin, current known limitations and future work.

The plugin is open source on Github: You can follow these instructions to install it on your own Mattermost instance. A comprehensive end-user documentation is also provided.

Attack models

Passive attacker

In this attack model, we consider that the attacker either has access to the data that the server receives (but won't modify them), or can somehow read the database of the Mattermost server (but can't/won't write anything to it).

In this model, end-to-end encryption is efficient, as the server does not own any secret to decrypt the transmitted posts. Also, it can't inject malicious Javascript as in the active attacker model described below.

Active attacker

In this attack model, the attacker has full control over the server, or communications between clients and the server. It means that it can, among other things:

  1. deliver fake public keys for some users (some form of MiTM), and get messages encrypted for a private key he owns
  2. deliver compromised Javascript to clients

We discuss these issues in details in the following sections.

Existing work

An end-to-end encryption plugin has already been written, named anonymous.

We didn't use this plugin for various reasons:

  • It uses the node-rsa module, which is a pure JS implementation of the RSA algorithm. It can't have the nice non extractibility property that the WebCrypto API has.
  • The choice of whether a message is encrypted or not is done per message by the user. We wanted a per-channel and server-enforced configuration. Moreover, there's no UI difference between an encrypted message and a classical one.
  • No authentication is done on the sent messages.

That being said, this plugin has been a great inspiration to us!

Cryptographic scheme & compromises

There are a few problems common to all E2EE systems:

  1. How to make it work on all the user's devices in the most transparent way?
  2. How the public keys of each user are stored & trusted.

Let's discuss them!

Sharing a private key among multiples devices

There are multiple ways to fix this first problem. The naive one is to find a way to copy/paste the private key of the user between devices. Another option (used by some messaging applications) is to use a device as a relay to the other ones, that have their own ephemeral private keys.

In our case, there is also the fact that our users want to be able to recover their backlog from any devices, even if all of them are lost. The problem is thus reduced to how securely backup the user's Mattermost private key, and make it easy to recover that backup and import it back into devices.

So we had to find a solution that would make it easy to use for Quarkslab's employees, while being secure. As it turns out, literally every one at Quarkslab has a working GPG setup inside its mail client. So we decided to leverage that opportunity by encrypting a backup of their Mattermost private key at generation time using their GPG public key. In such a way, having access to their mail account and GPG key, they can easily recover their Mattermost private key. The diligent reader would have noticed that, in the end, we have transferred the responsibility of backuping the Mattermost private key to the GPG one :)

In practice, the user's public key is fetched by the Mattermost web app plugin, using a server-side configurable HKP URL. With this GPG public key, it encrypts the freshly generated Mattermost private key and sends this encrypted backup to the server, which in turns sends it to the user by mail. Meanwhile, (s)he has been prompted to acknowledge that the HKP server URL and the gathered GPG public key fingerprint are legit (in case the server served a malicious HPK server URL).

We can also notice that this backup can be useful if the IndexedDB storage of a browser is wiped for whatever reason, or if the user wants to use another browser or Mattermost application, even on the same device.

It's worth mentioning that all this process is not mandatory, and is disabled if no HKP URL is set. In any case, the private key of the user is always displayed upon creation, giving them the opportunity to backup it manually.

As a final note on this matter, this solution is clearly tailored to Quarkslab's internal organisation. There are many other potential ideas to solve this, and we are open to discussions on that matter!

Public keys storage & trust

The second general problems boils down to trust: if we consider the server to be evil, how can we be sure it is serving legitimate public keys? GPG for instance solves this problem with a web of trust, having each individual digitally sign other individuals' public keys as they trust them, creating a graph of trusted relationships.

In our case, we inspired ourselves with what applications like WhatsApp or Signal do, which can be summarized by Trust On First Use (TOFU). The idea is that each device starts with an empty public key database, and fills it as it gathers users' public keys from the server. If at some point one of them changes (meaning that the server returns a different one for a known user), the web app plugin shows a message to alert the user. This is the equivalent of the "Security code has changed" message of WhatsApp for instance.

Cryptographic protocol

For now, we have one encryption mode, that we call P2P. In this mode, each message is encrypted for each member of the channel and then signed. There is no per-channel encryption key shared among the participants. We might develop this channel shared key [1] [2] mode in the future.

The complete protocol is described in a design document.


There is a lot more that could be said on all these problems, and it will deserve a complete blog post of its own, describing all the various solutions that already exist. Stay tuned!

Let's now describe some currently known limitations.

The web app integrity problem

In the active attack model - where the server is considered as compromised -, nothing prevents the server from serving malicious Javascript code that could send clear text messages alongside their encrypted counterpart. Moreover, code could also be injected to decrypt old messages. This problem is described in detail here.

Note that, as the private key is kept as non extractable in the browser, even malicious Javascript code can't just extract and dump your private key. It would need to exploit a vulnerability in the browser itself (but you might have bigger issues at this moment).

The Firefox Page Integrity plugin could help solve this problem, by checking that the shipped Mattermost web application is known against a list of known hashes. Unfortunately (in this case), Mattermost plugins' Javascript code is dynamically loaded by the main Mattermost application, so bypassing this check in some ways.

Fixing this is work-in-progress, and any help or suggestions would be appreciated! We track progress on this matter in this ticket.

Other limitations

The plugin has other known UX limitations, that are explained in detail in the project's README:

Future work

Future work will mainly focus on fixing the known limitations. We will also work to improve the overall user experience, especially around private key generation and/or import.

We also have some idea to make sure we don't use too much crypto (and still be secure obviously), and try to reduce the CPU usage while encrypting/decrypting messages.


We'd like to greatly thank the Mattermost community & developers for having answered our questions while developing this plugin. We especially appreciated the patch that will allow us to implement encrypted messages edition!

Also thanks to my colleagues Béatrice Creusillet, Charlie Boulo, Laurent Grémy, Angèle Bossuat & Damien Aumaitre for their valuable comments & reviews of this blog post!


[2] (page 6-7)

Article Link: