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/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: "[email protected]", // optional
},
openpgpkey.Entry{
Email: "[email protected]",
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):
hash, _ := openpgpkey.WKDHash("[email protected]") // the hu/<hash> local part
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 [email protected]; the resolver fetches the hu/ file,
and a CompositeResolver cross-checks it against the embedded key. See
Configure trust.
See also¶
- Configure trust — the consumer/verify side of WKD.
- The trust model — why embedded + WKD.
- WKD helpers on pkg.go.dev/gitlab.com/phpboyscout/signing/openpgpkey.