├── img ├── gmail_score.jpg └── mail_score.jpg ├── .github └── FUNDING.yml ├── LICENSE └── README.md /img/gmail_score.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3cod3/EndtoEndEncryptedMailServer/HEAD/img/gmail_score.jpg -------------------------------------------------------------------------------- /img/mail_score.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3cod3/EndtoEndEncryptedMailServer/HEAD/img/mail_score.jpg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: d3cod3 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: d3cod3 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # End-to-end (E2EE) Encrypted Email Server 3 | 4 | 5 | Table of Contents 6 | ================= 7 | 8 | * [Description](#description) 9 | * [Sources](#sources) 10 | * [DNS](#dns) 11 | * [Encryption](#encription) 12 | * [ENCRYPT the Mail Store](#encrypt-the-mail-store) 13 | * [Postfix](#postfix) 14 | * [SSL / Let's Encrypt](#ssl-lets-encrypt) 15 | * [Dovecot](#dovecot) 16 | * [GPGIT](#gpgit) 17 | * [Anti-Spam](#anti-spam) 18 | * [SPF](#spf) 19 | * [Amavis](#amavis) 20 | * [Postgray](#postgray) 21 | * [OpenDKIM](#opendkim) 22 | * [Spamassassin](#spamassassin) 23 | * [Anonymize headers](#anonymize-headers) 24 | * [FAIL2BAN](#fail2ban) 25 | * [IPTABLES](#iptables) 26 | * [Router Settings](#router-settings) 27 | * [Testing](#testing) 28 | * [Conclusions](#conclusions) 29 | 30 | 31 | 32 | # Description 33 | 34 | Secure (reasonably) host your own e-mail accounts, as e-mail was originally designed to work! 35 | 36 | Let's make ourselves more independent from corporations, from others minding our own business, and most important, re-gain control of our personal communication over the web, in this case specifically over e-mail. 37 | 38 | Yes, those electronic mail boxes that others maintain for us, the technical infrastructure that give us this ability to communicate instantly with everyone all over the planet, this last revision of what long ago was smoke messages, or carrier pigeons, through the centuries of postal systems, till the actual technology, where we do have, now, nor knowledge or control about every part of the entire mechanism, but hey, they give it to us for free!!! 39 | 40 | So 41 | 42 | Corporations and governments read and/or store all our emails, plus, we can't even complain about it anymore (from august 2013), and that doesn't mean necessary "spying" until it eventually became that. 43 | 44 | And if you're ok with it, then you don't need this tutorial 45 | 46 | But in case you are not, good news, i'm going to explain here how to set up an End-to-end encrypted ([E2EE](https://ssd.eff.org/en/glossary/end-end-encryption)) email server, and hosting it in your personal server at home. 47 | I'm assuming here you know how to configure a reasonably secure server at home, but if you don't, you can check my [Raspbian Secure Server Config Tutorial](https://github.com/d3cod3/raspbian-server) first. 48 | 49 | # Sources 50 | 51 | This is a list of sources and other articles i've used to learn, prototype and realize what i'll try to explain in detail in this tutorial. 52 | 53 | Thanks to all this people for the help and knowledge: 54 | 55 | [http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/](http://sealedabstract.com/code/nsa-proof-your-e-mail-in-2-hours/) 56 | 57 | [https://scaron.info/blog/debian-mail-postfix-dovecot.html](https://scaron.info/blog/debian-mail-postfix-dovecot.html) 58 | 59 | [https://appbead.com/blog/how-to-setup-mail-server-on-debian-8-jessie-with-postfix-dovecot-1.html](https://appbead.com/blog/how-to-setup-mail-server-on-debian-8-jessie-with-postfix-dovecot-1.html) 60 | 61 | [https://appbead.com/blog/how-to-setup-mail-server-on-debian-8-jessie-with-postfix-dovecot-2.html](https://appbead.com/blog/how-to-setup-mail-server-on-debian-8-jessie-with-postfix-dovecot-2.html) 62 | 63 | [https://www.digitalocean.com/community/tutorials/how-to-set-up-a-postfix-email-server-with-dovecot-dynamic-maildirs-and-lmtp](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-postfix-email-server-with-dovecot-dynamic-maildirs-and-lmtp) 64 | 65 | [https://security.stackexchange.com/questions/81944/perfectly-secure-postfix-mta-smtp-configuration](https://security.stackexchange.com/questions/81944/perfectly-secure-postfix-mta-smtp-configuration) 66 | 67 | [https://scaron.info/blog/debian-mail-spf-dkim.html](https://scaron.info/blog/debian-mail-spf-dkim.html) 68 | 69 | [https://www.upcloud.com/support/secure-postfix-using-lets-encrypt/](https://www.upcloud.com/support/secure-postfix-using-lets-encrypt/) 70 | 71 | [https://gist.github.com/jkullick/bbb36828a1f413abd6b9d6431bafa54b](https://gist.github.com/jkullick/bbb36828a1f413abd6b9d6431bafa54b) 72 | 73 | [https://www.void.gr/kargig/blog/2013/11/24/anonymize-headers-in-postfix/](https://www.void.gr/kargig/blog/2013/11/24/anonymize-headers-in-postfix/) 74 | 75 | [http://kacangbawang.com/encrypting-stored-email-with-postfix/](http://kacangbawang.com/encrypting-stored-email-with-postfix/) 76 | 77 | [https://www.grepular.com/Automatically_Encrypting_all_Incoming_Email](https://www.grepular.com/Automatically_Encrypting_all_Incoming_Email) 78 | 79 | [https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-9-4-on-debian-8](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-9-4-on-debian-8) 80 | 81 | [https://www.void.gr/kargig/blog/2013/11/24/anonymize-headers-in-postfix/](https://www.void.gr/kargig/blog/2013/11/24/anonymize-headers-in-postfix/) 82 | 83 | 84 | 85 | # DNS 86 | 87 | So, we'll start with the DNS records, and obviously we need a domain, we can buy one through some hosting platform, or we can obtain one for free (with some limitations, on the name for example). Take a look at [FreeDNS](https://freedns.afraid.org/) if you want to learn more about free DNS hosting and domain hosting. 88 | So we'll use here an example domain called _supersecure.mydomain.net_, and just to avoid confusion, a possible mail address could be: 89 | _astronaut57@supersecure.mydomain.net_. 90 | We are using here a different subdomain to avoid the disruption of the standard mail service usually packed with standard hosting services, for example _mymail@mydomain.net_, so we will maintain the standard mail service on our domain and add a new super-secure encrypted one on a subdomain. 91 | When we have our domain, we'll just need to configure it to point to our public IP address: 92 | 93 | - **DNS A** record, that maps your domain name to the IP address 94 | ```bash 95 | supersecure A YOUR_IPv4_ADDRESS 96 | ``` 97 | 98 | - **MX** record, which tells to the others mail servers where deliver mails 99 | ```bash 100 | supersecure MX supersecure.mydomain.net 101 | ``` 102 | 103 | - **TXT/SPF** record, a Sender Policy Framework (SPF) record in order to not be considered a spammer 104 | ```bash 105 | default._domainkey.supersecure TXT v=DKIM1; h=sha256; p=v=DKIM1;h=sha256;k=rsa;p=YOUR_DKIM_KEY; s=email; t=s 106 | supersecure txt v=DKIM1;h=sha256;k=rsa;p=YOUR_DKIM_KEY 107 | supersecure TXT v=spf1 a mx ip4:YOUR_IPv4_ADDRESS ip6:YOUR_IPv6_ADDRESS ~all 108 | ``` 109 | 110 | - **TXT/SPF** record, DMARC record in order to not be considered a spammer by big e-mail providers (google, yahoo, etc...) 111 | ```bash 112 | supersecure TXT v=DMARC1; p=none 113 | _dmarc.supersecure TXT v=DMARC1; p=none 114 | ``` 115 | 116 | Regarding the **YOUR_DKIM_KEY** field, we'll work on that later, when configuring [OpenDKIM](#opendkim) (Domain Keys Identified Mail sender authentication system), so you can wait for that, or jump to the section and close the DNS records config right now, your choice! 117 | 118 | That's a standard scenario, feel free to customize yours! 119 | 120 | Usually DNS propagation takes a while, so it could be useful to set it at the beginning. 121 | 122 | One easy step more, edit the file _/etc/mailname_ 123 | 124 | ```bash 125 | sudo nano /etc/mailname 126 | ``` 127 | 128 | and enter your selected mail domain name, in our case _supersecure.mydomain.net_ 129 | 130 | Let's start talking about encryption! 131 | 132 | # ENCRYPTION 133 | 134 | Ok, this step is delicate, so i'll start describing what we're trying to achieve here: 135 | first i want an encrypted mail store for my mail server, but most important, i want every mail account associated with a gpg public key to asymmetrically encrypt messages, so only the users, with his/her private gpg key, will be able to decrypt and read their messages, plus, in case of server compromised, the attacker will own only a bunch of gpg encrypted texts! 136 | 137 | So, before we start implementing this, a little reminder: 138 | 139 | > Even if you're using the best encrypted super secure mail account of the entire universe, when you write to someone that don't, well, your conversation is potentially already compromised. A reasonably secure conversation over the web must be encrypted on both sides. 140 | 141 | If you ended up here not randomly, probably you already know that, but just in case, check this tutorial from Free Software Foundation about [email self-defense](https://emailselfdefense.fsf.org/en/) 142 | 143 | Perfect, let's analyze in depth how and why we'll implement the required conditions to obtain such server encryption level. 144 | 145 | ## ENCRYPT the Mail Store 146 | 147 | I'll start with the easy one, encrypt the mail store, and for that we'll use [gocryptfs](https://nuetzlich.net/gocryptfs/), a project inspired by [EncFS](https://vgough.github.io/encfs/) 148 | 149 | ### WHY 150 | 151 | Someone could be asking, why encrypt the mail store if we are going to asymmetrically encrypt every single message with users public gpg keys? 152 | Well, is actually a redundancy, but let's consider this, on one side we'll learn how to properly configure generic encrypted filesystem in user-space, and on another side, we can consider it as planting some kind of honeypot for possible attackers; imagine someone taking control of our server, with read/write access to our mail store, he/she will rapidly detect the presence of gocryptfs, that, like every filesystem encryption mechanism, is potentially vulnerable to some kind of advanced attacks, so he/she will probably start to try to exploit the gocryptfs vulnerabilities (if your are interested, take a look at this audit [here](https://defuse.ca/audits/gocryptfs.htm)), and after some time, maybe, with some luck, he/she will decrypt the mail store, finding a bunch of asymmetric gpg encrypted messages!!! Wouldn't you like, just in that moment, to see his/her face? 153 | And to go further, we could plant some other kind of hidden side-channel remote logging system over our encrypted mail store, in order to try to extract information about our potential attacker working on our compromised server, but this is just a little far away from the purposes and skill level of this tutorial, so i'll let this particular point in the hands of the interested enthusiastic contributor that will teach us how to implement this properly (thanks in advance!). 154 | 155 | ### HOW 156 | 157 | As always, let's install it: 158 | 159 | ```bash 160 | sudo apt-get install gocryptfs 161 | ``` 162 | 163 | Then create our encrypted filesystem and the mount point, basically we'll create two folders (where you prefer): 164 | 165 | ```bash 166 | mkdir cipher plain 167 | ``` 168 | 169 | Init our encrypted filesystem (our folder for the mailstore): 170 | 171 | ```bash 172 | gocryptfs -init cipher 173 | ``` 174 | 175 | and mount it: 176 | 177 | ```bash 178 | gocryptfs cipher plain 179 | ``` 180 | 181 | We can now try a little test, just listing the content of the folder where we created the two folders, _cypher_ and _plain_ (or whatever names you used) 182 | 183 | ```bash 184 | ls -la 185 | ``` 186 | 187 | And we will obtain 188 | 189 | ```bash 190 | ls: cannot access plain: Permission denied 191 | ``` 192 | 193 | But if we list it as sudo 194 | 195 | ```bash 196 | sudo ls -la 197 | ``` 198 | 199 | obviously no problem! 200 | 201 | That's all, we now have a password protected encrypted folder for our mail store. If you want to try it, just create some text file inside the plain folder, and you 'll find a new encrypted file inside cipher folder. Any time we want to mount our plain folder, we just use the second command again. 202 | 203 | # POSTFIX 204 | 205 | Postfix is a Mail Transfer Agent (MTA), that is, software that sends and receives emails to and from other computers on the network using the Simple Mail Transfer Protocol (SMTP). And as a reminder: 206 | 207 | * POP/IMAP are used by a client to read messages from an email server 208 | * SMTP is used to exchange emails between computers 209 | 210 | So, let's install it: 211 | 212 | ```bash 213 | sudo apt-get install postfix 214 | ``` 215 | 216 | A command line GUI will pop up, configure it as follow: 217 | 218 | * Internet Site 219 | * your mail server domain: _supersecure.mydomain.net_ 220 | 221 | Postfix configuration file is located at _/etc/postfix/main.cf_, in case you'll need to change more settings 222 | 223 | Here follows a basic configuration: 224 | 225 | ```bash 226 | myhostname = supersecure.mydomain.net 227 | myorigin = /etc/mailname 228 | 229 | append_dot_mydomain = no 230 | readme_directory = no 231 | config_directory = /etc/postfix 232 | inet_interfaces = all 233 | inet_protocols = ipv4 234 | mydestination = supersecure.mydomain.net, supersecure.mydomain.net, localhost.localdomain, localhost 235 | relayhost = 236 | mynetworks = 127.0.0.0/8 237 | mailbox_size_limit = 0 238 | recipient_delimiter = + 239 | ``` 240 | Now, the default for postfix is to use the _mbox_ format (one single file to store all messages) or the _Maildir_ format (each email stored in a individual file), but we are going to take it a step further and use Dovecot instead, so be patient, we are now going to set up our SSL/TLS certificates, and come back here for the Dovecot part in a bit. 241 | 242 | 243 | ## SSL / Let's Encrypt 244 | 245 | We are using here the amazing free, automated and open Certificate Authority called [Let's Encrypt](https://letsencrypt.org/), a project by the [Linux Foundation](https://www.linuxfoundation.org/) 246 | 247 | As always, install the module: 248 | 249 | ```bash 250 | sudo apt-get install letsencrypt 251 | ``` 252 | 253 | And run the certificate creation process: 254 | 255 | ```bash 256 | sudo letsencrypt certonly --standalone -d 257 | ``` 258 | 259 | Replace here with your domain name, obviusly! (and without the < > ...) 260 | 261 | Then follow the process: 262 | 263 | 1. Use the default vhost filesystem 264 | 2. Enter your email server domain name: ex. _supersecure.mydomain.net_ 265 | 3. Enter a contact email 266 | 4. Read and agree to Let's Encrypt Terms of Service (only if you agree) 267 | 5. Select the _Secure_ option (HTTPS ONLY) 268 | 269 | That's it, if everything went ok, you will have your certificates stored under _/etc/letsencrypt/live/_ 270 | 271 | Now we need to enable SMTP-AUTH on Postfix, which will allow a client to identify itself through the authentication mechanism SASL. TLS (Transport Layer Security) will be used to encrypt the authentication process, and once authenticated, our server will allow the client to relay mail. 272 | 273 | Here follow the config to add to _/etc/postfix/main.cf_ 274 | 275 | ```bash 276 | smtp_use_tls = yes 277 | smtp_tls_CApath = /etc/ssl/certs 278 | smtp_tls_cert_file = /etc/letsencrypt/live//fullchain.pem 279 | smtp_tls_key_file = /etc/letsencrypt/live//privkey.pem 280 | smtp_tls_security_level = may 281 | smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache 282 | 283 | smtpd_use_tls=yes 284 | smtpd_tls_CApath = /etc/ssl/certs 285 | smtpd_tls_cert_file = /etc/letsencrypt/live//fullchain.pem 286 | smtpd_tls_key_file = /etc/letsencrypt/live//privkey.pem 287 | 288 | smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache 289 | smtpd_sasl_auth_enable = yes 290 | smtpd_sasl_path = private/auth 291 | smtpd_sasl_security_options = noanonymous 292 | 293 | smtpd_tls_eecdh_grade = strong 294 | smtpd_tls_mandatory_ciphers = medium 295 | smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1 296 | smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1 297 | smtpd_tls_security_level = may 298 | 299 | smtpd_banner = $myhostname ESMTP 300 | 301 | smtpd_recipient_restrictions = permit_mynetworks, reject_invalid_hostname, reject_non_fqdn_hostname, reject_non_fqdn_sender, reject_rbl_client sbl.spamhaus.org, reject_unknown_sender_domain, reject_unknown_recipient_domain, permit_sasl_authenticated, reject_unauth_destination 302 | smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination 303 | 304 | tls_medium_cipherlist = AES128+EECDH:AES128+EDH 305 | tls_preempt_cipherlist = yes 306 | 307 | policy-spf_time_limit = 3600s 308 | ``` 309 | 310 | Now, one trick more to have an even better security, generate new [_Diffie Hellman Keys_](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange) 311 | 312 | ```bash 313 | openssl gendh -out /etc/postfix/dh_512.pem -2 512 314 | openssl gendh -out /etc/postfix/dh_2048.pem -2 2048 315 | ``` 316 | 317 | And add it to _/etc/postfix/main.cf_ 318 | 319 | ```bash 320 | smtpd_tls_dh1024_param_file = /etc/postfix/dh_2048.pem 321 | smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem 322 | ``` 323 | 324 | Then we can edit the other Postfix important config file, _/etc/postfix/master.cf_ 325 | 326 | ```bash 327 | sudo nano /etc/postfix/master.cf 328 | ``` 329 | 330 | And make sure you have this: 331 | 332 | ```bash 333 | smtp inet n - - - - smtpd 334 | 335 | submission inet n - n - - smtpd 336 | -o syslog_name=postfix/submission 337 | -o smtpd_tls_security_level=encrypt 338 | -o smtpd_sasl_auth_enable=yes 339 | -o smtpd_recipient_restrictions=permit_mynetworks,permit_sasl_authenticated,reject 340 | -o smtpd_client_restrictions=permit_sasl_authenticated,reject 341 | -o milter_macro_daemon_name=ORIGINATING 342 | -o smtpd_sasl_type=dovecot 343 | -o smtpd_sasl_path=private/auth 344 | 345 | smtps inet n - - - - smtpd 346 | -o syslog_name=postfix/smtps 347 | -o smtpd_tls_wrappermode=yes 348 | -o smtpd_sasl_auth_enable=yes 349 | -o smtpd_client_restrictions=permit_sasl_authenticated,reject 350 | -o milter_macro_daemon_name=ORIGINATING 351 | 352 | dovecot unix - n n - - pipe 353 | flags=DRhu user=email:email argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient} 354 | ``` 355 | 356 | We'll come back later at this file, so study it a little bit and get comfortable with it. 357 | 358 | Right, so we have our MTA (Mail Transfer Agent) half configured, already secured with Let's Encrypt certificates and a strong encryption settings; but why only half configured, well, for example our MTA didn't know anything, yet, about possible recipients, who are they? and how we want email to be stored? 359 | 360 | Setting up a mail server is not an easy task, especially when we are trying to build up a reasonably secure one (and amazingly encrypted :P), so one step at the time, no rush here, the goal is to learn more as possible, because at the time we'll have this tutorial finished and our mail server up and running, it wouldn't be unusual to have to change or add something in order to avoid the possibility of some brand new attack vector. 361 | 362 | But i'm changing course, so let's get back to work, next story, [Dovecot](https://www.dovecot.org/), the open source IMAP and POP3 email server written with security primarily in mind. 363 | 364 | # DOVECOT 365 | 366 | The usual first step, install it: 367 | 368 | ```bash 369 | sudo apt-get install dovecot-core dovecot-imapd dovecot-lmtpd dovecot-pgsql postgresql postfix-pgsql 370 | ``` 371 | 372 | As you can see, i've decided to install the dovecot PostgreSQL module, and that's because we will use a PostgreSQL database to securely encrypt and store the mail users data. 373 | 374 | Someone could be asking right now, why PostgreSQL and not MySQL or MongoDB or whatever, and the answer is: there is not just one answer! 375 | So to get to the point fast, let's use a line from [_The Database Hacker's Handbook_](https://www.wiley.com/en-gb/The+Database+Hacker%27s+Handbook%3A+Defending+Database+Servers-p-9780764578014), _"By default, PostgreSQL is probably the most security-aware database available ..."_. 376 | 377 | So here we are, program and modules installed, now the tricky part, the configuration, and i think now it's time, like Agent Cooper use to say, for a damn fine cup of coffee! 378 | 379 | We'll start with the _/etc/dovecot/dovecot.conf_ file 380 | 381 | ```bash 382 | sudo nano /etc/dovecot/dovecot.conf 383 | ``` 384 | 385 | And set this: 386 | 387 | ```bash 388 | # Enable installed protocols 389 | !include_try /usr/share/dovecot/protocols.d/*.protocol 390 | 391 | listen = * 392 | 393 | disable_plaintext_auth = yes 394 | mail_privileged_group = mail 395 | 396 | passdb { 397 | args = /etc/dovecot/dovecot-sql.conf 398 | driver = sql 399 | } 400 | protocols = imap lmtp 401 | 402 | namespace inbox { 403 | inbox = yes 404 | 405 | mailbox Trash { 406 | auto = subscribe # autocreate and autosubscribe the Trash mailbox 407 | special_use = \Trash 408 | } 409 | mailbox Sent { 410 | auto = subscribe # autocreate and autosubscribe the Sent mailbox 411 | special_use = \Sent 412 | } 413 | } 414 | 415 | service auth { 416 | unix_listener /var/spool/postfix/private/auth { 417 | group = postfix 418 | mode = 0660 419 | user = postfix 420 | } 421 | } 422 | service imap-login { 423 | inet_listener imap { 424 | port = 0 425 | } 426 | inet_listener imaps { 427 | port = 993 428 | } 429 | } 430 | 431 | service lmtp { 432 | unix_listener /var/spool/postfix/private/dovecot-lmtp { 433 | group = postfix 434 | mode = 0600 435 | user = postfix 436 | } 437 | } 438 | protocol lmtp { 439 | postmaster_address=postmaster@ 440 | hostname= 441 | } 442 | 443 | ssl = required 444 | ssl_cert = /fullchain.pem 445 | ssl_cipher_list = AES128+EECDH:AES128+EDH 446 | ssl_dh_parameters_length = 4096 447 | ssl_key = /privkey.pem 448 | ssl_prefer_server_ciphers = yes 449 | ssl_protocols = !SSLv3 450 | 451 | userdb { 452 | driver = prefetch 453 | } 454 | 455 | userdb { 456 | driver = sql 457 | args = /etc/dovecot/dovecot-sql.conf 458 | } 459 | 460 | ``` 461 | 462 | So we have connected Dovecot with Postfix, configured a standard inbox for mails, configured the SSL/TLS certificates and instructed Dovecot to search for user data in a SQL database with access specified in _/etc/dovecot/dovecot-sql.conf_ file, so let's edit that one: 463 | 464 | ```bash 465 | sudo nano /etc/dovecot/dovecot-sql.conf 466 | ``` 467 | 468 | And we edit it as follows: 469 | 470 | ```bash 471 | driver = pgsql 472 | userdb_warning_disable = yes 473 | connect = host=/var/run/postgresql/ dbname= user= 474 | default_pass_scheme = SHA512 475 | password_query = SELECT email as user, password FROM users WHERE email = '%u' 476 | user_query = SELECT email as user, 'maildir:/your_mailstore_path/plain/maildir/'||maildir as mail, '/your_mailstore_path/plain/home/'||maildir as home, 500 as uid, 500 as gid FROM users WHERE email = '%u' 477 | ``` 478 | 479 | We'll leave _dbname_ and _user_ alone just for now, because we haven't created our database to store users data yet, so let's do it, and we'll come back here later. 480 | 481 | The idea here is to have the less identifiable information stored in plain on our server, so we will design the simplest of user table structure in our database, with the following fields: 482 | 483 | ```bash 484 | Column | Type | Modifiers 485 | ----------+--------------------------+--------------- 486 | email | text | not null 487 | pgpkey | text | not null 488 | password | text | not null 489 | maildir | text | not null 490 | created | timestamp with time zone | default now() 491 | ``` 492 | 493 | Where _pgpkey_ field will store the public key related with the email in order to automatically encrypt every receiving message, each user with his public key. The other fields are the common ones, the complete email, the SHA512 hashed password, the timestamp of the mail creation and the name of the directory (_maildir_) in our mailstore were Dovecot will be store (and automatically encrypt thanks to more stuff we'll see later) every user message. 494 | 495 | So, first things first, we need now to create the database, and a database user, and that is a really straightforward process, but if you're not familiar with PostgreSQL, let's have a quick recap: 496 | 497 | 0. Just before we start, let's setup the Postgres Database, first we edit _/etc/postgresql/9.4/main/pg_ident.conf_ file (my version is 9.4, so check yours before for the correct file path) 498 | ```bash 499 | sudo nano /etc/postgresql/9.4/main/pg_ident.conf 500 | ``` 501 | 502 | and make it look like this 503 | 504 | ```bash 505 | # MAPNAME SYSTEM-USERNAME PG-USERNAME 506 | 507 | mailmap dovecot testuser 508 | mailmap postfix testuser 509 | mailmap root testuser 510 | ``` 511 | 512 | Where _testuser_ will be the name of the postgres user we will use for accessing the mailstore database 513 | 514 | then edit _/etc/postgresql/9.4/main/pg_hba.conf_ file 515 | 516 | ```bash 517 | sudo nano /etc/postgresql/9.4/main/pg_hba.conf 518 | ``` 519 | 520 | and add this line (Warning: Make sure to add it right after the **Put your actual configuration here** comment block! Otherwise one of the default entries might catch first and the database authentication will fail) 521 | 522 | ```bash 523 | local mail all peer map=mailmap 524 | ``` 525 | 526 | save it and reload postgresql service 527 | 528 | ```bash 529 | sudo service postgresql reload 530 | ``` 531 | 532 | 1. On Debian, PostgreSQL is installed with a default user and default database both called _postgres_, so 533 | 534 | 2. we'll connect to the PostgreSQL console with this default user and 535 | ```bash 536 | psql -U postgres -h localhost 537 | ``` 538 | 539 | 3. create our database for the mailstore, 540 | ```bash 541 | CREATE DATABASE mailstore_db; 542 | ``` 543 | 544 | 4. create a db user to access this new database (choose your preferred username and password) and 545 | ```bash 546 | CREATE USER testuser WITH PASSWORD 'test_password'; 547 | ``` 548 | 549 | 5. grant all the necessary privileges to this new db user. 550 | ```bash 551 | GRANT ALL PRIVILEGES ON DATABASE "mailstore_db" to testuser; 552 | ``` 553 | 554 | 6. Now exit the PostgreSQL console 555 | ```bash 556 | \q 557 | ``` 558 | 559 | 7. And re-log into the PostgreSQL console as the new created _testuser_, to edit our new created db _mailstore_db_ 560 | ```bash 561 | psql -U testuser -d mailstore_db -h localhost 562 | ``` 563 | 564 | 8. And finally, we create our tables, the one for the users, and another one for aliases 565 | ```bash 566 | CREATE TABLE users ( 567 | email text NOT NULL, 568 | pgpkey text NOT NULL, 569 | password text NOT NULL, 570 | maildir text NOT NULL, 571 | created TIMESTAMPTZ DEFAULT NOW() 572 | ); 573 | 574 | CREATE TABLE aliases ( 575 | alias text NOT NULL, 576 | email text NOT NULL 577 | ); 578 | ``` 579 | 580 | 9. now we check that everything is in it's right place, we list the databases 581 | ```bash 582 | \l 583 | ``` 584 | 585 | my output: 586 | 587 | ```bash 588 | List of databases 589 | Name | Owner | Encoding | Collate | Ctype | Access privileges 590 | ----------------+------------+----------+-------------+-------------+----------------------- 591 | mailstore_db | testuser | UTF8 | en_US.UTF-8 | en_US.UTF-8 | 592 | postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | 593 | ``` 594 | 595 | 10. we list the _mailstore_db_ tables 596 | ```bash 597 | \dt 598 | ``` 599 | 600 | ```bash 601 | List of relations 602 | Schema | Name | Type | Owner 603 | -------+---------+-------+------------ 604 | public | aliases | table | testuser 605 | public | users | table | testuser 606 | ``` 607 | 608 | 11. and the tables structure 609 | ```bash 610 | \d users 611 | ``` 612 | 613 | ```bash 614 | Column | Type | Modifiers 615 | ----------+--------------------------+--------------- 616 | email | text | not null 617 | pgpkey | text | not null 618 | password | text | not null 619 | maildir | text | not null 620 | created | timestamp with time zone | default now() 621 | ``` 622 | 623 | ```bash 624 | \d aliases 625 | ``` 626 | 627 | ```bash 628 | Column | Type | Modifiers 629 | --------+------+----------- 630 | alias | text | not null 631 | email | text | not null 632 | ``` 633 | 634 | 12. And here we are! We have now our database ready, we can come back to the _/etc/dovecot/dovecot-sql.conf_ file, and finally configure the db name and db username: 635 | 636 | ```bash 637 | sudo nano /etc/dovecot/dovecot-sql.conf 638 | ``` 639 | 640 | and we edit the specific line: 641 | 642 | ```bash 643 | connect = host=/var/run/postgresql/ dbname=mailstore_db user=testuser 644 | ``` 645 | 646 | Right after that, we can now tell postfix to deliver mail directly to dovecot, so we open again _/etc/postfix/main.cf_ and added 647 | 648 | ```bash 649 | mailbox_transport = lmtp:unix:private/dovecot-lmtp 650 | alias_maps = hash:/etc/aliases proxy:pgsql:/etc/postfix/pgsql-aliases.cf 651 | local_recipient_maps = proxy:pgsql:/etc/postfix/pgsql-boxes.cf $alias_maps 652 | ``` 653 | 654 | then we need to create two files, first _/etc/postfix/pgsql-aliases.cf_ 655 | 656 | ```bash 657 | user=testuser 658 | dbname=mailstore_db 659 | table=aliases 660 | select_field=alias 661 | where_field=email 662 | hosts=unix:/var/run/postgresql 663 | ``` 664 | 665 | and then _/etc/postfix/pgsql-boxes.cf_ 666 | 667 | ```bash 668 | user=testuser 669 | dbname=mailstore_db 670 | table=users 671 | select_field=email 672 | where_field=email 673 | hosts=unix:/var/run/postgresql/ 674 | ``` 675 | 676 | This is done! 677 | 678 | Now restart Dovecot and Postfix to apply the new settings 679 | 680 | ```bash 681 | sudo service postfix restart 682 | sudo service dovecot restart 683 | ``` 684 | 685 | Amazing! Now, theoretically, we are already able to send and receive emails using secure authentication , **BUT**, in order to be able to do that, we have to create our first user mail account! And how we do that? Well, we just need to add a correct entry in our _users_ table inside our postgres database. 686 | 687 | A little digression about a possible scenario here, we could imagine some sort of form in a webpage, for everyone or maybe just for a few (your choice), to sign up and obtain an email account in our server. 688 | 689 | Ok, So i'm _astronaut57_, i use this aka almost everywhere on the internet (is it true?, or is it just for the story? you decide!), and, while surfing the web, i happen to enter into this amazing and little mysterious webpage where i can sign up and obtain a really interesting reasonably secure gpg encrypted email account, and i start to think: why not? So the webpage seem legit, i like the Terms of Use, is free and [everything is in it's right place](https://www.youtube.com/watch?v=bD2j0fH6-UQ), let's have one, i'm thinking _astronaut57@supersecure.mydomain.net_ 690 | 691 | The form moment then, i see that is a really simple one, really straightforward, it don't ask for my telephone number, or my street address in case i loose my devices and for some reason the mail provider decide to send me, printed in paper, the last 7 GB of received emails, or for the name of my dog, among other tipical questions you have to answer when registering for a free (free?) service. No, this form is neat, it ask me for those simple things: 692 | 693 | 1. the email name, _astronaut57@supersecure.mydomain.net_, check 694 | 2. the pgp public key you want to associate with this mail account, i create a pgp keypair in my machine at home (or my laptop) associated with this new email, and paste here the public key, check 695 | 3. the password for the account, _*****************************_, check 696 | 697 | And that's it, push register button and a message appear that in, let's say, 1 minute, my new email account will be available, with a link to the mail config guide for thunderbird & enigmail. Yes, no checking your email in a browser, this kind of reasonably secure gpg encrypted email account cannot work with the actual browsers security standards. Sounds pro! 698 | 699 | That's it, the user is happy, but let's analyze the server side now, we obtain from the online form all the necessary info to add a new entry in our mailstore database _users_ table, so when the user push the register button, we check all the fields (front-end side) and if all is correct, we call a script to add a new entry in our database. I'm not going to cover here how to do that from the front-end, but i'm going to show how to do it the old fashioned way, from the terminal 700 | 701 | First of all, we need to SHA512 the password entered by the user, yes, because we have configured dovecot to work with SHA512 password scheme, but most important because we do not want to know anything about the passwords used by our users, so we hash it: 702 | 703 | ```bash 704 | doveadm pw -s sha512 -r 100 705 | ``` 706 | 707 | Enter the password twice and we will obtain something like this: 708 | 709 | ```bash 710 | {SHA512}NieQminDE4Ggcewn98nKl3Jhgq7Smn3dLlQ1MyLPswq7njpt8qwsIP4jQ2MR1nhWTQyNMFkwV19g4tPQSBhNeQ== 711 | ``` 712 | 713 | Let's save that somewhere (a variable maybe?) and continue 714 | 715 | We log into the PostgreSQL console 716 | 717 | ```bash 718 | psql -U testuser -d mailstore_db -h localhost 719 | ``` 720 | 721 | then we add the new entry 722 | 723 | ```bash 724 | INSERT INTO aliases ( alias,email ) VALUES ( 725 | 'astronaut57', 726 | 'astronaut57@supersecure.mydomain.net' 727 | ); 728 | ``` 729 | 730 | and 731 | 732 | ```bash 733 | INSERT INTO users ( email,pgpkey,password,maildir ) VALUES ( 734 | 'astronaut57@supersecure.mydomain.net', 735 | ' 736 | -----BEGIN PGP PUBLIC KEY BLOCK----- 737 | ................................................................ 738 | ................................................................ 739 | -----END PGP PUBLIC KEY BLOCK----- 740 | ', 741 | '{SHA512}NieQminDE4Ggcewn98nKl3Jhgq7Smn3dLlQ1MyLPswq7njpt8qwsIP4jQ2MR1nhWTQyNMFkwV19g4tPQSBhNeQ==', 742 | 'astronaut57/' 743 | ); 744 | ``` 745 | 746 | And exit PostgreSQL console. 747 | ```bash 748 | \q 749 | ``` 750 | 751 | Done, user created and added to the database. 752 | 753 | It's time to test the system, sending an email to and from _astronaut57@supersecure.mydomain.net_, and to properly do that, we'll need to configure our thunderbird client imagining we are the real _astronaut57_. 754 | 755 | So Thunderbird, new account, with this settings: 756 | 757 | 1. Your name: 758 | 2. Email address: astronaut57@supersecure.mydomain.net 759 | 3. Password: 760 | 4. Incoming: IMAP | supersecure.mydomain.net | 993 | SSL/TLS | Normal password 761 | 5. Outgoing: SMTP | supersecure.mydomain.net | 587 | STARTTLS | Normal password 762 | 6. Username: astronaut57@supersecure.mydomain.net | astronaut57@supersecure.mydomain.net 763 | 764 | DONE! 765 | 766 | If everything went ok, all the config files are correct and the DNS of your server are working, then you will be able now to send and receive emails from/to your new email address _astronaut57@supersecure.mydomain.net_ 767 | 768 | But where is the encryption? Well, we have some more work to do, so next story, [GPGIT](https://gitlab.com/mikecardwell/gpgit)! 769 | 770 | # GPGIT 771 | 772 | I want to remember something here, maybe i'm repeating but i believe it's important to make it clear, WE ARE NOT building a NSA-proof mail server, the skill level and the infrastructure to try that is far beyond the knowledge available in this tutorial, but, we can say without doubts that our system, if working properly, will maintain at least confidentiality and authenticity of our email service; and if some evil hacker attack us with some 0-day, gaining temporary control over the server, this scheme with automated encryption we are building will keep the contents of all our users mails secret. 773 | 774 | So, how we do it? 775 | 776 | This is the idea, we'll set up a trigger on every message arriving (at Postfix level) to the server that will call an encryption function, as we have every account related with a pgp public key (nothing dangerous to store public keys on the server), we'll use this key to encrypt the messages, so the message will land automatically encrypted. Only the owner of the account, or the one who control the associated private key, will be able to open and read the content of the messages (remember that this mechanism make impossible to send messages with multiple recipients, so one message at the time). 777 | 778 | And how we treat the message sending or the _sent_ folder? Well, we are focusing on security and privacy here, so this mail server will not have the _sent_ folder, for the following reasons: 779 | 780 | 1. An obvious and easy one is that we don't know if the recipients, on the other side, use encryption or not, so even if on our side the sent message is safe, on the other side could be stored in plain, so we just lost confidentiality. 781 | 782 | 2. Postfix do not differentiate incoming mail from outgoing mail, so everything must be handled in the same hook. Due to that apply the trigger to outgoing email will need saving the _stdin_ content to the filesystem temporarily, and this is vulnerable to forensic recover. This could be partially resolved with encrypted folder/volumes, but there is a bigger problem: 783 | 784 | 3. If we want to make changes to the _IMAP_ mailbox, we'll need that mailbox username/password, and there is no way of doing that without store this credentials somewhere on plaintext (remember, we store in our mail user database the email in plain, but we hash with SHA512 the user password). Or else use a "master" account with read/write permission over all the accounts, but this is really counter-productive to what we are trying to do here. 785 | 786 | In conclusion, we are not going to have here the typical _sent_ folder with a copy of our sent messages, if we want a copy of something we are sending, we just re-send the message to ourselves. Plus, we can add a filter in Thunderbird, [thunderbird filters](http://write.flossmanuals.net/thunderbird/filters/), to automatically move a message into the _sent_ folder if comes directly from ourselves. 787 | 788 | Installation time! We need to install the [Enigmail](https://www.enigmail.net/index.php/en/) plugin on our local machine (and gpg obviously, but if we already created our keypair before, we already have it!), while on the server (if debian) comes preinstalled, so we check the version: 789 | 790 | ```bash 791 | gpg --version 792 | ``` 793 | 794 | ```bash 795 | gpg (GnuPG) 2.1.18 796 | libgcrypt 1.7.6-beta 797 | Copyright (C) 2017 Free Software Foundation, Inc. 798 | License GPLv3+: GNU GPL version 3 or later 799 | This is free software: you are free to change and redistribute it. 800 | There is NO WARRANTY, to the extent permitted by law. 801 | 802 | Home: /home/user/.gnupg 803 | Supported algorithms: 804 | Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA 805 | Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, 806 | CAMELLIA128, CAMELLIA192, CAMELLIA256 807 | Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224 808 | Compression: Uncompressed, ZIP, ZLIB, BZIP2 809 | ``` 810 | 811 | In case your linux distro doesn't come with gpg preinstalled, just install it. 812 | 813 | Next, we need to create an unprivileged user for running the encryption scripts: 814 | 815 | ```bash 816 | sudo adduser --shell /bin/false --home /var/opt/gpgit --disabled-password --disabled-login --gecos "" gpgit 817 | ``` 818 | 819 | then set up _gpg_ for this user (if you're not familiar with gpg, you can start [here](https://theprivacyguide.org/tutorials/gpg.html)): 820 | 821 | ```bash 822 | sudo mkdir /var/opt/gpgit/.gnupg 823 | sudo chown gpgit:gpgit /var/opt/gpgit/.gnupg 824 | sudo chmod 700 /var/opt/gpgit/.gnupg 825 | # Import the mail users public keys (we import this one, but this must be repeated for each new registered user) 826 | sudo -u gpgit /usr/bin/gpg --homedir=/var/opt/gpgit/.gnupg --import astronaut57@supersecure.mydomain.net.gpg 827 | # List all imported keys (you should see the just imported key) 828 | sudo -u gpgit /usr/bin/gpg --homedir=/var/opt/gpgit/.gnupg/ --list-keys 829 | # give ultimate trust (5) to the imported key 830 | sudo -u gpgit /usr/bin/gpg --homedir=/var/opt/gpgit/.gnupg --edit-key astronaut57@supersecure.mydomain.net trust quit 831 | ``` 832 | 833 | We then install (clone) gpgit script by Mark Cardwell from [here](https://gitlab.com/mikecardwell/gpgit), inside our newly created _gpgit_ user home folder: 834 | 835 | ```bash 836 | cd /var/opt/gpgit/ 837 | sudo -u gpgit /usr/bin/git clone https://gitlab.com/mikecardwell/gpgit 838 | ``` 839 | 840 | If necessary, install the required Perl modules, we are using here the [cpan](https://www.cpan.org/) console: 841 | 842 | ```bash 843 | # install cpanminus 844 | cpan App::cpanminus 845 | # Now install module. 846 | cpanm Mail::GnuPG 847 | # or any others you may be missing 848 | # cpanm XXX::YYY 849 | ``` 850 | 851 | And finally a little test: 852 | 853 | ```bash 854 | # this should produce a text file with some b64 data, call the command, wait two seconds, then Ctrl-D 855 | sudo -u gpgit /var/opt/gpgit/gpgit/gpgit.pl astronaut57@supersecure.mydomain.net > /var/opt/gpgit/success 856 | cat /var/opt/gpgit/success 857 | ``` 858 | 859 | My output: 860 | 861 | ```bash 862 | Content-Type: multipart/encrypted; boundary="----------=_1524436225-25788-0"; protocol="application/pgp-encrypted" 863 | 864 | This is a multi-part message in MIME format... 865 | 866 | ------------=_1524436225-25788-0 867 | Content-Type: application/pgp-encrypted; name="msg.asc" 868 | Content-Disposition: inline; filename="msg.asc" 869 | Content-Transfer-Encoding: 7bit 870 | 871 | Version: 1 872 | ------------=_1524436225-25788-0 873 | Content-Type: application/octet-stream 874 | Content-Disposition: inline 875 | Content-Transfer-Encoding: 7bit 876 | 877 | -----BEGIN PGP MESSAGE----- 878 | 879 | hQIMA6SuSblhzUCkAQ/+M520zEDPEOwmsPmjOS8Sv1teygcLbId5wUEwHgPz3o3r 880 | teu7UIUXHvVLGVW9zg7ggIaUzMj+XPPhZvmKWLyK2pBNYOjwaBAYTjlB6y6ANs3b 881 | M2t3/OUzoFRchdY6AVZQGwD+RNvb0pUTrvf4tC8TQBHbdOYojP2qodNVTJ408kxx 882 | q0qcoYjHz+1L7Qng8uMXSX1LI4PNWcVqG0sBvtiaTUgDDVJb7MA5Ig/EW2V8FB1i 883 | sTAR13sDJ7VhbCasDUUBhs0x1Y0ssj+LiDd1qkQNgqvePqJ0RikwtQ9OzaZ11o5H 884 | N/FikujIYUTzoGvR7KBLmGBpUyAwagkbUlJKsGhLNDaSo32dgnz34UBaTxa41GDF 885 | M3JGuhiIQe7nH9AMavTt4sHc28cmYdqnOOKDA+1dwE6lLOoGQg3IkFlz0cunN8ee 886 | JVyuC7lUnEKgJJVjRaWQOlzWltwIuQXEitoj33/bMktYtEbRydRSwvM2ajmpgEyF 887 | U0lTr/yz/PdfBk/kPrezPIAJFMWDun58Vi+VJemvCsOxgi7e0MwrEpe4u9ap1SKf 888 | g2zEXovVlPFldapqFztSjLKIlskompXQbSs4IaF4ciMXatRUNpzIFICUFMw/Cd4T 889 | G2N9Kq5f4hGdskbb5PVAqWXvy8m7zH3pIrQaPQHJAFbAh99Ull4w4LCaEGUjI9bS 890 | PAGEAqNYGUUuvN+srtVtj/ciMMET2VvPoA0BuiVzbD7XZqdF2jlf6fL8JAQ3zls2 891 | CHUR7ahLHVffgqHqPA== 892 | =h9sJ 893 | -----END PGP MESSAGE----- 894 | 895 | ------------=_1524436225-25788-0-- 896 | ``` 897 | 898 | It's working, **gpgit** Perl script from Mike Cardwell automatically use the email address provided, to look up the public key that the message will be encrypted with. 899 | 900 | Almost finished, we just need now to trigger this script on arriving mail in Postfix, and basically this is just a filter over the incoming messages, not so different to the spam filters that we'll implement later, in this case encrypting the content of the message before delivery. 901 | 902 | Postfix then, but we need here to clarify something about how we achieve this; the Postfix mechanics doesn't let us apply some content filter to incoming messages only, if we setup some filter, it will be applied to all messages, incoming and outgoing. So far so good, the gpgit script search for local installed gpg public keys, so the only keys available will be the ones related with users accounts, so the only messages encrypted will be the ones with a local mail user as recipients, meaning incoming messages only! We let on the client side the possible encryption of outgoing messages, using the common (and user friendly) mechanism through the enigmail plugin. 903 | And a last detail, while the message will be encrypted, headers (from:, time:, and more metadata) will not, so later we'll add some sort of anonymization of message headers, but let just finish the encryption part first. 904 | 905 | We open again the _/etc/postfix/master.cf_ Postfix config file 906 | 907 | ```bash 908 | sudo nano /etc/postfix/master.cf 909 | ``` 910 | 911 | and add _-o content_filter=gpgit-pipe_ to the specified blocks (smtp, smtps, submission) 912 | 913 | ```bash 914 | smtp inet n - - - - smtpd 915 | -o content_filter=gpgit-pipe 916 | 917 | submission inet n - n - - smtpd 918 | -o content_filter=gpgit-pipe 919 | 920 | smtps inet n - - - - smtpd 921 | -o content_filter=gpgit-pipe 922 | ``` 923 | 924 | and at the end we add our hook 925 | 926 | ```bash 927 | gpgit-pipe unix - n n - - pipe 928 | flags=Rq user=gpgit argv=/var/opt/gpgit/gpgit_postfix.sh -oi -f ${sender} ${recipient} 929 | ``` 930 | 931 | Save it and close it. Now we need to create the _gpgit_postfix.sh_ script to finally get the job done! 932 | 933 | Create the new file 934 | 935 | ```bash 936 | sudo -u gpgit nano /var/opt/gpgit/gpgit_postfix.sh 937 | ``` 938 | 939 | with the following contents 940 | 941 | ```bash 942 | #!/bin/bash 943 | 944 | SENDMAIL=/usr/sbin/sendmail 945 | GPGIT=/var/opt/gpgit/gpgit/gpgit.pl 946 | 947 | #encrypt and resend directly from stdin 948 | set -o pipefail 949 | 950 | ${GPGIT} "$4" | ${SENDMAIL} "$@" 951 | 952 | exit $? 953 | ``` 954 | 955 | save & close it, and change the file permission to 755 956 | 957 | ```bash 958 | sudo chmod 755 /var/opt/gpgit/gpgit_postfix.sh 959 | ``` 960 | 961 | At this point, we should have everything tuned, restart Postfix service 962 | 963 | ```bash 964 | sudo service postfix restart 965 | ``` 966 | 967 | And try to send an email to your brand new email account _astronaut57@supersecure.mydomain.net_, if everything is correct, thunderbird will ask for your private key password to decrypt your incoming message (client side). 968 | 969 | The beauty of this piped construct (the script in _/var/opt/gpgit/gpgit_postfix.sh_) is in that the message body is never saved as a file on the mail server disk. If it was, it could potentially be recovered via forensic disk analysis, which is undesirable. 970 | 971 | This chapter is not a walk in the park, as they say, so if you want some more technical details, or just more info about that, here you'll find the original article from which i've extracted this part of the tutorial: [Encrypting Stored Email with Postfix](http://kacangbawang.com/encrypting-stored-email-with-postfix/) 972 | 973 | Next story, anti-spam chicanery! 974 | 975 | # Anti-Spam 976 | 977 | Well, yes, email use to go hand by hand with spam, their relationship has grown exponentially over the years, and all the ecosystems around email are filled with various anti-spam hacks and workarounds. 978 | The combination of tools we are going to use here for our mail server is, at the moment of writing (April 2018), pretty solid and well tested, we will try at the end a quality test over our mail server using [MailTester](https://www.mail-tester.com/) spam test, and i've used this combination in my personal mail server for almost a year with 0 spam issues, yes, ZERO! 979 | This coupled with properly configured jails in [Fail2ban](#fail2ban) will give us a solid system protected from spammers. 980 | But first of all, we'll need to not be considered spammers ourselves, we'll start then with SPF! 981 | 982 | ## SPF 983 | 984 | Sender Policy Framework (SPF) is one of the two services you should configure in order not to be considered as a spammer by major e-mail service providers. 985 | 986 | So install it: 987 | 988 | ```bash 989 | sudo apt-get install postfix-policyd-spf-python 990 | ``` 991 | 992 | edit the postfix _/etc/postfix/master.cf_ config file 993 | 994 | ```bash 995 | sudo nano /etc/postfix/master.cf 996 | ``` 997 | 998 | and add this 999 | 1000 | ```bash 1001 | policy-spf unix - n n - - spawn 1002 | user=nobody argv=/usr/bin/policyd-spf 1003 | ``` 1004 | 1005 | Then, modify this line in _/etc/postfix/main.cf_ 1006 | 1007 | ```bash 1008 | smtpd_recipient_restrictions = permit_mynetworks, reject_invalid_hostname, reject_non_fqdn_hostname, reject_non_fqdn_sender, reject_rbl_client sbl.spamhaus.org, reject_unknown_sender_domain, reject_unknown_recipient_domain, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy-spf 1009 | ``` 1010 | 1011 | as you can see, we added at the end _check_policy_service unix:private/policy-spf_, to activate the SPF policy in Postfix. 1012 | 1013 | And that's it, we already setup a SPF record in our DNS at the beginning of this tutorial, so we are good to go to the next story, Amavis! 1014 | 1015 | ## AMAVIS 1016 | 1017 | [Amavis](https://amavis.org/) is a high-performance and reliable interface between mailer (MTA) and one or more content checkers: virus scanners, and/or Mail::SpamAssassin Perl module. 1018 | 1019 | As always 1020 | 1021 | ```bash 1022 | sudo apt-get install amavisd-new 1023 | ``` 1024 | 1025 | Then the config, first we need to tell Postfix about our content filter 1026 | 1027 | ```bash 1028 | sudo nano /etc/postfix/main.cf 1029 | ``` 1030 | 1031 | add this 1032 | 1033 | ```bash 1034 | content_filter = amavis:[127.0.0.1]:10024 1035 | ``` 1036 | 1037 | save it and open _/etc/postfix/master.cf_ 1038 | 1039 | ```bash 1040 | sudo nano /etc/postfix/master.cf 1041 | ``` 1042 | 1043 | and add this blocks 1044 | 1045 | ```bash 1046 | amavis unix - - - - 2 smtp 1047 | -o smtp_send_xforward_command=yes 1048 | -o smtp_tls_security_level=none 1049 | 1050 | 127.0.0.1:10025 inet n - - - - smtpd 1051 | -o content_filter= 1052 | -o receive_override_options=no_milters 1053 | ``` 1054 | 1055 | Save it and restart Postfix 1056 | 1057 | ```bash 1058 | sudo service postfix restart 1059 | ``` 1060 | 1061 | Then the last one, open _/etc/amavis/conf.d/20-debian_defaults_ file 1062 | 1063 | ```bash 1064 | sudo nano /etc/amavis/conf.d/20-debian_defaults 1065 | ``` 1066 | 1067 | and make this line looks like this 1068 | 1069 | ```bash 1070 | $inet_socket_bind = '127.0.0.1'; 1071 | ``` 1072 | 1073 | That's was easy! So next one, Postgray! 1074 | 1075 | ## POSTGRAY 1076 | 1077 | [Postgrey](http://postgrey.schweikert.ch/) is a Postfix policy server implementing greylisting developed by [David Schweikert](http://david.schweikert.ch/). 1078 | 1079 | Install it 1080 | 1081 | ```bash 1082 | sudo apt-get install postgrey 1083 | ``` 1084 | 1085 | Then edit his config file _/etc/default/postgrey_ 1086 | 1087 | ```bash 1088 | sudo nano /etc/default/postgrey 1089 | ``` 1090 | 1091 | and edit this line as follows 1092 | 1093 | ```bash 1094 | POSTGREY_OPTS="--inet=10023 --delay=30" 1095 | ``` 1096 | 1097 | Ok, now we need to tell Postfix to use Postgray, edit the usual _/etc/postfix/main.cf_ file and modify this line 1098 | 1099 | ```bash 1100 | smtpd_recipient_restrictions = permit_mynetworks, reject_invalid_hostname, reject_non_fqdn_hostname, reject_non_fqdn_sender, reject_rbl_client sbl.spamhaus.org, reject_unknown_sender_domain, reject_unknown_recipient_domain, permit_sasl_authenticated, reject_unauth_destination, check_policy_service inet:[127.0.0.1]:10023, check_policy_service unix:private/policy-spf 1101 | ``` 1102 | 1103 | adding _check_policy_service inet:[127.0.0.1]:10023_ just before the SPF policy check 1104 | 1105 | restart Postfix as usual 1106 | 1107 | ```bash 1108 | sudo service postfix restart 1109 | ``` 1110 | 1111 | and jump to the next one, OpenDKIM! 1112 | 1113 | ## OPENDKIM 1114 | 1115 | DomainKeys Identified Mail (DKIM) is the other service you need to configure in order not to be considered as a spammer by big e-mail providers. It ties your e-mail server to your domain name, so that receivers can check that e-mails originating from your domain indeed correspond to your computer. So we'll use [opendkim](http://www.opendkim.org/) for this; as always, install it: 1116 | 1117 | ```bash 1118 | sudo apt-get install opendkim opendkim-tools 1119 | ``` 1120 | 1121 | DKIM is based on asymmetric cryptography. Basically, we will generate a pair of public/private keys on your server, and publish the public key on your DNS records (remember the beginning of the tutorial?). 1122 | 1123 | So, first we edit _/etc/opendkim.conf_ 1124 | 1125 | ```bash 1126 | sudo nano /etc/opendkim.conf 1127 | ``` 1128 | 1129 | and we make sure it contains what follows 1130 | 1131 | ```bash 1132 | Syslog yes 1133 | 1134 | UMask 002 1135 | 1136 | AutoRestart Yes 1137 | AutoRestartRate 10/1h 1138 | SyslogSuccess Yes 1139 | LogWhy Yes 1140 | 1141 | Canonicalization relaxed/simple 1142 | 1143 | ExternalIgnoreList refile:/etc/opendkim/TrustedHosts 1144 | InternalHosts refile:/etc/opendkim/TrustedHosts 1145 | KeyTable refile:/etc/opendkim/KeyTable 1146 | SigningTable refile:/etc/opendkim/SigningTable 1147 | 1148 | Mode sv 1149 | PidFile /var/run/opendkim/opendkim.pid 1150 | SignatureAlgorithm rsa-sha256 1151 | 1152 | UserID opendkim:opendkim 1153 | 1154 | Socket inet:12345@localhost 1155 | ``` 1156 | 1157 | Then we edit _/etc/opendkim/TrustedHosts_ and we make sure it contains all our domains, hostnames or IP addresses 1158 | 1159 | ```bash 1160 | 127.0.0.1 1161 | localhost 1162 | *.supersecure.mydomain.net 1163 | ``` 1164 | 1165 | next, we tell Postfix to connect with OpenDKIM, we open _/etc/postfix/main.cf_ and add this: 1166 | 1167 | ```bash 1168 | # DKIM 1169 | milter_default_action = accept 1170 | milter_protocol = 6 1171 | smtpd_milters = inet:localhost:12345 1172 | non_smtpd_milters = inet:localhost:12345 1173 | ``` 1174 | 1175 | good, now we need to generate a keypair for our server: 1176 | 1177 | ```bash 1178 | sudo mkdir /etc/opendkim/keys/ 1179 | sudo chown opendkim:opendkim -R /etc/opendkim/keys/ 1180 | cd /etc/opendkim/keys/ 1181 | sudo opendkim-genkey -s default -d 1182 | sudo chown opendkim:opendkim /etc/opendkim/keys//default.private 1183 | ``` 1184 | 1185 | Keypair created, now we add the key to _/etc/opendkim/KeyTable_ 1186 | 1187 | ```bash 1188 | default._domainkey. :default:/etc/opendkim/keys//default.private 1189 | ``` 1190 | 1191 | and don't forget to change __ with your real domain (and cut the < > !!!) 1192 | 1193 | ok, last one, in _/etc/opendkim/SigningTable_ file, we add this: 1194 | 1195 | ```bash 1196 | *@ default._domainkey. 1197 | ``` 1198 | 1199 | With everything saved, restart Postfix and opendkim and we have it! 1200 | 1201 | ```bash 1202 | sudo service postfix restart 1203 | sudo service opendkim restart 1204 | ``` 1205 | 1206 | And to finish this chapter, the last thing will be to display our openDKIM DNS generated key, to add it to our **TXT/SPF** record and finally have it properly configured 1207 | 1208 | ```bash 1209 | sudo cat /etc/opendkim/keys//default.txt 1210 | ``` 1211 | 1212 | my output (some) 1213 | 1214 | ```bash 1215 | default._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " 1216 | "p=WT...long hash....niuohefopUgIPUGUVWYF" ) ; ----- DKIM key default for 1217 | ``` 1218 | 1219 | And that's it! Wait some time for the DNS to propagate and test it 1220 | 1221 | ```bash 1222 | dig TXT 1223 | ``` 1224 | 1225 | Our mail server is starting to look really fine-tuned! It's time for our last tool of anti-spam magic, next story [SpamAssassin](https://spamassassin.apache.org/) 1226 | 1227 | ## SPAMASSASSIN 1228 | 1229 | SpamAssassin is a server level filter to avoid junk mails (spam), it's a renowned one and easy to configure. 1230 | 1231 | Install it 1232 | 1233 | ```bash 1234 | sudo apt-get install spamassassin spamc 1235 | ``` 1236 | 1237 | and add it to Postfix as _content_filter_, but we have a little problem here, we already have a _content_filter_ configured in Postfix, we add it with the setup of [GPGIT](#gpgit), and we can't add two _content_filter_. 1238 | 1239 | Anyway, every problem have a solution, and this one is pretty easy to solve, we just need to modify our _gpgit_postfix.sh_ script file, in order to filter the message with SpamAssassin BEFORE encrypting it. 1240 | 1241 | So we open the file and make it look like that: 1242 | 1243 | ```bash 1244 | sudo nano /var/opt/gpgit/gpgit_postfix.sh 1245 | ``` 1246 | 1247 | ```bash 1248 | #!/bin/bash 1249 | 1250 | SENDMAIL=/usr/sbin/sendmail 1251 | SPAMASSASSIN=/usr/bin/spamc 1252 | GPGIT=/var/opt/gpgit/gpgit/gpgit.pl 1253 | 1254 | #encrypt and resend directly from stdin 1255 | set -o pipefail 1256 | 1257 | ${SPAMASSASSIN} | ${GPGIT} "$4" | ${SENDMAIL} "$@" 1258 | 1259 | exit $? 1260 | ``` 1261 | 1262 | save it and the last step, we open _/etc/spamassassin/local.cf_ file and uncomment this line 1263 | 1264 | ```bash 1265 | rewrite_header Subject *****SPAM***** 1266 | ``` 1267 | 1268 | to label spam mails. 1269 | 1270 | We have it! 1271 | 1272 | Form time to time, if you want to update the SpamAssassin database, you can run this command 1273 | 1274 | ```bash 1275 | sa-update 1276 | ``` 1277 | 1278 | and restart SpamAssassin 1279 | 1280 | ```bash 1281 | /etc/init.d/spamassassin restart 1282 | ``` 1283 | 1284 | We are almost finished, just one step more to increase anonymity on our mail server, next story: Anonymize Headers! 1285 | 1286 | # ANONYMIZE HEADERS 1287 | 1288 | In Postfix, we can manage to hide the sender originating IP (plus some other stuff), in order to reduce the presence of user information on the server, and this can be achieved using Postfix's _cleanup_service_name_ directive; let's do it! 1289 | 1290 | Install _postfix-pcre_ module 1291 | 1292 | ```bash 1293 | sudo apt-get install postfix-pcre 1294 | ``` 1295 | 1296 | then create a file _/etc/postfix/smtp_header_checks.pcre_ with this content: 1297 | 1298 | ```bash 1299 | /^\s*(Received: from)[^\n]*(.*)/ REPLACE $1 [127.0.0.1] (localhost [127.0.0.1])$2 1300 | /^\s*User-Agent/ IGNORE 1301 | /^\s*X-Enigmail/ IGNORE 1302 | /^\s*X-Mailer/ IGNORE 1303 | /^\s*X-Originating-IP/ IGNORE 1304 | ``` 1305 | 1306 | save&close 1307 | 1308 | Now we edit again the _/etc/postfix/master.cf_ Postfix config file, and add this: 1309 | 1310 | ```bash 1311 | -o cleanup_service_name=subcleanup 1312 | ``` 1313 | 1314 | at the end of _smtp_, _submission_, _smtps_ and _amavis_ blocks, just like this: 1315 | 1316 | ```bash 1317 | smtp inet n - - - - smtpd 1318 | ........ 1319 | -o cleanup_service_name=subcleanup 1320 | 1321 | submission inet n - n - - smtpd 1322 | ........ 1323 | -o cleanup_service_name=subcleanup 1324 | 1325 | smtps inet n - - - - smtpd 1326 | ........ 1327 | -o cleanup_service_name=subcleanup 1328 | 1329 | amavis unix - - - - 2 smtp 1330 | ........ 1331 | -o cleanup_service_name=subcleanup 1332 | ``` 1333 | 1334 | then at the end of the config file we add 1335 | 1336 | ```bash 1337 | subcleanup unix n - - - 0 cleanup 1338 | -o header_checks=pcre:/etc/postfix/smtp_header_checks.pcre 1339 | ``` 1340 | 1341 | and that's it! We now restart Postfix as always, and if everything is correct, our mail server is perfectly up&running! 1342 | 1343 | We are going to add some security on the next story, using our favorite intrusion prevention software, [Fail2Ban](https://www.fail2ban.org/wiki/index.php/Main_Page) 1344 | 1345 | # FAIL2BAN 1346 | 1347 | If you like stuff as set up your own server at home, or this is your field of work, you must probably know really well Fail2Ban software, but if you're not, well, it's time you catch up the good stuff, because intrusion detection systems, nowadays, are more than necessaries. 1348 | 1349 | More of the same, install it 1350 | 1351 | ```bash 1352 | sudo apt-get install fail2ban 1353 | ``` 1354 | 1355 | make a local copy of the configuration file 1356 | 1357 | ```bash 1358 | cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 1359 | ``` 1360 | 1361 | and edit your local copy 1362 | 1363 | ```bash 1364 | nano /etc/fail2ban/jail.local 1365 | ``` 1366 | 1367 | then go down to the _[JAILS]_ section and search for _# Mail servers_ line. 1368 | 1369 | Ok, we need now to activate jails for Postfix, Dovecot y SASL: 1370 | 1371 | ```bash 1372 | [postfix] 1373 | 1374 | enabled = true 1375 | port = smtp,ssmtp,submission 1376 | filter = postfix 1377 | logpath = /var/log/mail.log 1378 | 1379 | 1380 | [sasl] 1381 | 1382 | enabled = true 1383 | port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s 1384 | filter = postfix-sasl 1385 | # You might consider monitoring /var/log/mail.warn instead if you are 1386 | # running postfix since it would provide the same log lines at the 1387 | # "warn" level but overall at the smaller filesize. 1388 | logpath = /var/log/mail.warn 1389 | maxretry = 1 1390 | bantime = 21600 1391 | 1392 | [dovecot] 1393 | 1394 | enabled = true 1395 | port = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s 1396 | filter = dovecot 1397 | logpath = /var/log/mail.log 1398 | ``` 1399 | 1400 | Perfect, this is the end of the road, close the config fail and restart fail2ban 1401 | 1402 | ```bash 1403 | sudo /etc/init.d/fail2ban restart 1404 | ``` 1405 | 1406 | Now that our server is running as we want it, properly configured and protected, we can now open the necessary ports on our firewall and on our router, to finally make our mail server visible to the Internet. 1407 | 1408 | Next short stories, _iptables_ and _router settings_ 1409 | 1410 | # IPTABLES 1411 | 1412 | Just add this rules to your firewall, adjust them if your not using _iptables_ 1413 | 1414 | ```bash 1415 | sudo iptables -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT 1416 | sudo iptables -A INPUT -p tcp -m tcp --dport 587 -j ACCEPT 1417 | sudo iptables -A INPUT -p tcp -m tcp --dport 993 -j ACCEPT 1418 | 1419 | sudo iptables -A OUTPUT -p tcp -m tcp --dport 25 -m state --state NEW -j ACCEPT 1420 | sudo iptables -A OUTPUT -p tcp -m tcp --sport 25 -m state --state ESTABLISHED -j ACCEPT 1421 | sudo iptables -A OUTPUT -p tcp -m tcp --dport 587 -m state --state NEW -j ACCEPT 1422 | sudo iptables -A OUTPUT -p tcp -m tcp --sport 587 -m state --state ESTABLISHED -j ACCEPT 1423 | ``` 1424 | 1425 | # ROUTER SETTINGS 1426 | 1427 | open router port 25 (SMTP, to instantiate servers communication), 587 (SUBMISSION, secure send), and 993 (IMAPS over TLS/SSL, secure receive) 1428 | 1429 | 1430 | # Testing 1431 | 1432 | This is the end of another journey, we can enjoy it, ask ourselves if was worth the hours of work, and if we learned something, important or not, learning can only make us better. 1433 | 1434 | But before let's try a test, a comprehensive one about the spammyness of our mail server, plus a lot of info on how to improve the quality of the service, is [mail tester](https://www.mail-tester.com/), i'll let you try yours then, and here you have the result for my mail server, the one identically configured as the tutorial itself 1435 | 1436 | ![End-to-end (E2EE) Encrypted Email Server](https://github.com/d3cod3/EndtoEndEncryptedMailServer/blob/master/img/mail_score.jpg) 1437 | 1438 | and just for having something to compare, this is another test from a gmail account (you can try with one if you have it, and you will see the same result) 1439 | 1440 | ![Gmail](https://github.com/d3cod3/EndtoEndEncryptedMailServer/blob/master/img/gmail_score.jpg) 1441 | 1442 | 1443 | # Conclusions 1444 | 1445 | ...soon 1446 | --------------------------------------------------------------------------------