<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>OpenPGP on Jonatan Miarecki</title><link>https://miarecki.eu/tags/openpgp/</link><description>The latest posts on Jonatan Miarecki</description><language>en</language><managingEditor>jonatan@miarecki.eu (Jonatan Miarecki)</managingEditor><webMaster>jonatan@miarecki.eu (Jonatan Miarecki)</webMaster><lastBuildDate>Thu, 23 Apr 2026 01:31:34 +0200</lastBuildDate><atom:link href="https://miarecki.eu/tags/openpgp/index.xml" rel="self" type="application/rss+xml"/><atom:link rel="search" type="application/opensearchdescription+xml" href="https://miarecki.eu/opensearch.xml" title="Search all available posts"/><item><title>Web Key Directory Checker</title><link>https://miarecki.eu/tools/wkd-checker/</link><pubDate>Sun, 09 Mar 2025 15:00:00 +0000</pubDate><author>jonatan@miarecki.eu (Jonatan Miarecki)</author><guid>https://miarecki.eu/tools/wkd-checker/</guid><description>Check the status of your Web Key Directory (WKD) configuration for email addresses.</description><content:encoded> <![CDATA[<p>This tool verifies Web Key Directory (WKD) configuration for email addresses, checking both direct and advanced discovery methods. It validates policy files, CORS headers, key availability, and proper key association with the email address.</p>
<p>At least one Method is required to be set up for the WKD to work.</p>
<p>More Information on <a href="https://miarecki.eu/posts/web-key-directory-setup/">how to set up WKD</a></p>
<div class="wkd-check-container has-content">
    <form method="post" action="/api/wkd" class="wkd-check-form" id="wkd-check" toolname="check_wkd" tooldescription="Check the status of Web Key Directory (WKD) configuration for an email address.">
        <div class="form-group">
            <input type="email" name="email" id="email" required autocapitalize="none" autocomplete="email"
                   placeholder="Enter email address to check WKD status"
                   class="email-input placeholder"
                   title="Email Address"
                   toolparamdescription="Email address to check" />
        </div>
        <button type="submit" class="check-button">Check WKD Status</button>
    </form>
    <div class="wkd-results" id="wkd-check-result">
<div class="result-section">
<h3>Direct Method <span class="badge badge-unknown">Unknown</span></h3>
<div class="status-grid">
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Location:</span>
<span class="status-value key-location">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Available:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key CORS Valid:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Type:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Fingerprint:</span>
<span class="status-value fingerprint">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Email in Key:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Available:</span>
<span class="status-value">Unknown</span>
</div>
</div>
</div>
<details class="policy-dropdown">
<summary>
Policy Configuration <span class="badge badge-unknown">Unknown</span>
</summary>
<div class="policy-explanation">
The WKD specification requires a policy file to be present (even if empty) to check for WKD support and advanced configuration properties.
</div>
<div class="status-grid">
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Location:</span>
<span class="status-value key-location">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy CORS Valid:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Compliant:</span>
<span class="status-value">Unknown</span>
</div>
</div>
</div>
</details>
</div>
<div class="result-section unknown"> 
<h3>Advanced Method <span class="badge badge-unknown">Unknown</span></h3>
<div class="status-grid">
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Location:</span>
<span class="status-value key-location">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Available:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key CORS Valid:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Key Type:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Fingerprint:</span>
<span class="status-value fingerprint">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Email in Key:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Available:</span>
<span class="status-value">Unknown</span>
</div>
</div>
</div>
<details class="policy-dropdown">
<summary>
Policy Configuration <span class="badge badge-unknown">Unknown</span>
</summary>
<div class="policy-explanation">
The WKD specification requires a policy file to be present (even if empty) to check for WKD support and advanced configuration properties.
</div>
<div class="status-grid">
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Location:</span>
<span class="status-value key-location">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy CORS Valid:</span>
<span class="status-value">Unknown</span>
</div>
</div>
<div class="status-item">
<div class="status-header">
<span class="status-label">Policy Compliant:</span>
<span class="status-value">Unknown</span>
</div>
</div>
</div>
</details>
</div>
<div class="result-section unknown">
<h3>Overall Status <span class="badge badge-unknown">Unknown</span></h3>
<div class="status-grid">
<div class="status-item">
<span class="status-label">Valid:</span>
<span class="status-value">Unknown</span>
</div>
</div>
</div>
</div>
</div>
<h2 id="npm-library">NPM Library</h2>
<p>This tool is powered by the <a href="https://www.npmjs.com/package/wkd-checker">wkd-checker</a> npm library. You can use it in your own projects or run it via CLI. The JSON output below is identical to the library output.</p>
<h3 id="cli-usage">CLI Usage</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npx wkd-checker test@example.com
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="library-usage">Library Usage</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">wkd</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">&#39;wkd-checker&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">email</span> <span class="o">=</span> <span class="s1">&#39;test@example.com&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">wkd</span><span class="p">.</span><span class="nx">checkKey</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">result</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;WKD Result:&#39;</span><span class="p">,</span> <span class="nx">result</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Advanced:&#39;</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nx">advanced</span><span class="p">.</span><span class="nx">valid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Direct:&#39;</span><span class="p">,</span> <span class="nx">result</span><span class="p">.</span><span class="nx">direct</span><span class="p">.</span><span class="nx">valid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">wkd</span><span class="p">.</span><span class="nx">getKey</span><span class="p">(</span><span class="nx">email</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">key</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Retrieved Key:\n&#39;</span><span class="p">,</span> <span class="nx">key</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">err</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Error retrieving key:&#39;</span><span class="p">,</span> <span class="nx">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><details>
<summary><h2>API Documentation</h2></summary>
<p><strong>Endpoint</strong>: <code>POST https://miarecki.eu/api/wkd</code></p>
<p><strong>Method</strong>: <code>POST</code> with <code>Content-Type</code>: <code>application/json</code> or <code>application/x-www-form-urlencoded</code></p>
<p><strong>Request Body</strong>:</p>
<ul>
<li>
<p>JSON Format:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;email&#34;</span><span class="p">:</span> <span class="s2">&#34;user@example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p>x-www-form-urlencoded Format:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">email=user@example.com
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
<p><strong>Response</strong>:</p>
<ul>
<li>
<p>JSON Object with <code>advanced</code> and <code>direct</code> properties, each containing a <code>KeyCheckResult</code> object.</p>
</li>
<li>
<p>Example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;advanced&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policy_location&#34;</span><span class="p">:</span> <span class="s2">&#34;https://openpgpkey.miarecki.eu/.well-known/openpgpkey/miarecki.eu/policy&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyAvailable&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyCorsValid&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policy&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;key_location&#34;</span><span class="p">:</span> <span class="s2">&#34;https://openpgpkey.miarecki.eu/.well-known/openpgpkey/miarecki.eu/hu/u5u31zhdn1tyua7kdr4xmbd7k3h733b7?l=jonatan&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;key_available&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyCorsValid&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyType&#34;</span><span class="p">:</span> <span class="s2">&#34;Invalid&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyTypeValid&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;fingerprint&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;emailInKey&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;expired&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;revoked&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyCompliant&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">false</span>
</span></span><span class="line"><span class="cl"> <span class="p">},</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;direct&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policy_location&#34;</span><span class="p">:</span> <span class="s2">&#34;https://miarecki.eu/.well-known/openpgpkey/policy&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyAvailable&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyCorsValid&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policy&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;mailboxOnly&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;daneOnly&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;authSubmit&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;protocolVersion&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;submissionAddress&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="nt">&#34;others&#34;</span><span class="p">:</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">   <span class="p">},</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;key_location&#34;</span><span class="p">:</span> <span class="s2">&#34;https://miarecki.eu/.well-known/openpgpkey/hu/u5u31zhdn1tyua7kdr4xmbd7k3h733b7?l=jonatan&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;key_available&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyCorsValid&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyType&#34;</span><span class="p">:</span> <span class="s2">&#34;BinaryKey&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;keyTypeValid&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;fingerprint&#34;</span><span class="p">:</span> <span class="s2">&#34;18EECCF319422F31D717796880B1C14A34B7634A&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;emailInKey&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;expired&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;revoked&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;policyCompliant&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;valid&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
</ul>
]]></content:encoded></item><item><title>Setting up Web Key Directory</title><link>https://miarecki.eu/posts/web-key-directory-setup/</link><pubDate>Sun, 17 Mar 2024 15:00:00 +0000</pubDate><author>jonatan@miarecki.eu (Jonatan Miarecki)</author><guid>https://miarecki.eu/posts/web-key-directory-setup/</guid><description>Web Key Directory (WKD) is a protocol that enables discovery of OpenPGP public keys uploaded to your own server to secure your communication.</description><content:encoded> <![CDATA[<p>Getting a PGP Key is easy, but sharing it with others can be a hassle.</p>
<h2 id="why-use-web-key-directory">Why use Web Key Directory</h2>
<p>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.</p>
<h3 id="traditional-ways-to-share-your-public-key">Traditional ways to share your public key</h3>
<p>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.</p>
<p>Another option would be to download the key from the person&rsquo;s website, but it would be much harder to find the key in the first place as everyone&rsquo;s site would be different and still requires manual work to get the key.</p>
<p>Even sending the Key via E-Mail isn&rsquo;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.</p>
<h3 id="the-solution">The solution</h3>
<p>This is where Web Key Directory comes in. <dfn title="Web Key Directory">WKD</dfn> is a protocol that enables the discovery of OpenPGP public keys uploaded to people&rsquo;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.</p>
<p>WKD allows E-Mail clients, like Thunderbird, to automatically discover the public key of the recipient and directly use it on the first conversation.</p>
<h2 id="setting-up-web-key-directory">Setting up Web Key Directory</h2>
<p>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 <code>openpgpkey</code> to be created, which will be tested first before falling back to the Direct setup.</p>
<h3 id="requirements">Requirements</h3>
<ol>
<li>
<p>A domain:</p>
<p>The domain is required to host the public key as otherwise you wouldn&rsquo;t be able to use WKD. For the basic setup, you just need to have the ability to place files in the <code>.well-known</code> directory of your domain. For the advanced setup, you need to be able to create a subdomain called <code>openpgpkey</code> and place files in the <code>.well-known</code> directory of that subdomain.</p>
</li>
<li>
<p>A public key that matches the email address host on the domain</p>
</li>
<li>
<p>A web server:</p>
<p>The web server is required to host the public key. If you don&rsquo;t have web hosting or can&rsquo;t control the web server, you can use <a href="https://keys.openpgp.org/about/usage#wkd-as-a-service">Openpgp.org&rsquo;s WKD-as-a-service</a>. 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.</p>
</li>
</ol>
<h3 id="getting-the-hash">Getting the hash</h3>
<p>The basic mode expects the Key at this location:
<code>https://{domain}/.well-known/openpgpkey/hu/{hash}?l={email-name}</code> 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.</p>
<p>The advanced mode is similar but uses the openpgpkey subdomain and includes the domain in lowercase in the path, like so:</p>
<p><code>https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hash}?l={email-name}</code></p>
<p>The hash is calculated by taking the local-part of the email address (the part before the @), converting all ASCII characters to lowercase, and hashing it using the SHA-1 algorithm. The resulting 160-bit digest is encoded using the <a href="https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt">Z-Base-32</a> 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.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># A specific key:</span>
</span></span><span class="line"><span class="cl">gpg --with-wkd-hash --fingerprint john@example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># All stored keys:</span>
</span></span><span class="line"><span class="cl">gpg --list-keys --with-wkd-hash
</span></span></code></pre></td></tr></table>
</div>
</div><p>Outputs:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="hl"><span class="lnt">4
</span></span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">pub   ed25519 2024-03-16 [SCA] [expires: 2026-03-17]
</span></span><span class="line"><span class="cl">      82BB 5FCA 5F46 ED3F 04A2  9A66 1973 3425 922A A05E
</span></span><span class="line"><span class="cl">uid           [ultimate] John Doe &lt;john@example.com&gt;
</span></span><span class="line hl"><span class="cl">              wwq7w9d96wfsd4zkytndq84kpkjod3eb@example.com
</span></span><span class="line"><span class="cl">sub   cv25519 2024-03-16 [E] [expires: 2026-03-17]
</span></span></code></pre></td></tr></table>
</div>
</div><p>The hash is before the @ in the email address, which in this case is <code>wwq7w9d96wfsd4zkytndq84kpkjod3eb</code>.</p>
<p>Now that you have the hash, you need to save a binary unarmored version of the public key to the location of the hash, which you can do with the following command:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gpg --no-armor --export john@example.com &gt; wwq7w9d96wfsd4zkytndq84kpkjod3eb
</span></span></code></pre></td></tr></table>
</div>
</div><p>Be careful on Windows as it does not use the redirect operator <code>&gt;</code> correctly and the binary data gets messed up. Consider using Git Bash or WSL to prevent this.</p>
<h3 id="dns">DNS</h3>
<p>If you plan on using the advanced setup, you need to create a subdomain called <code>openpgpkey</code> and have the web server serve the keys from there.
In case 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.</p>
<h3 id="uploading-the-key">Uploading the key</h3>
<p>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 <code>application/octet-stream</code> and allow access from anywhere using the <code>Access-Control-Allow-Origin: *</code> header.</p>
<p>Example configurations for nginx could look like this:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">^~</span> <span class="s">/.well-known/openpgpkey</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kn">default_type</span> <span class="s">application/octet-stream</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="kn">add_header</span> <span class="s">Access-Control-Allow-Origin</span> <span class="s">*</span> <span class="s">always</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>or for Cloudflare pages by creating a _headers file in the output directory:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">/.well-known/openpgpkey/*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nt">Content-Type</span><span class="p">:</span><span class="w"> </span><span class="l">application/octet-stream</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nt">Access-Control-Allow-Origin</span><span class="p">:</span><span class="w"> </span>*<span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="adding-the-policy-file">Adding the Policy file</h3>
<p>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.</p>
<p>The Policy Flags file can just be an empty file. For the Direct setup, it needs to be served at <code>/.well-known/openpgpkey/policy</code>, and for the Advanced setup at <code>/.well-known/openpgpkey/{domain}/policy</code>.</p>
<h2 id="testing-the-setup">Testing the setup</h2>
<p>Testing the setup requires WKD to be already set up and publicly accessible.</p>
<h3 id="local-check">Local check</h3>
<p>You can test your setup by the following GnuPG command, which will try to fetch the key from the server:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gpg --auto-key-locate clear,nodefault,wkd --locate-keys john@example.com
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gpg: key 19733425922AA05E: public key <span class="s2">&#34;john@example.com&#34;</span> imported
</span></span><span class="line"><span class="cl">gpg: Total number processed: <span class="m">1</span>
</span></span><span class="line"><span class="cl">gpg:               imported: <span class="m">1</span>
</span></span><span class="line"><span class="cl">pub   ed25519 2024-03-17 <span class="o">[</span>SC<span class="o">]</span> <span class="o">[</span>expires: 2026-03-17<span class="o">]</span>
</span></span><span class="line"><span class="cl">      82BB5FCA5F46ED3F04A29A6619733425922AA05E
</span></span><span class="line"><span class="cl">uid           john@example.com
</span></span></code></pre></td></tr></table>
</div>
</div><p>For daily use, you can just use the <code>--locate-keys</code> in gpg to automatically fetch the key from the server or look for the appropriate option in your E-Mail client.</p>
<h3 id="online-check">Online check</h3>
<p>Alternatively, you can use the following form to check if the WKD setup is working. It is a replacement for the <a href="https://metacode.biz/openpgp/web-key-directory">Metacode WKD Checker</a> 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.</p>
<div class="wkd-check">
    <form method="get" action="/tools/wkd-checker/" class="form" id="wkd-check" target="_blank">
        <div class="form-input">
            <input type="email" name="email" id="email" required placeholder="The email address you want to check"
                title="Email Address" />
        </div>
        <input value="Check" type="submit" class="form-button" />
    </form>
   <div class="wkd-check-result" id="wkd-check-result"></div>
</div>
<h2 id="conclusion">Conclusion</h2>
<p>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.</p>
<p>Sources:</p>
<ul>
<li><a href="https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/">OpenPGP Web Key Directory Internet-Draft</a></li>
</ul>
]]></content:encoded></item></channel></rss>