Publish keys via WKD¶
The Web Key Directory (WKD) lets consumers fetch your signing public key over HTTPS from your domain, keyed by the signing email address. It is the publisher side of the external trust anchor that Configure trust consumes — together they give the embedded-key + WKD cross-check that the trust model relies on.
This guide writes a static WKD tree you can serve from any web host.
Generate the tree¶
openpgpkey.WriteWKDTree writes a complete, RFC-compliant directory layout for
one or more email→keys bindings:
import "gitlab.com/phpboyscout/go/signing/openpgpkey"
paths, err := openpgpkey.WriteWKDTree(
"public", // output directory
"example.com", // the domain the keys are served from
openpgpkey.Options{
// Method defaults to MethodAdvanced (dedicated-subdomain layout).
SubmissionAddress: "key-submission@example.com", // optional
},
openpgpkey.Entry{
Email: "release@example.com",
Keys: [][]byte{armoredPub}, // armored or binary; auto-detected
},
)
if err != nil {
return err
}
// `paths` lists every file written.
Entry.Keys accepts the armoured public key you minted with
openpgpkey.ArmoredPublicKey (see Sign with the local backend
or Sign with AWS KMS). Multiple Entry values sharing an
Email merge into one published file.
The resulting layout¶
For the advanced method (the default) and domain example.com:
public/.well-known/openpgpkey/example.com/
├── policy (zero-byte, RFC-required)
├── submission-address (only if Options.SubmissionAddress set)
└── hu/<z-base-32-hash> (one per unique email — binary OpenPGP packets)
The direct method (Options{Method: openpgpkey.MethodDirect}) drops the
<domain>/ level — use it when serving from the domain itself rather than a
dedicated openpgpkey. subdomain.
To find the hashed filename for an address (e.g. to verify hosting):
Serve it¶
Publish the .well-known/openpgpkey/… tree under HTTPS so that, for the advanced
method, this resolves:
WKD mandates HTTPS; the consumer's fetch enforces TLS (inject a hardened
*http.Client on the verify side if you need stricter transport — see
Configure trust).
Note on the WKD hash. The
hu/<hash>local-part hash is SHA-1 by WKD wire-format mandate (draft-koch §3.1) — it is an addressing scheme, not a security control. Authenticity comes from the OpenPGP signature verification, not the filename.
How consumers use it¶
A consumer points verify.KeyResolverConfig.ExternalKeyEmail (or a
WKDResolver) at release@example.com; the resolver fetches the hu/ file,
and a CompositeResolver cross-checks it against the embedded key. See
Configure trust.
What this will not do for you¶
- Serve the tree.
WriteWKDTreewrites files; hosting them under HTTPS at the right URL is yours. Files are written0644and directories0755, because a Web Key Directory is meant to be publicly readable. - Submit a key.
Options.SubmissionAddresswrites a file advertising where submissions go. Nothing here sends or services one. - Publish anything but WKD. There is no keyserver, HKP or DANE support on either the publish or the fetch side.
- Mint an Ed25519 OpenPGP key.
openpgpkeyproduces RSA keys only. An Ed25519 OpenPGP key generated elsewhere can still be published throughEntry.Keysand will verify, because the trust-set policy accepts Ed25519. - Reject a bad domain quietly. A domain containing a path separator,
.., a leading or trailing dot, or any non-hostname character is an error, not a sanitised value — that check is what keeps output insideoutDir/.well-known/openpgpkey.
See also¶
- Configure trust — the consumer/verify side of WKD.
- The trust model — why embedded + WKD.
- File formats — the exact tree layout, the
hu/hash, and the URLs a consumer requests. - WKD helpers on pkg.go.dev/gitlab.com/phpboyscout/go/signing/openpgpkey.