2021-09-26 08:40:02 +02:00
|
|
|
|
# EasyWKS
|
|
|
|
|
|
|
|
|
|
### OpenPGP WKS for Human Beings
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## What is WKD/WKS?
|
|
|
|
|
|
|
|
|
|
Due to all the issues involved with the PGP key servers we're using today, GnuPG introduced a feature named [**Web Key
|
2023-04-04 20:15:46 +02:00
|
|
|
|
Directory**][wkd] (WKD): Instead of searching for keys on the usual key servers, WKD is taking a **fully**
|
2021-09-26 08:40:02 +02:00
|
|
|
|
decentralized and federated approach, where each mail domain is responsible for hosting its users public keys on an
|
|
|
|
|
HTTPS web directory. For example, in order to retrieve the key for `john.doe@example.org`, they key can be located at
|
|
|
|
|
|
|
|
|
|
https://openpgpkey.example.org/.well-known/openpgpkey/example.org/hu/iy9q119eutrkn8s1mk4r39qejnbu3n5q?l=john.doe
|
|
|
|
|
|
|
|
|
|
In order to get the keys there, GnuPG developed an email-based protocol named [**Web Key Service**][wks] (WKS), which
|
|
|
|
|
lets users publish their public keys once they have proven ownership of the key using an email-based challenge-responce
|
|
|
|
|
mechanism.
|
|
|
|
|
|
|
|
|
|
At the time, WKS and WKD aren't yet part of the OpenPGP standard, but there's an [IETF Draft RFC][ietf] that's being
|
|
|
|
|
updated from time to time.
|
|
|
|
|
|
|
|
|
|
## Why EasyWKS?
|
|
|
|
|
|
|
|
|
|
I've experienced the WKS standard to be extremely cumbersome to conform to. The tools shipped with GnuPG,
|
|
|
|
|
`gpg-wks-server` and `gpg-wks-client` are usable well enough as long as you have shell access on a mailserver and pipe
|
|
|
|
|
the output of `gpg-wks-client` into `sendmail`. However, I'm usually doing my email via SMTP using a variety of
|
|
|
|
|
clients.
|
|
|
|
|
|
|
|
|
|
No matter which client I tried, I did not manage to get them to send mails in the format required by `gpg-wks-server`.
|
|
|
|
|
Even pasting the output of `gpg-wks-client` into `openssl s_client` after manually performing SMTP auth proved to be
|
|
|
|
|
difficult in case any line of the PGP-encrypted message happened to start with `Q` or `R`.
|
|
|
|
|
|
|
|
|
|
So I decided to write a WKS server that's much more lenient regarding the exact format of the mails it receives.
|
|
|
|
|
Instead of enforcing the strict format mandated by the standard, EasyWKS only requires:
|
|
|
|
|
|
|
|
|
|
- For the initial submission request: An unsigned & encrypted PGP/MIME message anywhere in the MIME tree, and the
|
|
|
|
|
ASCII-armored PGP-Key anywhere inside the encrypted message.
|
|
|
|
|
- For the confirmation response: A signed & encrypted PGP/MIME message anywhere in the MIME tree, and the
|
|
|
|
|
confirmation response anywhere inside the encrypted message.
|
|
|
|
|
|
|
|
|
|
This makes EasyWKS usable with every mail client, no matter whether WKS support is built-in or not.
|
|
|
|
|
|
|
|
|
|
EasyWKS aims to be a drop-in replacement for gpg-wks-server; see "Bootstrapping" below to learn how to migrate from
|
|
|
|
|
gpg-wks-server to EasyWKS.
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
- Python 3.6 or newer
|
|
|
|
|
- PyYAML
|
|
|
|
|
- bottle.py
|
|
|
|
|
- PGPy
|
2023-04-04 20:15:46 +02:00
|
|
|
|
- dnspython (for DANE support)
|
|
|
|
|
- Twisted (for DANE support)
|
2021-09-26 08:40:02 +02:00
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
MIT
|
|
|
|
|
|
|
|
|
|
## Setup
|
|
|
|
|
|
|
|
|
|
### Installation
|
|
|
|
|
|
|
|
|
|
TODO
|
|
|
|
|
|
|
|
|
|
Create a cronjob, e.g. in `/etc/cron.d/easywks`
|
|
|
|
|
|
|
|
|
|
```crontab
|
|
|
|
|
0 3 * * * webkey /path/to/easywks clean
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Configuration
|
|
|
|
|
|
|
|
|
|
Configuration is done in `/etc/easywks.yml` (or any other place as specified by `--config`)
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
---
|
2022-01-12 04:13:00 +01:00
|
|
|
|
# EasyWKS works inside this directory. Its PGP keys as well as all
|
|
|
|
|
# the submitted and published keys are stored here.
|
2021-09-26 08:40:02 +02:00
|
|
|
|
directory: /var/lib/easywks
|
2021-09-29 00:44:07 +02:00
|
|
|
|
|
|
|
|
|
# Number of seconds after which a pending submission request is
|
|
|
|
|
# considered stale and should be removed by easywks clean.
|
2021-09-26 08:40:02 +02:00
|
|
|
|
pending_lifetime: 604800
|
2021-09-29 00:44:07 +02:00
|
|
|
|
|
|
|
|
|
# Some clients (including recent versions of gpg-wks-client follow an
|
|
|
|
|
# older version of the WKS standard where signing the confirmation
|
|
|
|
|
# response is only recommended, but not required. Set this option to
|
|
|
|
|
# true if you want to accept such unsigned responses.
|
|
|
|
|
permit_unsigned_response: false
|
|
|
|
|
|
|
|
|
|
# Port configuration for the webserver. Put this behind an
|
2021-09-26 08:40:02 +02:00
|
|
|
|
# HTTPS-terminating reverse proxy!
|
2021-09-29 00:44:07 +02:00
|
|
|
|
httpd:
|
|
|
|
|
host: 127.0.0.1
|
|
|
|
|
port: 8080
|
2021-09-29 03:52:07 +02:00
|
|
|
|
# Some older HTTP clients omit the ?l=<userid> query suffix. Set
|
|
|
|
|
# this to false in order to permit such clients to retrieve keys.
|
|
|
|
|
#require_user_urlparam: true
|
2021-09-29 00:44:07 +02:00
|
|
|
|
|
2021-09-26 11:12:01 +02:00
|
|
|
|
# Defaults to stdout, supported: stdout, smtp
|
|
|
|
|
mailing_method: smtp
|
2021-09-29 00:44:07 +02:00
|
|
|
|
|
2021-09-26 11:12:01 +02:00
|
|
|
|
# Configure smtp client options
|
|
|
|
|
smtp:
|
|
|
|
|
# Connect to this SMTP server to send a mail.
|
2021-09-26 17:11:05 +02:00
|
|
|
|
host: localhost
|
2021-09-26 11:12:01 +02:00
|
|
|
|
port: 25
|
|
|
|
|
# if tls=True, starttls is ignored
|
|
|
|
|
tls: false
|
|
|
|
|
starttls: false
|
|
|
|
|
# Omit username/password if authentication is not needed.
|
|
|
|
|
username: webkey
|
|
|
|
|
password: SuperS3curePassword123
|
2021-10-03 02:32:33 +02:00
|
|
|
|
|
2021-09-26 17:11:05 +02:00
|
|
|
|
# Configure the LMTP server
|
2021-10-03 02:32:33 +02:00
|
|
|
|
lmtpd:
|
2021-09-26 17:11:05 +02:00
|
|
|
|
host: "::1"
|
|
|
|
|
port: 8024
|
2021-10-03 02:32:33 +02:00
|
|
|
|
|
2023-04-04 20:15:46 +02:00
|
|
|
|
# Configure the authoritative DNS server for DANE zones
|
|
|
|
|
dnsd:
|
|
|
|
|
host: "::1"
|
|
|
|
|
port: 8053
|
|
|
|
|
|
2021-10-03 02:32:33 +02:00
|
|
|
|
# You can override the mail response templates with your own text.
|
|
|
|
|
# The following templates can be overridden:
|
|
|
|
|
# - "header": Placed in front of every message.
|
|
|
|
|
# - "footer": Appended to every message.
|
|
|
|
|
# - "confirm": Sent with the confirmation request.
|
|
|
|
|
# - "done": Sent after a key was published.
|
|
|
|
|
# - "error": Sent when an error occurs.
|
|
|
|
|
# The following placeholders can be used (enclosed in curly braces):
|
|
|
|
|
# - {domain}: The email domain for with the request is processed.
|
|
|
|
|
# - {sender}: The submitter's mail address.
|
|
|
|
|
# - {submission}: The submission address.
|
2022-01-12 04:13:00 +01:00
|
|
|
|
# When overriding the "error" template, there's an additional
|
2021-10-03 02:32:33 +02:00
|
|
|
|
# placeholder you can use:
|
|
|
|
|
# - {error}: The error message.
|
|
|
|
|
#responses:
|
|
|
|
|
# error: |
|
|
|
|
|
# An error has occurred while processing your request:
|
|
|
|
|
#
|
|
|
|
|
# {error}
|
|
|
|
|
#
|
|
|
|
|
# If this error persists, please contact admin@example.org for help.
|
|
|
|
|
|
2021-09-26 08:40:02 +02:00
|
|
|
|
# Every domain served by EasyWKS must be listed here
|
|
|
|
|
domains:
|
|
|
|
|
example.org:
|
2021-09-29 00:44:07 +02:00
|
|
|
|
# Users send their requests to this address. It's up to you to
|
|
|
|
|
# make sure that the mails sent their get handed to EasyWKS.
|
2021-09-27 23:40:10 +02:00
|
|
|
|
submission_address: webkey@example.org
|
2021-09-29 00:44:07 +02:00
|
|
|
|
# If you want the PGP key for this domain to be password-
|
|
|
|
|
# protected, or if you're supplying your own password-protected
|
|
|
|
|
# key, set the passphrase here:
|
2021-09-26 08:40:02 +02:00
|
|
|
|
passphrase: "Correct Horse Battery Staple"
|
|
|
|
|
# Defaults are gpgwks@<domain> and no password protection.
|
|
|
|
|
example.com: {}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Bootstrapping
|
|
|
|
|
|
|
|
|
|
Run `easywks init` (as the correct user) to initialize all files and directories. This will also generate a PGP key for
|
|
|
|
|
each domain, stored in `<workdir>/<domain>/key.pgp`. If you want to, you can replace this key by a key you generated
|
|
|
|
|
yourself. If your key is password-protected, you have to supply the passphrase in the config file or remove the
|
|
|
|
|
password protection.
|
|
|
|
|
|
|
|
|
|
If you are migrating from gpg-wks-server to EasyWKS, you can point EasyWKS to gpg-wks-server's working directory
|
|
|
|
|
(usually `/var/lib/gnupg/wks`). EasyWKS uses the same directory layout and file formats as gpg-wks-server, so it should
|
|
|
|
|
be able to take over where gpg-wks-server stopped. The only thing you need to do is to export the private keys from the
|
|
|
|
|
GnuPG keyring and write them to their domain's `key.pgp`.
|
|
|
|
|
|
|
|
|
|
### Webserver Setup
|
|
|
|
|
|
|
|
|
|
There are generally two ways to get WKD working:
|
|
|
|
|
|
|
|
|
|
- The gpg-wks-server approach, i.e. regularly copying the `hu` directories from EasyWKS' working directory to the
|
|
|
|
|
webroot.
|
|
|
|
|
- Running the EasyWKS web server behind a Reverse Proxy, e.g. using the follwing systemd unit:
|
|
|
|
|
|
|
|
|
|
```unit file (systemd)
|
|
|
|
|
[Unit]
|
2021-09-26 17:11:05 +02:00
|
|
|
|
Description=OpenPGP WKS for Human Beings - HTTP Server
|
2021-09-26 08:40:02 +02:00
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
ExecStart=/path/to/easywks webserver
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
User=webkey
|
|
|
|
|
Group=webkey
|
|
|
|
|
WorkingDirectory=/var/lib/easywks
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Once this service is up and running, you can configure your webserver to proxy requests to EasyWKS. E.g. with Apache2:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
ProxyPass /.well-known/openpgpkey/ http://127.0.0.1:8080/.well-known/openpgpkey/
|
|
|
|
|
ProxyPassReverse /.well-known/openpgpkey/ http://127.0.0.1:8080/.well-known/openpgpkey/
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note that the webserver built into EasyWKS validates that the `?l=<uid>` matches the uid hash (the last URL component).
|
|
|
|
|
|
|
|
|
|
### MTA Setup
|
|
|
|
|
|
|
|
|
|
#### Postfix
|
|
|
|
|
|
2021-09-26 17:11:05 +02:00
|
|
|
|
You can either connect EasyWKS using a pipe transport or as a LMTP server.
|
|
|
|
|
|
|
|
|
|
##### Pipe Transport
|
|
|
|
|
|
2021-09-26 08:40:02 +02:00
|
|
|
|
Add an entry in `/etc/postfix/master.cf`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
webkey unix - n n - - pipe
|
2021-09-26 11:12:01 +02:00
|
|
|
|
flags=DRhu user=webkey argv=/path/to/easywks process
|
2021-09-26 08:40:02 +02:00
|
|
|
|
```
|
|
|
|
|
|
2021-09-26 17:11:05 +02:00
|
|
|
|
Then tell postfix to forward mails to the submission addresses to said transport, e.g. in `/etc/postfix/transport`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
# The colon at the end is important, it lets postfix know
|
|
|
|
|
# that "webkey" is a transport, not a name
|
|
|
|
|
gpgwks@example.org webkey:
|
|
|
|
|
webkey@example.com webkey:
|
|
|
|
|
```
|
|
|
|
|
|
2021-09-26 11:12:01 +02:00
|
|
|
|
Configure EasyWKS to send outgoing mails via SMTP.
|
|
|
|
|
|
2021-09-26 17:11:05 +02:00
|
|
|
|
##### LMTP Server
|
|
|
|
|
|
|
|
|
|
Configure EasyWKS to run the LMTP server, e.g. using the following systemd unit:
|
|
|
|
|
|
|
|
|
|
```unit file (systemd)
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=OpenPGP WKS for Human Beings - LMTP Server
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
ExecStart=/path/to/easywks lmtpd
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
User=webkey
|
|
|
|
|
Group=webkey
|
|
|
|
|
WorkingDirectory=/var/lib/easywks
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Also configure EasyWKS to send outgoing mails via SMTP.
|
|
|
|
|
|
|
|
|
|
Then tell postfix to forward mails to the submission addresses to said transport, e.g. in `/etc/postfix/transport`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
gpgwks@example.org lmtp:localhost:10024
|
|
|
|
|
webkey@example.com lmtp:localhost:10024
|
|
|
|
|
```
|
2021-09-26 08:40:02 +02:00
|
|
|
|
|
2023-04-04 20:15:46 +02:00
|
|
|
|
### DANE DNS Setup
|
|
|
|
|
|
|
|
|
|
Apart from WKD, EasyWKS can also serve PGP keys using RFC7929 DNS records ("OPENPGPKEY" or "TYPE61" records). However,
|
|
|
|
|
since EasyWKS does not implement DNSSEC signing, it cannot do this alone. The authoritative DNS server in EasyWKS only
|
|
|
|
|
responds to AXFR zone transfer requests. In order for DANE lookups to work, the zones must be replicated (AXFR'd) by an
|
|
|
|
|
authoritative secondary nameserver that signs the zones itself.
|
|
|
|
|
|
|
|
|
|
#### EasyWKS DNS Server
|
|
|
|
|
|
|
|
|
|
Configure EasyWKS to run the DNS server, e.g. using the following systemd unit:
|
|
|
|
|
|
|
|
|
|
```unit file (systemd)
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=OpenPGP WKS for Human Beings - DANE DNS Server
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
Type=simple
|
|
|
|
|
ExecStart=/path/to/easywks dnsd
|
|
|
|
|
Restart=on-failure
|
|
|
|
|
User=webkey
|
|
|
|
|
Group=webkey
|
|
|
|
|
WorkingDirectory=/var/lib/easywks
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If you're using EasyWKS' DANE feature, it is highly recommended to configure the SOA and NS records for each domain
|
|
|
|
|
you're serving. Generally you want to add NS records for all nameservers that will be serving your zone, and at least
|
|
|
|
|
set the MNAME and RNAME components of the SOA record:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
domains:
|
|
|
|
|
example.org:
|
|
|
|
|
ns:
|
|
|
|
|
- ns1.example.org.
|
|
|
|
|
- ns2.example.org.
|
|
|
|
|
- ns1.example.com.
|
|
|
|
|
- ns2.example.com.
|
|
|
|
|
soa:
|
|
|
|
|
mname: ns1.example.org.
|
|
|
|
|
rname: dnsadmin.example.org.
|
|
|
|
|
refresh: 300
|
|
|
|
|
retry: 60
|
|
|
|
|
expire: 1209600
|
|
|
|
|
minimal: 300
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Knot
|
|
|
|
|
|
|
|
|
|
Knot is an authoritative nameserver that supports signing a replicated zone by the secondary (replicating) nameserver.
|
|
|
|
|
|
|
|
|
|
To configure Knot to transfer the zone from EasyWKS, set up an EasyWKS remote and use it as the replication master for
|
|
|
|
|
DANE zones. DNSSEC signing must be enabled as well:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
remote:
|
|
|
|
|
- id: remote-easywks.example.org
|
|
|
|
|
address: [::1]@10053
|
|
|
|
|
|
|
|
|
|
zone:
|
|
|
|
|
- domain: _openpgpkey.example.org
|
|
|
|
|
master: remote-easywks.example.org
|
|
|
|
|
dnssec-signing: on
|
|
|
|
|
dnssec-policy: ...
|
|
|
|
|
```
|
|
|
|
|
|
2022-01-12 04:13:00 +01:00
|
|
|
|
## EasyWKS Client
|
|
|
|
|
|
|
|
|
|
The file `client.py` contains a self-contained WKS client, which
|
|
|
|
|
prompts you for your email address and IMAP/SMTP/POP3 password, and
|
|
|
|
|
then attempts to figure out the mail servers via common
|
|
|
|
|
autoconfiguration methods. Afterwards it will attempt a WKS key submission:
|
|
|
|
|
|
|
|
|
|
```console?prompt=$,
|
|
|
|
|
$ ./client.py
|
|
|
|
|
Enter email: john.doe@example.org
|
|
|
|
|
Chose A58D3221F8079F35FF084890505A563492A56583
|
|
|
|
|
Enter IMAP/POP3/SMTP password (will not echo): ********
|
|
|
|
|
Autoconfigured incoming server: imaps://john.doe@example.org@imap.example.org:993
|
|
|
|
|
Autoconfigured outgoing server: smtp+starttls://john.doe@example.org@smtp.example.org:587
|
|
|
|
|
Please confirm: [Y/n] y
|
|
|
|
|
Retrieved submission key
|
|
|
|
|
Retrieved key to publish
|
|
|
|
|
Created encrypted message
|
|
|
|
|
Sending submission request
|
|
|
|
|
Awaiting response
|
|
|
|
|
Received confirmation request
|
|
|
|
|
Nonce: 95184efbc5d2f75ed4b56162
|
|
|
|
|
Creating confirmation response. GnuPG may prompt you for your passphrase.
|
|
|
|
|
Sending confirmation response
|
|
|
|
|
Awaiting publish response
|
|
|
|
|
Decrypting WKS response. GnuPG may prompt you for your passphrase.
|
|
|
|
|
|
|
|
|
|
Hi there!
|
|
|
|
|
|
|
|
|
|
This is the EasyWKS system at example.org
|
|
|
|
|
|
|
|
|
|
Your key has been published to the Web Key Directory.
|
|
|
|
|
You can test WKD key retrieval e.g. with:
|
|
|
|
|
|
|
|
|
|
gpg --auto-key-locate=wkd,nodefault --locate-key john.doe@example.org
|
|
|
|
|
|
|
|
|
|
For more information on WKD and WKS see:
|
|
|
|
|
|
|
|
|
|
https://gnupg.org/faq/wkd.html
|
|
|
|
|
https://gnupg.org/faq/wks.html
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Regards
|
|
|
|
|
EasyWKS
|
|
|
|
|
|
|
|
|
|
--
|
|
|
|
|
Dance like nobody is watching.
|
|
|
|
|
Encrypt live everybody is.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2022-11-26 19:00:16 +01:00
|
|
|
|
## Manual Key Import
|
|
|
|
|
|
|
|
|
|
In addition to WKS, EasyWKS also provides a command line interface for
|
|
|
|
|
importing keys from standard input. This feature is mainly intended
|
|
|
|
|
to be used for technical email accounts where using WKS might prove to
|
|
|
|
|
be difficult:
|
|
|
|
|
|
|
|
|
|
```console?prompt=$,
|
|
|
|
|
$ cat pubkey.asc | easywks import
|
|
|
|
|
Skipping foreign email john.doe@notmydepartment.org
|
|
|
|
|
Imported key A58D3221F8079F35FF084890505A563492A56583 for email john.doe@example.org
|
|
|
|
|
```
|
|
|
|
|
|
2021-09-26 08:40:02 +02:00
|
|
|
|
[wkd]: https://wiki.gnupg.org/WKD
|
|
|
|
|
[wks]: https://wiki.gnupg.org/WKS
|
2022-01-12 04:13:00 +01:00
|
|
|
|
[ietf]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service-12
|