Setting up WKD (Web Key Directory) with Cloudflare Worker
Web Key Directory, or WKD for short, seems to me like a massive upgrade from traditional public key servers. Since I use my own domain for email and want my emails encrypted if possible, I’ve decided to set up WKD.
It’s dead simple; all you really need are a few static files under the .well-known/openpgpkey/ directory. Using Cloudflare Workers is definitely overkill for this, if not just wrong.
But I decided to do it anyway since it meant I could forget about availability and stuff. Cloudflare Pages, in theory, should be able to serve this with even fewer things to worry about, but for some reason it didn’t work as I expected.
Before setting up the worker, prepare the key file and the hash:
1# Check hash and copy it.
2HASH=$(gpg-wks-client --print-wkd-hash -q you@example.com | awk '{print $1}')
3
4gpg --export you@example.com > "$HASH"After this, upload the hash file to the R2 bucket.
Make new worker using this code:
1export default {
2 async fetch(request, env) {
3 const { pathname } = new URL(request.url);
4 const corsHeaders = {
5 "Access-Control-Allow-Origin": "*",
6 "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
7 };
8
9 if (request.method === "OPTIONS") {
10 return new Response(null, { headers: corsHeaders });
11 }
12
13 const domain = env.DOMAIN;
14 const hash = env.WKD_HASH;
15
16 // Strict path matching for Direct and Advanced methods
17 const isPolicy = pathname === `/.well-known/openpgpkey/policy` ||
18 pathname === `/.well-known/openpgpkey/${domain}/policy`;
19
20 const isKey = pathname === `/.well-known/openpgpkey/hu/${hash}` ||
21 pathname === `/.well-known/openpgpkey/${domain}/hu/${hash}`;
22
23 if (isPolicy) {
24 return new Response("", {
25 headers: { ...corsHeaders, "Content-Type": "text/plain" }
26 });
27 }
28
29 if (isKey) {
30 // Fetch from R2 bucket
31 const object = await env.WKD.get(hash);
32
33 if (object !== null) {
34 return new Response(object.body, {
35 headers: { ...corsHeaders, "Content-Type": "application/octet-stream" },
36 });
37 }
38 }
39
40 return new Response("Not found", { status: 404 });
41 },
42};You should also bind the bucket we used for the hash file, and configure the environment variables and routes.
Initially, I just set up Custom Domain for the worker. But the moment the subdomain openpgpkey. went online, bots started to scan the shit out of it, invoking the worker each time and wasting computing power (and, more importantly, my free tier usage limit).
At first, the solution seemed obvious: only keep Routes. But this didn’t work because Cloudflare deletes the DNS record for the openpgpkey subdomain, and the Routes setup doesn’t get triggered since no requests went through Cloudflare.
AAAA 100:: openpgpkey.shlewislee.meAdding this (essentially empty) DNS record and making it go through CF proxy solved the issue. I could’ve just let it go to my server but found very little use in that.