Getting a PGP Key is easy, but sharing it with others can be a hassle.

Why use Web Key Directory

Sharing your public key is important to secure your communication as it allows others to encrypt messages that only you with the corresponding private key can decrypt and to verify that messages are from you. This is especially important for E-Mail, as it is not encrypted by default and can be intercepted and read by the entity handling the E-Mail. However this requires the recipient to have your public key, which needs a way to be shared.

Traditional ways to share your public key

The traditional way to share your public key is to upload it to a keyserver, but this method has its drawbacks. Anyone can upload a key to a keyserver, and there is no way to verify that the key actually belongs to the person it claims to belong to.

Another option would be to download the key from the person’s website, but it would be way harder to find the key in the first place as everyones site would be different and still requires manual work to get the key.

Even sending the Key via E-Mail isn’t great, as the first E-Mail would be unencrypted and the key could be intercepted and replaced with a malicious one and still require manual work to import the key.

The solution

This is where Web Key Directory comes in. WKD is a protocol that enables the discovery of OpenPGP public keys uploaded to people’s own servers, bypassing the need for dedicated keyservers and allowing greater control over the keys. While using HTTPs to get the key, you can be a little more sure that the key used for the email address has been distributed by the owner of the domain, which might be the same person.

WKD allows E-Mail clients, like Thunderbird, to automatically discover the public key of the recipient and directly use it on the first conversation.

Setting up Web Key Directory

WKD has two setups: the Direct setup and the Advanced setup. Despite their names, both require roughly the same steps. The Direct setup requires no additional DNS entries, while the Advanced setup requires a sub-domain with the fixed name openpgpkey to be created, which will be tested first before falling back to the Direct setup.

Requirements

  1. A domain:

    The domain is required to host the public key as otherwise you wouldn’t be able to use WKD. For the basic setup, you just need to have the ability to place files in the .well-known directory of your domain. For the advanced setup, you need to be able to create a subdomain called openpgpkey and place files in the .well-known directory of that subdomain.

  2. A public key that matches the email address host on the domain

  3. A web server:

    The web server is required to host the public key. If you don’t have web hosting or can’t control the web server, you can use Openpgp.org’s WKD-as-a-service. This service allows you to host your public key on their servers by utilizing the advanced setup. This approach comes with the downside of not having full control over the key and it including all the available keys for that identity.

Getting the hash

The basic mode expects the Key at this location: https://{domain}/.well-known/openpgpkey/hu/{hash}?l={email-name} where {domain} is the domain of the email address, {hash} is the WKD hash of the email address and {email-name} is the name part of the email address before the @ using proper percent escaping.

The advanced mode is similar but uses the openpgpkey subdomain and includes the domain in lowercase in the path, like so:

https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hash}?l={email-name}

The hash is calculated by taking the email address, converting it to lowercase, and hashing it using the SHA-1 algorithm. The resulting 160-bit digest is encoded using the Z-Base-32 method to create a 32-octet string. This string is then used as the hash in the URL. However you do not need to do this by hand as the gpg command line tool can do this for you.

1
2
3
4
5
# A specific key:
gpg --with-wkd-hash --fingerprint john@example.com

# All stored keys:
gpg --list-keys --with-wkd-hash

Outputs:

1
2
3
4
5
pub   ed25519 2024-03-16 [SCA] [expires: 2026-03-17]
      82BB 5FCA 5F46 ED3F 04A2  9A66 1973 3425 922A A05E
uid           [ultimate] John Doe <john@example.com>
              wwq7w9d96wfsd4zkytndq84kpkjod3eb@example.com
sub   cv25519 2024-03-16 [E] [expires: 2026-03-17]

The hash is before the @ in the email address, which in this case is wwq7w9d96wfsd4zkytndq84kpkjod3eb.

Now that you have the hash, need to save an binary unarmored version of the public key to the location of the hash, which you can do with the following command:

1
gpg --no-armor --export john@example.com > wwq7w9d96wfsd4zkytndq84kpkjod3eb

Be careful on Windows as it does not use the redirect operator > correctly and the binary data gets messed up. Consider using Git Bash or WSL to prevent this.

DNS

If you plan on using the advanced setup, you need to create a subdomain called openpgpkey and have the web server serve the keys from there. Incase you use the Direct method, you need to make sure the openpgpkey subdomain does not respond. For example when you use wildcard DNS records, make sure that the openpgpkey subdomain is not subject to the wildcarding. This can be done by inserting an empty TXT RR for this sub-domain, which will prevent the wildcard DNS records from influencing the openpgp DNS record, as the Advanced method is tried first before falling back to the Direct method.

Uploading the key

The key needs to be uploaded to the web server at the location of the hash appropriate for the chosen setup. It should be served as application/octet-stream and allow acces from anywhere using the Access-Control-Allow-Origin: * header.

Example configurations for nginx could look like this:

1
2
3
4
location ^~ /.well-known/openpgpkey {
   default_type application/octet-stream;
   add_header Access-Control-Allow-Origin * always;
}

or for Cloudflare pages by creating a _headers file in the output directory:

1
2
3
/.well-known/openpgpkey/*
   Content-Type: application/octet-stream
   Access-Control-Allow-Origin: *

Adding the Policy file

The specification requires that a Policy Flags file is served. This file is required even if the Web Key Directory Update Protocol is not supported, which is a protocol that allows for the automatic update of the public key. But this is not necessary as the keys will be statically hosted and served.

The Policy Flags file can just be an empty file, but it needs to be served at /.well-known/openpgpkey/policy for both the Direct and Advanced setup.

Testing the setup

Testing the setup requires for WKD to be already set up and publicly accessible.

Local check

You can test your setup by the following GnuPG command, which will try to fetch the key from the server:

1
gpg --auto-key-locate clear,nodefault,wkd --locate-keys john@example.com
1
2
3
4
5
6
gpg: key 19733425922AA05E: public key "john@example.com" imported
gpg: Total number processed: 1
gpg:               imported: 1
pub   ed25519 2024-03-17 [SC] [expires: 2026-03-17]
      82BB5FCA5F46ED3F04A29A6619733425922AA05E
uid           john@example.com

For daily use, you can just use the --locate-keys in gpg to automatically fetch the key from the server or look for the appropriate option in your E-Mail client.

Online check

Alternatively, you can use the following form to check if the WKD setup is working. It is a replacement for the Metacode WKD Checker as it will be sunsetted on 1.05.2024. This tool has the benefit of showing which parts of the setup are working and which are not and tests both the Direct and Advanced setup.

Conclusion

Setting up Web Key Directory, is a great way to share your public key with others. It allows for automatic discovery of the public key for example in E-Mail clients, and allows for greater control over the key as it is hosted on your own server and allows communication without manual key exchange. It also allows you to remove old or revoked keys from the server, which is not possible with traditional key servers, which will keep the key forever, cluttering the keyserver with old keys.

Sources: