commit 95d413db4ef8cf469d4a81a7e65d6bd9008734f0 Author: Eric Meehan Date: Sat Feb 7 11:36:54 2026 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87b0d8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +docker-mailserver/certs/*.pem +docker-mailserver/config/dovecot-quotas.cf +docker-mailserver/config/ssl/* +docker-mailserver/mail/* +docker-mailserver/transport +tor/data/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..1416094 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +!!! Work in Progress !!! + +# Hidden Mailserver +This repository deploys a simple SMTP server on the Tor network using Docker containers for the purpose of self-hosting +anonymous and end-to-end encrypted email communications without a public domain name or opening ports on one's router. +It is based on the instructions provided by [ehloonion/onionmx](https://github.com/ehloonion/onionmx/tree/master), and is +designed to be as easy as possible to deploy. + +The goal of this project is to allow individuals to communicate with as much privacy and security as possible. If you and +your friend each deploy this and share your .onion email addresses, your subsequent correspondance would be virtually +untraceable. Clearnet email providers will reject mail from servers lacking authentication from DNS records, so this is +primarily intended for interpersonal communications between trusted parties. + +## Requirements +* Linux or MacOS (PRs welcome for Windows support) +* 2GB RAM (minimum) +* 2GB available storage for your emails (bare minimum) +* Internet connection +* [Docker](https://www.docker.com/) +* Sudo privileges to change the ownership of directories within this repository (read ```startup.sh```) + +## Usage +1. Add known .onion addresses to the ```known_servers``` file, one per line + +2. Run the startup script + +``` +./startup.sh +``` + +3. Create an initial email account (pseudonyms are more secure) + +``` +docker compose exec mailserver setup email create {{ your_username }}@{{ your_onion_service }}.onion +``` + +4. Configure your email client to use a SOCKS5 proxy + +5. Import the CA cert into your email client + +6. Login with POP3 using an email client + +7. Configure GPG in your client (optional but highly recommended) + +## Security +You are encouraged to read ```startup.sh```, ```docker-compose.yaml```, and the provided configuration files. This repository +utilizes well-known Docker containers ([mailserver/docker-mailserver](https://hub.docker.com/r/mailserver/docker-mailserver) +and [dockurr/tor](https://hub.docker.com/r/dockurr/tor)) with minimal configurations to achieve SMTP over Tor. Most users +should be able to verify the contents of this repository themselves, and are encouraged to do so. + +The startup script will generate keys for encrypting mail on disk; however, it stores these keys alongside the encrypted data +all within this repository. You are highly encouraged to take steps to separate your keys from your data. + +The encryption keys created in the startup script encrypts all mail stored on the server. GPG encryption establishes +true end-to-end encryption between the sender and receiver. Configuring GPG encryption in your email client is highly +recommended. + +TLS is not used here, and [is not needed](https://community.torproject.org/onion-services/advanced/https/). + +Beware spoofed email addresses. Usernames can be reused across server instances and .onion addresses can be difficult to +discern by eye. Make sure the person to whom you reply is who you think they are. + +Additional restrictions may be desireable in ```docker-mailserver/postfix/master.cf``` and +```docker-mailserver/postfix/main.cf``` (not provided) to ensure the server does not reveal its public IP address by attempting +to send or relay mail over the public internet. Please submit a PR if you have suggestions for a more secure default +configuration. + +## Notes +A bridge network with static IPs is defined in ```docker-compose.yaml```. This was done so that container IP addresses could +be hardcoded into ```docker-mailserver/smtp_tor/smtp_tor.sh``` and ```tor/config/torrc```. You can duplicate this repository +in order to host multiple email servers at different .onion addresses from a single machine, which will allow you to maintain +different identities for different purposes; however, the name of the network must be changed between deployments. Edit lines +2, 15, and 30 of ```docker-compose.yaml``` to do this. + +You can [use Python to generate a QR code](https://pypi.org/project/qrcode/) an easy way to share your .onion address and GPG +public key with trusted parties. + +This application can easily run on modern consumer-grade hardware. While it can be hosted on one's own personal desktop or +even laptop computer, delivery can fail if your server is offline. A Raspberry Pi is an inexpensive option for an always-on +server. The same hardware requirements listed above would apply to a Raspberry Pi (2GB RAM, >2GB storage). diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..62e336f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,43 @@ +networks: + hidden_mailserver_network: + driver: bridge + ipam: + config: + - subnet: 172.32.0.0/24 +services: + tor: + image: dockurr/tor + container_name: tor + volumes: + - ./tor/config/torrc:/etc/tor/torrc + - ./tor/data:/var/lib/tor + networks: + hidden_mailserver_network: + ipv4_address: 172.32.0.2 + restart: always + docker-mailserver: + image: mailserver/docker-mailserver + container_name: mailserver + depends_on: + - tor + volumes: + - ./docker-mailserver/certs:/certs + - ./docker-mailserver/config:/tmp/docker-mailserver + - ./docker-mailserver/dovecot/10-encryption.conf:/etc/dovecot/conf.d/10-encryption.conf + - ./docker-mailserver/mail:/var/mail + - ./docker-mailserver/smtp_tor/smtp_tor.sh:/usr/lib/postfix/sbin/smtp_tor + - ./docker-mailserver/transport:/etc/postfix/transport + - ./docker-mailserver/master.cf:/etc/postfix/master.cf + networks: + hidden_mailserver_network: + ipv4_address: 172.32.0.3 + restart: always + command: + - "/bin/bash" + - "-c" + - "apt-get update -y && apt-get install -y torsocks && echo 'AllowInbound 1' >> /etc/tor/torsocks.conf && chown root:root /usr/lib/postfix/sbin/smtp_tor && chown root:root /etc/postfix/transport && postmap /etc/postfix/transport && supervisord -c /etc/supervisor/supervisord.conf" + environment: + - "ENABLE_POP3=1" + - "OVERRIDE_HOSTNAME=mail.${HIDDEN_SERVICE_ADDRESS}" + - "POSTMASTER_ADDRESS=postmaster@${HIDDEN_SERVICE_ADDRESS}" + - "SSL_TYPE=self-signed" diff --git a/docker-mailserver/config/postfix-accounts.cf b/docker-mailserver/config/postfix-accounts.cf new file mode 100644 index 0000000..95cef00 --- /dev/null +++ b/docker-mailserver/config/postfix-accounts.cf @@ -0,0 +1 @@ +test@tokrtmlmfhkt63yb5z2d6mrcphfi5gdawblnxjz3menmu72dtttj2iad.onion|{SHA512-CRYPT}$6$mu78dupB2eQWSa9s$eISjUVt9p9xZa2kpkEMoH72dj3pSS9VmRqreN1mkAgs2NzgwTZbTzZCB.iyyUP3/s6.uZ9zUr/9eJF4yvTuon/ diff --git a/docker-mailserver/config/postfix-main.cf b/docker-mailserver/config/postfix-main.cf new file mode 100644 index 0000000..eaaa004 --- /dev/null +++ b/docker-mailserver/config/postfix-main.cf @@ -0,0 +1 @@ +transport_maps = hash:/etc/postfix/transport diff --git a/docker-mailserver/dovecot/10-encryption.conf b/docker-mailserver/dovecot/10-encryption.conf new file mode 100644 index 0000000..07356fc --- /dev/null +++ b/docker-mailserver/dovecot/10-encryption.conf @@ -0,0 +1,7 @@ +# Enables mail_crypt for all services (imap, pop3, etc) +mail_plugins = $mail_plugins mail_crypt +plugin { + mail_crypt_global_private_key = ./docker-mailserver/transport +while read -r pseudonym onion +do + echo "$pseudonym smtptor:[$onion]" >> ./docker-mailserver/transport +done < known_servers +echo "* discard" >> ./docker-mailserver/transport +echo "Done creating transport map." + +# Generate keys for encrypted storage if needed +if ! [ -e ./docker-mailserver/certs/pubkey.pem ] || ! [ -e ./docker-mailserver/certs/privkey.pem ] +then + echo "Creating encryption keys..." + openssl ecparam -name prime256v1 -genkey| openssl pkey -out ./docker-mailserver/certs/privkey.pem + openssl pkey -in ./docker-mailserver/certs/privkey.pem -pubout -out ./docker-mailserver/certs/pubkey.pem + echo "Finished creating encryption keys." +else + echo "Using existing encryption keys." +fi + +# Generate SSL certificates if needed +if ! [ -e ./docker-mailserver/config/ssl/demoCA/cacert.pem ] +then + echo "Creating SSL certificates..." + docker run -d --rm --user "$(id -u):$(id -g)" -v ./docker-mailserver/config/ssl:/tmp/step-ca/ --workdir /tmp/step-ca \ + --entrypoint /tmp/step-ca/generate-certs.sh -e HIDDEN_SERVICE_ADDRESS=$HIDDEN_SERVICE_ADDRESS smallstep/step-ca + echo "Finished creating SSL certificates." +else + echo "Using existing SSL certificates." +fi + +# Start the containers +echo "Starting containers..." +docker compose up -d +echo "Finished starting containers." +echo "Hidden service address: $HIDDEN_SERVICE_ADDRESS" diff --git a/tor/config/torrc b/tor/config/torrc new file mode 100644 index 0000000..9cb5779 --- /dev/null +++ b/tor/config/torrc @@ -0,0 +1,258 @@ +SocksPort 0.0.0.0:9050 +## Configuration file for a typical Tor user +## Last updated 28 February 2019 for Tor 0.3.5.1-alpha. +## (may or may not work for much older or much newer versions of Tor.) +## +## Lines that begin with "## " try to explain what's going on. Lines +## that begin with just "#" are disabled commands: you can enable them +## by removing the "#" symbol. +## +## See 'man tor', or https://www.torproject.org/docs/tor-manual.html, +## for more options you can use in this file. +## +## Tor will look for this file in various places based on your platform: +## https://support.torproject.org/tbb/tbb-editing-torrc/ + +## Tor opens a SOCKS proxy on port 9050 by default -- even if you don't +## configure one below. Set "SOCKSPort 0" if you plan to run Tor only +## as a relay, and not make any local application connections yourself. +#SOCKSPort 9050 # Default: Bind to localhost:9050 for local connections. +#SOCKSPort 192.168.0.1:9100 # Bind to this address:port too. + +## Entry policies to allow/deny SOCKS requests based on IP address. +## First entry that matches wins. If no SOCKSPolicy is set, we accept +## all (and only) requests that reach a SOCKSPort. Untrusted users who +## can access your SOCKSPort may be able to learn about the connections +## you make. +#SOCKSPolicy accept 192.168.0.0/16 +#SOCKSPolicy accept6 FC00::/7 +#SOCKSPolicy reject * + +## Logs go to stdout at level "notice" unless redirected by something +## else, like one of the below lines. You can have as many Log lines as +## you want. +## +## We advise using "notice" in most cases, since anything more verbose +## may provide sensitive information to an attacker who obtains the logs. +## +## Send all messages of level 'notice' or higher to /var/log/tor/notices.log +Log notice file /var/log/tor/notices.log +## Send every possible message to /var/log/tor/debug.log +#Log debug file /var/log/tor/debug.log +## Use the system log instead of Tor's logfiles +#Log notice syslog +## To send all messages to stderr: +#Log debug stderr + +## The directory for keeping all the keys/etc. By default, we store +## things in $HOME/.tor on Unix, and in Application Data\tor on Windows. +DataDirectory /var/lib/tor + +## The port on which Tor will listen for local connections from Tor +## controller applications, as documented in control-spec.txt. +#ControlPort 9051 +## If you enable the controlport, be sure to enable one of these +## authentication methods, to prevent attackers from accessing it. +#HashedControlPassword 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C +#CookieAuthentication 1 + +############### This section is just for location-hidden services ### + +## Once you have configured a hidden service, you can look at the +## contents of the file ".../hidden_service/hostname" for the address +## to tell people. +## +## HiddenServicePort x y:z says to redirect requests on port x to the +## address y:z. + +#HiddenServiceDir /var/lib/tor/hidden_service/ +#HiddenServicePort 80 127.0.0.1:80 + +#HiddenServiceDir /var/lib/tor/other_hidden_service/ +#HiddenServicePort 80 127.0.0.1:80 +#HiddenServicePort 22 127.0.0.1:22 + +HiddenServiceDir /var/lib/tor/docker-mailserver +HiddenServicePort 25 172.32.0.3:25 +HiddenServicePort 110 172.32.0.3:110 +HiddenServicePort 143 172.32.0.3:143 +HiddenServicePort 465 172.32.0.3:465 +HiddenServicePort 587 172.32.0.3:587 +HiddenServicePort 993 172.32.0.3:993 +HiddenServicePort 995 172.32.0.3:995 + +################ This section is just for relays ##################### +# +## See https://community.torproject.org/relay for details. + +## Required: what port to advertise for incoming Tor connections. +#ORPort 9001 +## If you want to listen on a port other than the one advertised in +## ORPort (e.g. to advertise 443 but bind to 9090), you can do it as +## follows. You'll need to do ipchains or other port forwarding +## yourself to make this work. +#ORPort 443 NoListen +#ORPort 127.0.0.1:9090 NoAdvertise +## If you want to listen on IPv6 your numeric address must be explicitly +## between square brackets as follows. You must also listen on IPv4. +#ORPort [2001:DB8::1]:9050 + +## The IP address or full DNS name for incoming connections to your +## relay. Leave commented out and Tor will guess. +#Address noname.example.com + +## If you have multiple network interfaces, you can specify one for +## outgoing traffic to use. +## OutboundBindAddressExit will be used for all exit traffic, while +## OutboundBindAddressOR will be used for all OR and Dir connections +## (DNS connections ignore OutboundBindAddress). +## If you do not wish to differentiate, use OutboundBindAddress to +## specify the same address for both in a single line. +#OutboundBindAddressExit 10.0.0.4 +#OutboundBindAddressOR 10.0.0.5 + +## A handle for your relay, so people don't have to refer to it by key. +## Nicknames must be between 1 and 19 characters inclusive, and must +## contain only the characters [a-zA-Z0-9]. +## If not set, "Unnamed" will be used. +#Nickname ididnteditheconfig + +## Define these to limit how much relayed traffic you will allow. Your +## own traffic is still unthrottled. Note that RelayBandwidthRate must +## be at least 75 kilobytes per second. +## Note that units for these config options are bytes (per second), not +## bits (per second), and that prefixes are binary prefixes, i.e. 2^10, +## 2^20, etc. +#RelayBandwidthRate 100 KBytes # Throttle traffic to 100KB/s (800Kbps) +#RelayBandwidthBurst 200 KBytes # But allow bursts up to 200KB (1600Kb) + +## Use these to restrict the maximum traffic per day, week, or month. +## Note that this threshold applies separately to sent and received bytes, +## not to their sum: setting "40 GB" may allow up to 80 GB total before +## hibernating. +## +## Set a maximum of 40 gigabytes each way per period. +#AccountingMax 40 GBytes +## Each period starts daily at midnight (AccountingMax is per day) +#AccountingStart day 00:00 +## Each period starts on the 3rd of the month at 15:00 (AccountingMax +## is per month) +#AccountingStart month 3 15:00 + +## Administrative contact information for this relay or bridge. This line +## can be used to contact you if your relay or bridge is misconfigured or +## something else goes wrong. Note that we archive and publish all +## descriptors containing these lines and that Google indexes them, so +## spammers might also collect them. You may want to obscure the fact that +## it's an email address and/or generate a new address for this purpose. +## +## If you are running multiple relays, you MUST set this option. +## +#ContactInfo Random Person +## You might also include your PGP or GPG fingerprint if you have one: +#ContactInfo 0xFFFFFFFF Random Person + +## Uncomment this to mirror directory information for others. Please do +## if you have enough bandwidth. +#DirPort 9030 # what port to advertise for directory connections +## If you want to listen on a port other than the one advertised in +## DirPort (e.g. to advertise 80 but bind to 9091), you can do it as +## follows. below too. You'll need to do ipchains or other port +## forwarding yourself to make this work. +#DirPort 80 NoListen +#DirPort 127.0.0.1:9091 NoAdvertise +## Uncomment to return an arbitrary blob of html on your DirPort. Now you +## can explain what Tor is if anybody wonders why your IP address is +## contacting them. See contrib/tor-exit-notice.html in Tor's source +## distribution for a sample. +#DirPortFrontPage /etc/tor/tor-exit-notice.html + +## Uncomment this if you run more than one Tor relay, and add the identity +## key fingerprint of each Tor relay you control, even if they're on +## different networks. You declare it here so Tor clients can avoid +## using more than one of your relays in a single circuit. See +## https://support.torproject.org/relay-operators/multiple-relays/ +## However, you should never include a bridge's fingerprint here, as it would +## break its concealability and potentially reveal its IP/TCP address. +## +## If you are running multiple relays, you MUST set this option. +## +## Note: do not use MyFamily on bridge relays. +#MyFamily $keyid,$keyid,... + +## Uncomment this if you want your relay to be an exit, with the default +## exit policy (or whatever exit policy you set below). +## (If ReducedExitPolicy, ExitPolicy, or IPv6Exit are set, relays are exits. +## If none of these options are set, relays are non-exits.) +#ExitRelay 1 + +## Uncomment this if you want your relay to allow IPv6 exit traffic. +## (Relays do not allow any exit traffic by default.) +#IPv6Exit 1 + +## Uncomment this if you want your relay to be an exit, with a reduced set +## of exit ports. +#ReducedExitPolicy 1 + +## Uncomment these lines if you want your relay to be an exit, with the +## specified set of exit IPs and ports. +## +## A comma-separated list of exit policies. They're considered first +## to last, and the first match wins. +## +## If you want to allow the same ports on IPv4 and IPv6, write your rules +## using accept/reject *. If you want to allow different ports on IPv4 and +## IPv6, write your IPv6 rules using accept6/reject6 *6, and your IPv4 rules +## using accept/reject *4. +## +## If you want to _replace_ the default exit policy, end this with either a +## reject *:* or an accept *:*. Otherwise, you're _augmenting_ (prepending to) +## the default exit policy. Leave commented to just use the default, which is +## described in the man page or at +## https://support.torproject.org/relay-operators +## +## Look at https://support.torproject.org/abuse/exit-relay-expectations/ +## for issues you might encounter if you use the default exit policy. +## +## If certain IPs and ports are blocked externally, e.g. by your firewall, +## you should update your exit policy to reflect this -- otherwise Tor +## users will be told that those destinations are down. +## +## For security, by default Tor rejects connections to private (local) +## networks, including to the configured primary public IPv4 and IPv6 addresses, +## and any public IPv4 and IPv6 addresses on any interface on the relay. +## See the man page entry for ExitPolicyRejectPrivate if you want to allow +## "exit enclaving". +## +#ExitPolicy accept *:6660-6667,reject *:* # allow irc ports on IPv4 and IPv6 but no more +#ExitPolicy accept *:119 # accept nntp ports on IPv4 and IPv6 as well as default exit policy +#ExitPolicy accept *4:119 # accept nntp ports on IPv4 only as well as default exit policy +#ExitPolicy accept6 *6:119 # accept nntp ports on IPv6 only as well as default exit policy +#ExitPolicy reject *:* # no exits allowed + +## Bridge relays (or "bridges") are Tor relays that aren't listed in the +## main directory. Since there is no complete public list of them, even an +## ISP that filters connections to all the known Tor relays probably +## won't be able to block all the bridges. Also, websites won't treat you +## differently because they won't know you're running Tor. If you can +## be a real relay, please do; but if not, be a bridge! +## +## Warning: when running your Tor as a bridge, make sure than MyFamily is +## NOT configured. +#BridgeRelay 1 +## By default, Tor will advertise your bridge to users through various +## mechanisms like https://bridges.torproject.org/. If you want to run +## a private bridge, for example because you'll give out your bridge +## address manually to your friends, uncomment this line: +#BridgeDistribution none + +## Configuration options can be imported from files or folders using the %include +## option with the value being a path. This path can have wildcards. Wildcards are +## expanded first, using lexical order. Then, for each matching file or folder, the following +## rules are followed: if the path is a file, the options from the file will be parsed as if +## they were written where the %include option is. If the path is a folder, all files on that +## folder will be parsed following lexical order. Files starting with a dot are ignored. Files +## on subfolders are ignored. +## The %include option can be used recursively. +#%include /etc/torrc.d/*.conf +