OpenPGP WKS for Human Beings
Go to file
s3lph 19cde32909
All checks were successful
/ test (push) Successful in 1m21s
/ codestyle (push) Successful in 1m25s
/ easywksserver_gpgwksclient (push) Successful in 1m43s
/ build_wheel (push) Successful in 1m47s
/ build_debian (push) Successful in 2m41s
feat: migrate from woodpecker to forgejo actions
2023-12-19 07:31:47 +01:00
.forgejo/workflows feat: migrate from woodpecker to forgejo actions 2023-12-19 07:31:47 +01:00
easywks feat: migrate from woodpecker to forgejo actions 2023-12-19 07:31:47 +01:00
package feat: migrate from woodpecker to forgejo actions 2023-12-19 07:31:47 +01:00
test feat(testing): add integration test against gpg-wks-client 2022-12-21 03:10:11 +01:00
.gitignore Initial commit 2021-09-26 09:01:18 +02:00 feat: migrate from woodpecker to forgejo actions 2023-12-19 07:31:47 +01:00 fix(client): Choose correct fingerprint for pubkey 2023-01-31 02:07:33 +01:00
LICENSE Initial commit 2021-09-26 09:01:18 +02:00 feat: provide dns notify on zone updates 2023-04-04 22:59:39 +02:00
setup.cfg Add CI with packaging/release process 2021-09-27 23:40:10 +02:00 chore: migrate from gitlab-ci to woodpecker 2023-07-30 00:48:06 +02:00


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 Directory (WKD): Instead of searching for keys on the usual key servers, WKD is taking a fully 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, they key can be located at

In order to get the keys there, GnuPG developed an email-based protocol named Web Key Service (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 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.


  • Python 3.6 or newer
  • PyYAML
  • PGPy
  • dnspython (for DANE support)
  • Twisted (for DANE support)






Create a cronjob, e.g. in /etc/cron.d/easywks

0 3 * * *    webkey    /path/to/easywks clean


Configuration is done in /etc/easywks.yml (or any other place as specified by --config)

# EasyWKS works inside this directory.  Its PGP keys as well as all
# the submitted and published keys are stored here.
directory: /var/lib/easywks

# Number of seconds after which a pending submission request is
# considered stale and should be removed by easywks clean.
pending_lifetime: 604800

# 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
# HTTPS-terminating reverse proxy!
  port: 8080
  # 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

# Defaults to stdout, supported: stdout, smtp
mailing_method: smtp

# Configure smtp client options
  # Connect to this SMTP server to send a mail.
  host: localhost
  port: 25
  # if tls=True, starttls is ignored
  tls: false
  starttls: false
  # Omit username/password if authentication is not needed.
  username: webkey
  password: SuperS3curePassword123

# Configure the LMTP server
  host: "::1"
  port: 8024

# Configure the authoritative DNS server for DANE zones
  host: "::1"
  port: 8053

# 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.
#  When overriding the "error" template, there's an additional
#  placeholder you can use:
#   - {error}:  The error message.
#  error: |
#    An error has occurred while processing your request:
#    {error}
#    If this error persists, please contact for help.

# Every domain served by EasyWKS must be listed here
    # Users send their requests to this address.  It's up to you to
    # make sure that the mails sent their get handed to EasyWKS.
    # 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:
    passphrase: "Correct Horse Battery Staple"
  # Defaults are gpgwks@<domain> and no password protection. {}


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:
Description=OpenPGP WKS for Human Beings - HTTP Server

ExecStart=/path/to/easywks webserver


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


You can either connect EasyWKS using a pipe transport or as a LMTP server.

Pipe Transport

Add an entry in /etc/postfix/

webkey  unix  -       n       n       -       -       pipe
  flags=DRhu user=webkey argv=/path/to/easywks process

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   webkey:   webkey:

Configure EasyWKS to send outgoing mails via SMTP.

LMTP Server

Configure EasyWKS to run the LMTP server, e.g. using the following systemd unit:

Description=OpenPGP WKS for Human Beings - LMTP Server

ExecStart=/path/to/easywks lmtpd


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:   lmtp:localhost:10024   lmtp:localhost:10024


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:

Description=OpenPGP WKS for Human Beings - DANE DNS Server

ExecStart=/path/to/easywks dnsd


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. You can also configure EasyWKS to provide zone update notifications whenever a key is modified:

      - "2001:db8::53@10053"
      refresh: 300
      retry: 60
      expire: 1209600
      minimal: 300


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. If you want Knot to be notified of zone changes, set up a notify ACL too:

  - id:
    address: [::1]
    action: notify

  - id:
    address: [::1]@10053

  - domain:
    dnssec-signing: on
    dnssec-policy: ...

EasyWKS Client

The file 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:

$ ./
Enter email:
Chose A58D3221F8079F35FF084890505A563492A56583
Enter IMAP/POP3/SMTP password (will not echo): ********
Autoconfigured incoming server: imaps://
Autoconfigured outgoing server: smtp+starttls://
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

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

For more information on WKD and WKS see:


Dance like nobody is watching.
Encrypt live everybody is.

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:

$ cat pubkey.asc | easywks import
Skipping foreign email
Imported key A58D3221F8079F35FF084890505A563492A56583 for email