In this blog post, you will learn how to configure your email server to encrypt all incoming emails using user's GPG public keys (when it exists). This will prevent anyone from reading the emails, except if you own the according GPG private key. This is known as "encryption at rest".
This setup, while effective, has limitations. Headers will not be encrypted, search in emails will break as the content is encrypted, and you obviously need to have the GPG private key available when you want to read your emails (if you read emails on your smartphone, you need to decide if you really want your GPG private key there).
Encryption is CPU consuming (and memory too for emails of a considerable size), I tried it on an openbsd.amsterdam virtual machine, and it was working fine until someone sent me emails with 20MB attachments. On a bare-metal server, there is absolutely no issue. Maybe GPG makes use of hardware acceleration cryptography, and it is not available in virtual machines hosted under the OpenBSD hypervisor vmm.
This is not an original idea, Etienne Perot wrote about a similar setup in 2012 and enhanced the `gpgit` script we will use in the setup. While his blog post is obsolete by now because of all the changes that happened in Dovecot, the core idea remains the same. Thank you very much Etienne for your job!
Etienne Perot: Encrypt specific incoming emails using Dovecot and Sieve
This guide is an extension of my recent email server setup guide:
2024-07-24 Full-featured email server running OpenBSD
This setup is useful to protect your emails stored on the IMAP server. If the server or your IMAP account are compromised, the content of your emails will be encrypted and unusable.
You must be aware that emails headers are not encrypted: recipients / senders / date / subject will remain in clear text even after encryption. If you already use end-to-end encryption with your recipients, there are no benefits using this setup.
An alternative is to not let any emails on the IMAP server, although they could be recovered as they are written in the disk until you retrieve them.
Personally, I keep many emails of my server, and I am afraid that a 0day vulnerability could be exploited on my email server, allowing an attacker to retrieve the content of all my emails. OpenSMTPD had critical vulnerabilities a few years ago, including a remote code execution, so it is a realistic threat.
I wrote a privacy guide (for a client) explaining all the information shared through emails, with possible mitigations and their limitations.
IVPN: The Technical Realities of Email Privacy
This setup makes use of the program `gpgit` which is a Perl script encrypt emails received over the standard input using GPG, it is a complicated task because the email structure can be very complicated. I have not been able to find any alternative to this script. In gpgit repository there is a script to encrypt an existing mailbox (maildir format), that script must be run on the server, I did not test it yet.
You will configure a specific sieve rule which is "global" (not user-defined) that will process all emails before any other sieve filter. This sieve script will trigger a `filter` (a program allowed to modify the email) and pass the email on the standard input of the shell script `encrypt.sh`, which in turn will run `gpgit` with the according username after verifying a gnupg directory existed for them. If there is no gnupg directory, the email is not encrypted, this allows multiple users on the email server without enforcing encryption for everyone.
If a user has multiple addresses, this is the system account name that is used in the local part of the GPG key address.
Some packages are required for gpgit to work, they are all available on OpenBSD:
pkg_add p5-Mail-GnuPG p5-List-MoreUtils
Download gpgit git repository and copy its `gpgpit` script into `/usr/local/bin/` as an executable:
cd /tmp/ git clone https://github.com/EtiennePerot/gpgit cd gpgit install -o root -g wheel -m 555 gpgit /usr/local/bin/
All the following paths will be relative to the directory `/usr/local/lib/dovecot/sieve/`, you can `cd` into it now.
Create the file `encrypt.sh` with this content, replace the variable `DOMAIN` with the domain configured in the GPG key:
#!/bin/sh DOMAIN="puffy.cafe" NOW=$(date +%s) DATA="$(cat)" if test -d ~/.gnupg then echo "$DATA" | /usr/local/bin/gpgit "${USER}@${DOMAIN}" NOW2=$(date +%s) echo "Email encryption for user ${USER}: $(( NOW2 - NOW )) seconds" | logger -p mail.info else echo "$DATA" echo "Email encryption for user for ${USER} none" | logger -p mail.info fi
Make the script executable with `chmod +x encrypt.sh`. This script will create a new log line in your email logs every time an email is processed, including the username and the time required for encryption (in case of encryption). You could extend the script to discard the `Subject` header from the email if you want to hide it, I do not provide the implementation as I expect this task to be trickier than it looks like if you want to handle all corner cases.
Create the file `global.sieve` with the content:
require ["vnd.dovecot.filter"]; filter "encrypt.sh";
Compile the sieve rules with `sievec global.sieve`.
Edit the file `/etc/dovecot/conf.d/90-plugin.conf` to add the following code within the `plugin` block:
sieve_filter_bin_dir = /usr/local/lib/dovecot/sieve sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment +vnd.dovecot.filter sieve_before = /usr/local/lib/dovecot/sieve/global.sieve sieve_filter_exec_timeout = 200s
You may have `sieve_global_extensions` already set, in that case update its value.
The variable `sieve_filter_exec_timeout` allows the script `encrypt.sh` to run for 200 seconds before being stopped, you should adapt the value to your system. I came up with 200 seconds to be able to encrypt email with 20MB attachments on an openbsd.amsterdam virtual machine. On a bare metal server with a Ryzen 5 CPU, it takes less than one second for the same email.
The full file should look like the following (in case you followed my previous email guide):
## ## Plugin settings ## # All wanted plugins must be listed in mail_plugins setting before any of the # settings take effect. See <doc/wiki/Plugins.txt> for list of plugins and # their configuration. Note that %variable expansion is done for all values. plugin { sieve_plugins = sieve_imapsieve sieve_extprograms # From elsewhere to Spam folder imapsieve_mailbox1_name = Spam imapsieve_mailbox1_causes = COPY imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.siev # From Spam folder to elsewhere imapsieve_mailbox2_name = * imapsieve_mailbox2_from = Spam imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve # for GPG encryption sieve_filter_bin_dir = /usr/local/lib/dovecot/sieve sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment +vnd.dovecot.filter sieve_before = /usr/local/lib/dovecot/sieve/global.sieve sieve_filter_exec_timeout = 200s }
Open the file `/etc/dovecot/conf.d/10-master.conf` and uncomment the variable `default_vsz_limit` and set its value to `1024M`. This is required as GPG uses a lot of memory and without this, the process will be killed and the email lost. I found 1024M to works with attachments up to 45 MB, however you should raise this value higher value if you plan to receive bigger attachments.
Restart dovecot to take account of the changes: `rcctl restart dovecot`.
You need to create a GPG keyring for each users you want use encryption, the simplest method is to setup a passwordless keyring and import your public key:
$ gpg --quick-generate-key --passphrase '' --batch "$USER" $ gpg --import public-key-file.asc $ gpg --edit-key FINGERPRINT_HERE gpg> sign [....] gpg> save
If you want to disable GPG encryption for the user, remove the directory `~/.gnupg`.
If you use a spam filter such as rspamd or spamassassin relying on bayes filter, it will only work if it process the emails before arriving at dovecot, for instance in my email setup this is the case as rspamd is a filter of opensmtpd and pass the email before being delivered to Dovecot.
Such service can have privacy issues, especially if you use encryption. Bayes filter works by splitting an email content into tokens (not really words but almost) and looking for patterns using these tokens, basically each emails is split and stored in the anti-spam local database in small parts. I am not sure one could recreate the emails based on tokens, but if someone like an attacker is able to access the token list, they may have some insights about your email content. If this is part of your threat model, disable your anti-spam Bayes filter.
This setup is quite helpful if you want to protect all your emails on their storage. Full disk encryption on the server does not prevent anyone able to connect over SSH (as root or the email user) from reading the emails, even file recovery is possible when the volume is unlocked (not on the real disk, but the software encrypted volume), this is where encryption at rest is beneficial.
I know from experience it is complicated to use end-to-end encryption with tech-savvy users, and that it is even unthinkable with regular users. This is a first step if you need this kind of security (see the threat model section), but you need to remember a copy of all your emails certainly exist on the servers used by the persons you exchange emails with.