README ΒΆ
pwch (/piΛwΙͺtΚ/)
pwch (short for password change) is a simple Go service for small mail server setups with a PostgreSQL user store and dovecot IMAP server. It will enable your users to change their password on their own via a pure html selfservice portal and encrypt dovecot mailboxes with per user keys. It's suppposed to be run on the same host as your dovecot IMAP server installation.
β Attention
Be sure to backup important emails before following the setup instructions. You'll have to initially encrypt mailboxes for your users manually (see here). pwch will handle all subsequent reencryptions during a password change.
π A note on security
Despite significant efforts to secure the application, there is a potential vulnerability when calling the doveadm binary to reencrypt mailboxes. This vulnerability arises when local attackers possess root permissions and the required knowledge to intercept passwords that are written via stdin to the doveadm command. It is essential to recognize that the only truly secure method of storing emails confidentially is through end-to-end encryption.
β What it does
- checks whether an email address exists in the user database
- sends one time links to change the password to existing email addresses
- enforces configurable password policy
- implements naive rate limiting when sending one time links
- encrypts mailboxes with per user keys derived from their password
β What it does not
- give an attacker hints whether an email address exists in the database
- there is no 'Forgot password' option. It's not possible by design. If you forget your password you will need to reset it manually in the database. All stored emails will be lost then.
β How it works
When a user enters an email address in the selfservice portal, pwch checks whether the address is present in the database. If it is, the given address will receive an email containing a one time link which is valid for a configurable amount of time.
You still need your current password to set a new one. If all checks are passed pwch will directly change the password in the database, run a wrapper script to reencrypt your mailbox and terminate all existing IMAP sessions for your user. If any of the above steps fail, pwch will rollback the changes. The wrapper script executes doveadm commands. That is why dovecot/doveadm has to be installed on the same host.
Attention: The wrapper script needs the setuid bit set.
π What it looks like
The html and css files are fully customizable. This is what the default looks like.
{height=700px}
{height=700px}
π¦ Requirements
- Local dovecot installation with doveadm
- PostgreSQL database with pgcrypto extension enabled (contains user store)
- SMTP server with STARTTLS enabled
- Optional: AppArmor
πΎ Database schema requirements
Take a look at postgres.sql for the minimal requirements to set up your database.
π Dovecot requirements
See dovecot-sql.conf to configure dovecot SQL queries.
You will have to configure dovecot as well to encrypt mailboxes:
mail_plugins = mail_crypt
plugin {
mail_crypt_curve = secp521r1
mail_crypt_require_encrypted_user_key = true
mail_crypt_save_version = 2
}
Please take a look at the official Documentation
π How to deploy
You can use this ansible role to take care of the deployment if you like.
- Create a system group
groupadd --system pwch
- Create a system user
useradd --gid pwch --no-create-home --shell /sbin/nologin --system pwch
- Create the config directory
mkdir /etc/pwch
-
Create the config file at
/etc/pwch/config.yml
. Set owner and group topwch
and remove all permissions to others. -
Create the html assets directory and copy the html assets to this directory.
mkdir /usr/local/src/pwch
- Create the socket directory
mkdir /run/pwch
chown pwch:pwch /run/pwch
-
Copy the pwch binary to
/usr/local/bin/
and runchmod +x
to make it executable. -
Copy the doveadm_wrapper binary to
/usr/local/bin/
and first runchown root:pwch
, then runchmod 4750
to set the setuid bit. -
Copy the systemd unit file to
/etc/systemd/system/
and runsystemctl daemon-reload
-
Enable and start the service
systemctl enable pwch.service
systemctl start pwch.service
AppArmor (Optional)
The pwch policy allows PostgreSQL unix socket connections only.
-
Copy the AppArmor policies to
/etc/apparmor.d/usr.local.bin.pwch
and/etc/apparmor.d/usr.local.bin.doveadm_wrapper
-
Load the policies with
apparmor_parser -r /etc/apparmor.d/usr.local.bin.pwch /etc/apparmor.d/usr.local.bin.doveadm_wrapper
π§ Create new mail user
pwch takes care of password changes of existing users. To create new users you have to execute these steps manually.
- Create initial user password
doveadm pw -s BLF-CRYPT -r 14 // or whatever cost you have set
ATTENTION: Remove the {BLF-CRYPT}
prefix before inserting the password into the database.
- Insert new user into database, e.g.
INSERT INTO accounts (username, domain, password, mail_crypt_salt, quota, enabled, sendonly) VALUES ('user', 'example.org', '$2y$14$28LTS...Fo1YMNK', ENCODE(gen_random_bytes(32), 'hex'), 2048, true, false);
- When the user accounts is present in the database, run:
doveadm -o plugin/mail_crypt_private_password=<salted-sha3-512-hashed password> mailbox cryptokey generate -u <user@example.org> -U
INFO: To calculate the salted sha3-512 hash of the password run this command inside postgres cli (pgcrypto must be enabled)
select encode(digest('<salt><plain_text_password>', 'sha3-512'), 'hex');
- When the mailbox is encrypted tell your new user to change the password.
π§ͺ How to test
Due to the external dependencies unit tests must be executed in a prepared environment. I decided against mocking the dependencies as this would mean pulling in several additional Go dependencies and writing a whole bunch of additional test code.
Instead I wrote an arguably disgusting all-in-one container image that is meant to be used to run unit tests. However, this container functions as continuous integration test as well.
Here's how to run tests:
INFO: All listed commands are expected to be run from the repo's root directory.
- Build the doveadm_wrapper binary
cd cmd/doveadm_wrapper
go build
- Build the container image
cd test
docker build -t pwch-test .
- Run the container in detached mode
docker run -d -v ${PWD}:/pwch --name pwch-test pwch-test
Or if you're running on SELinux:
docker run -d -v ${PWD}:/pwch:Z --name pwch-test pwch-test
- Enter the container, navigate to the test file and run the tests
docker exec -it pwch-test /bin/bash
cd pwch/cmd/pwch
go test -v
That's it!
Remove the container when you're done:
docker stop pwch-test
docker rm pwch-test
β License
This repository is licensed under the AGPL-3.0