Title: Emails encryption at rest on OpenBSD using dovecot and GPG | |
Author: Solène | |
Date: 14 August 2024 | |
Tags: security privacy emails openbsd | |
Description: In this blog post, you will learn how to configure your | |
email server to automatically encrypt incoming emails using GPG. | |
# Introduction | |
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 | |
gpgit GitHub project page | |
gpgit mirror on tildegit.org | |
This guide is an extension of my recent email server setup guide: | |
2024-07-24 Full-featured email server running OpenBSD | |
# Threat model | |
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 | |
# Setup | |
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. | |
## GPGit | |
Some packages are required for gpgit to work, they are all available on | |
OpenBSD: | |
```shell | |
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/ | |
``` | |
## Sieve | |
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: | |
```sh | |
#!/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" | logge… | |
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: | |
```sieve | |
require ["vnd.dovecot.filter"]; | |
filter "encrypt.sh"; | |
``` | |
Compile the sieve rules with `sievec global.sieve`. | |
## Dovecot | |
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.dov… | |
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.dov… | |
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`. | |
## User GPG setup | |
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`. | |
## Anti-spam service | |
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. | |
# Conclusion | |
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. |