A WKD server makes it easy to find genuine PGP keys and avoid fake ones. But, maintaining a WKD server for a one or just a few email addresses would be an overkill.
Fortunately, there are simpler alternatives:
- Self-host the public key.
- Proxy-serve the public key from a third-party keyserver.
- Point a DNS record to the keyserver hosting the public key. Assuming, the keyserver allows this setup.
I like the first option because it gives us more control over how our keys are handled and we only need a webserver FTW! So, let’s self-host!
Create a PGP key pair
Quickly generate a key
gpg --generate-key
OR
Generate a key with advanced options
gpg --full-generate-key
View metadata of your key
gpg --list-keys
Example metadata
pub rsa4096 2025-05-10 [SC] [expires: 2035-05-10]
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
uid [ultimate] Name <name@example.com>
sub rsa4096 2025-05-10 [E] [expires: 2035-05-10]
0000 0000 ...
is the KEY_FINGERPRINT
Export the public key
The public key should be saved unarmored, and named after the WKD hash of itself.
View the WKD hash of your key
gpg --with-wkd-hash --fingerprint KEY_FINGERPRINT
Example output for the WKD hash cmd
uid [ultimate] Name <name@example.com>
WKD_HASH@example.com
The next cmd will save the public key in your Downloads folder, named after the WKD_HASH of your key.
Don’t forget to replace KEY_FINGERPRINT and WKD_HASH with the fingerprint and WKD hash of your key.
Delete whitespaces from the fingerprint, if any.
gpg --no-armor --export --fingerprint KEY_FINGERPRINT > /home/$USER/Downloads/WKD_HASH
Web server configuration
I’ll assume you’ve copied the exported key file to the server.
I use Caddy, so I’ll focus on it’s configuration and skip tutorials for other web servers. Sorry.
Add this to your Caddyfile after replacing example.com and WKD_HASH with your domain and your key’s WKD hash.
openpgpkey.example.com {
header /.well-known/openpgpkey/example.com/policy Content-Type text/plain
respond /.well-known/openpgpkey/example.com/policy `protocol-version 5`
handle_path /.well-known/openpgpkey/example.com/hu/WKD_HASH {
header Content-Type application/octet-stream
root * /path/to/wkd/file/WKD_HASH
file_server
}
}
Comments on Caddyfile configuration:
As per WKD’s RFC draft, the webserver must serve a policy file at /.well-known/openpgpkey/example.com/policy
, with headers: Content-Type text/plain
and protocol-version 5
. However, this file can be empty.
The webserver must serve the unarmored public key file at /.well-known/openpgpkey/example.com/hu/WKD_HASH
with header Content-Type application/octet-stream
.
Testing the WKD setup
When set up correctly, any WKD-compatible client should be able to retrieve the public key from the server over an encrypted TLS connection, potentially with DNSSEC verification against the domain.
You can test your setup using the --locate-keys
option in gpg.
gpg --locate-keys whatever@domainname.tld
It should say new keys imported or output the key’s metadata if it already exists on your machine.
As per my test, the OpenKeychain app on Android could correctly fetch the key from my server using just my email address. Third-party keyserver search was turned off during the test.
It’s almost as if all this setup is just a way to bypass using the --fetch-keys
option.
LMFAO
gpg --fetch-keys https://link-to/public-key.asc