Skip to content

Instantly share code, notes, and snippets.

@mxmeinhold
Last active February 29, 2024 01:44
Show Gist options
  • Save mxmeinhold/50646a762949dcf8fc63a8e98b579c8f to your computer and use it in GitHub Desktop.
Save mxmeinhold/50646a762949dcf8fc63a8e98b579c8f to your computer and use it in GitHub Desktop.
Hosting GPG keys with Nginx and WKD

Hosting my GPG key

I host my gpg key on my personal nginx server, and make it accessible via https (gpg.mxmeinhold.com) and WKD. This means my key is obtainable by curl or by auto-key-locate, e.g.:

$ curl -L gpg.mxmeinhold.com | gpg --import-options show-only --import
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   178  100   178    0     0   2918      0 --:--:-- --:--:-- --:--:--  2966
100 11406  100 11406    0     0  64440      0 --:--:-- --:--:-- --:--:-- 64440
pub   rsa4096 2018-03-05 [SC] [expires: 2023-01-23]
      B77D730E8D444707FA93320D72E05836F8252405
uid                      Max Meinhold <mxmeinhold@gmail.com>
uid                      Max Meinhold <mom@csh.rit.edu>
uid                      Max Meinhold <max@mxmeinhold.com>
sub   rsa4096 2018-03-05 [E] [expires: 2022-02-17]
sub   rsa4096 2018-11-30 [S] [expires: 2022-02-17]
sub   rsa4096 2020-09-27 [A] [expires: 2022-02-17]

$ gpg --auto-key-locate clear,wkd,local --locate-keys max@mxmeinhold.com
gpg: key 72E05836F8252405: "Max Meinhold <mxmeinhold@gmail.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1
pub   rsa4096 2018-03-05 [SC] [expires: 2023-01-23]
      B77D730E8D444707FA93320D72E05836F8252405
uid           [ultimate] Max Meinhold <mxmeinhold@gmail.com>
uid           [ultimate] Max Meinhold <mom@csh.rit.edu>
uid           [ultimate] Max Meinhold <max@mxmeinhold.com>
sub   rsa4096 2018-03-05 [E] [expires: 2022-02-17]
sub   rsa4096 2018-11-30 [S] [expires: 2022-02-17]
sub   rsa4096 2020-09-27 [A] [expires: 2022-02-17]

Having the https link is quite useful for me, since I can pull my pub key via curl or give the short link to others, without having to rely on the keyservers, since I self host it.

HTTPS

This parts pretty simple. I've run gpg --armor --export $KEYID > /var/www/gpg.example.com/pubkey.gpg, and configured nginx to serve the file by default as text/plain. I've attached my config to this gist as gpg.example.com.conf. I'm using certbot for ssl certs.

WKD

WKD actually looks for a given key in 2 different places. If you're searching for example@example.com's key, it will check example.com/.well-known/openpgpkey/hu/<WKD_HASH> and openpgpkey.example.com/.well-known/openpgpkey/example.com/hu/<WKD_HASH>. I'm serving both out of the same directory, so the structure I have is as follows:

.well-known/
  openpgpkey/
    policy
    hu/
      <WKD_HASH>
    example.com/
      policy
      hu/
        <WKD_HASH>

You need the policy files, though they can just be empty files. I have both them and the <WKD_HASH> set as executable, since I was running into permissions issues when I first put this together.

I deploy and update those files from my keyring with the script update-gpg.sh, and serve them with example.com.conf. Note that you'll need the allow origin header in the nginx config.

For testing this, using gpg alone can be a little tricky, because dirmngr will cache failures, and it doesn't have the most verbose reporting. You can use a tester like this one to see if your config is working.

# /etc/nginx/sites-availible/example.com.conf
server {
server_name example.com openpgpkey.example.com;
root /var/www/example.com;
location /.well-known/openpgpkey/hu {
default_type "application/octet-stream";
add_header Access-Control-Allow-Origin * always;
}
location /.well-known/openpgpkey/example.com/hu {
default_type "application/octet-stream";
add_header Access-Control-Allow-Origin * always;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
}
server {
if ($host = openpgpkey.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name example.com openpgpkey.example.com;
return 404; # managed by Certbot
}
# /etc/nginx/sites-available/gpg.example.com.conf
server {
server_name gpg.example.com;
root /var/www/gpg.example.com;
index pubkey.gpg;
default_type text/plain;
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/gpg.example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/gpg.example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = gpg.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name gpg.example.com;
return 404; # managed by Certbot
}
#!/bin/bash
set -euo pipefail
KEYID='<KEY ID TO BE EXPORTED>'
# WKD lookup works by domain, so only the domain you're hosting needs to be updated by this script
DOMAIN='example.com'
# Since this will be plaintext, export in armor format for gpg.$DOMAIN
gpg --armor --export $KEYID > /var/www/gpg.${DOMAIN}/pubkey.gpg
# WKD - get the hashes from gpg and filter to only the email @ the $DOMAIN, and extract the hash
WKDHASH=`gpg --with-wkd-hash -k $KEYID | grep '^\s\+\S\+@'$DOMAIN'$' | sed -s 's/^\s\+\(\S\+\)@'$DOMAIN'$/\1/'`
# Direct key
mkdir -p /var/www/$DOMAIN/.well-known/openpgpkey/hu
gpg --export $KEYID > /var/www/${DOMAIN}/.well-known/openpgpkey/hu/$WKDHASH
# Advanced
cd /var/www/$DOMAIN/.well-known
gpg --list-options show-only-fpr-mbox -k @$DOMAIN | grep $DOMAIN | /usr/lib/gnupg/gpg-wks-client -v --install-key
@utaxiu
Copy link

utaxiu commented Sep 5, 2023

Thank you for great examples!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment