| 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. |