# Fetching a public key to verify a signature


Revaulter exposes the public half of every published signing key on an unauthenticated, cacheable endpoint. Verifiers fetch it once, pin it locally, and from then on can verify signatures fully offline.

## The endpoint

For a published key with id `<KEY_ID>`:

- `GET /v2/signing-keys/<KEY_ID>.jwk` (or `.json`): the JWK plus an anchor-signed publication proof
- `GET /v2/signing-keys/<KEY_ID>.pem` (or `.pub`): the public key as a PEM SubjectPublicKeyInfo

Both endpoints are public and un-authenticated.

## One-time pinning

Trust-on-first-use pins the key locally. After this, verification doesn't need to trust Revaulter at runtime.

```bash
KEY_ID="<key-id-from-publisher>"

# JWK form (for JOSE/JWT libraries)
curl -fsSL "https://revaulter.example.com/v2/signing-keys/$KEY_ID.jwk" \
  | jq '.jwk' > release-signing.jwk

# PEM form (for OpenSSL, Go, Python's `cryptography`, etc.)
curl -fsSL "https://revaulter.example.com/v2/signing-keys/$KEY_ID.pem" \
  > release-signing.pem
```

Inspect the key once, then commit the file to your repo (or distribute it via your usual configuration management). From this point on, none of the verification examples below talks to Revaulter.

## Verifying a JWS or JWT

Any JOSE library works against the JWK form. See the [release-manifest JWS section](/examples/signing-a-release-manifest-with-jws.md) for runnable Python, Node.js, and CLI examples.

## Verifying a raw `r || s` signature

`sign --format raw` emits a 64-byte signature. Most ECDSA libraries accept it directly:

<div class="content-tabs" data-content-tabs data-tabs-label="Content tabs">
  
<section class="content-tab" data-tab-title="Python">
  <div class="content-tab-heading">Python</div>
  <div class="content-tab-body">
    <p>This example uses the <code>cryptography</code> package:</p>
<div class="code-block">
    <button class="code-copy" type="button" aria-label="Copy code to clipboard" title="Copy code" data-copy-code>
      <svg class="code-copy-icon code-copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
        <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M20 6 9 17l-5-5"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-failed" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M18 6 6 18"/>
        <path d="m6 6 12 12"/>
      </svg>
    </button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install cryptography</span></span></code></pre></div></div><p>Code:</p>
<div class="code-block">
    <button class="code-copy" type="button" aria-label="Copy code to clipboard" title="Copy code" data-copy-code>
      <svg class="code-copy-icon code-copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
        <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M20 6 9 17l-5-5"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-failed" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M18 6 6 18"/>
        <path d="m6 6 12 12"/>
      </svg>
    </button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">cryptography.hazmat.primitives</span> <span class="kn">import</span> <span class="n">hashes</span><span class="p">,</span> <span class="n">serialization</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">cryptography.hazmat.primitives.asymmetric</span> <span class="kn">import</span> <span class="n">ec</span><span class="p">,</span> <span class="n">utils</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pub</span> <span class="o">=</span> <span class="n">serialization</span><span class="o">.</span><span class="n">load_pem_public_key</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="s2">&#34;release-signing.pem&#34;</span><span class="p">,</span> <span class="s2">&#34;rb&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the raw r||s bytes and convert to ASN.1 DER for cryptography&#39;s verify()</span>
</span></span><span class="line"><span class="cl"><span class="n">sig_raw</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;dist/myapp-linux-amd64.sig&#34;</span><span class="p">,</span> <span class="s2">&#34;rb&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">sig_raw</span><span class="p">)</span> <span class="o">==</span> <span class="mi">64</span>
</span></span><span class="line"><span class="cl"><span class="n">r</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">sig_raw</span><span class="p">[:</span><span class="mi">32</span><span class="p">],</span> <span class="s2">&#34;big&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="nb">int</span><span class="o">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">sig_raw</span><span class="p">[</span><span class="mi">32</span><span class="p">:],</span> <span class="s2">&#34;big&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">sig_der</span> <span class="o">=</span> <span class="n">utils</span><span class="o">.</span><span class="n">encode_dss_signature</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">s</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;dist/myapp-linux-amd64&#34;</span><span class="p">,</span> <span class="s2">&#34;rb&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">pub</span><span class="o">.</span><span class="n">verify</span><span class="p">(</span><span class="n">sig_der</span><span class="p">,</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">(),</span> <span class="n">ec</span><span class="o">.</span><span class="n">ECDSA</span><span class="p">(</span><span class="n">hashes</span><span class="o">.</span><span class="n">SHA256</span><span class="p">()))</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;OK&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
  </div>
</section>


<section class="content-tab" data-tab-title="Go">
  <div class="content-tab-heading">Go</div>
  <div class="content-tab-body">
    <div class="code-block">
    <button class="code-copy" type="button" aria-label="Copy code to clipboard" title="Copy code" data-copy-code>
      <svg class="code-copy-icon code-copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
        <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M20 6 9 17l-5-5"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-failed" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M18 6 6 18"/>
        <path d="m6 6 12 12"/>
      </svg>
    </button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;crypto/ecdsa&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;crypto/sha256&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;crypto/x509&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;encoding/pem&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;math/big&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">pemBytes</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="s">&#34;release-signing.pem&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">block</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">pem</span><span class="p">.</span><span class="nf">Decode</span><span class="p">(</span><span class="nx">pemBytes</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">pubAny</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">x509</span><span class="p">.</span><span class="nf">ParsePKIXPublicKey</span><span class="p">(</span><span class="nx">block</span><span class="p">.</span><span class="nx">Bytes</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">pub</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">pubAny</span><span class="p">.(</span><span class="o">*</span><span class="nx">ecdsa</span><span class="p">.</span><span class="nx">PublicKey</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">body</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="s">&#34;dist/myapp-linux-amd64&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sig</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nf">ReadFile</span><span class="p">(</span><span class="s">&#34;dist/myapp-linux-amd64.sig&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">sig</span><span class="p">)</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="s">&#34;expected 64-byte raw r||s signature&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">big</span><span class="p">.</span><span class="nx">Int</span><span class="p">).</span><span class="nf">SetBytes</span><span class="p">(</span><span class="nx">sig</span><span class="p">[:</span><span class="mi">32</span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">s</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nb">new</span><span class="p">(</span><span class="nx">big</span><span class="p">.</span><span class="nx">Int</span><span class="p">).</span><span class="nf">SetBytes</span><span class="p">(</span><span class="nx">sig</span><span class="p">[</span><span class="mi">32</span><span class="p">:])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">digest</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">sha256</span><span class="p">.</span><span class="nf">Sum256</span><span class="p">(</span><span class="nx">body</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">ecdsa</span><span class="p">.</span><span class="nf">Verify</span><span class="p">(</span><span class="nx">pub</span><span class="p">,</span><span class="w"> </span><span class="nx">digest</span><span class="p">[:],</span><span class="w"> </span><span class="nx">r</span><span class="p">,</span><span class="w"> </span><span class="nx">s</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="s">&#34;signature verification failed&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;OK&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
  </div>
</section>


<section class="content-tab" data-tab-title="OpenSSL">
  <div class="content-tab-heading">OpenSSL</div>
  <div class="content-tab-body">
    <p>OpenSSL&rsquo;s <code>dgst -verify</code> requires DER-encoded signatures, so wrap the raw bytes once:</p>
<div class="code-block">
    <button class="code-copy" type="button" aria-label="Copy code to clipboard" title="Copy code" data-copy-code>
      <svg class="code-copy-icon code-copy-icon-copy" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
        <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M20 6 9 17l-5-5"/>
      </svg>
      <svg class="code-copy-icon code-copy-icon-failed" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M18 6 6 18"/>
        <path d="m6 6 12 12"/>
      </svg>
    </button><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">python3 - <span class="s">&lt;&lt;&#39;PY&#39; dist/myapp-linux-amd64.sig dist/myapp-linux-amd64.sig.der
</span></span></span><span class="line"><span class="cl"><span class="s">import sys
</span></span></span><span class="line"><span class="cl"><span class="s">from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
</span></span></span><span class="line"><span class="cl"><span class="s">raw = open(sys.argv[1], &#34;rb&#34;).read()
</span></span></span><span class="line"><span class="cl"><span class="s">r = int.from_bytes(raw[:32], &#34;big&#34;); s = int.from_bytes(raw[32:], &#34;big&#34;)
</span></span></span><span class="line"><span class="cl"><span class="s">open(sys.argv[2], &#34;wb&#34;).write(encode_dss_signature(r, s))
</span></span></span><span class="line"><span class="cl"><span class="s">PY</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">openssl dgst -sha256 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -verify release-signing.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -signature dist/myapp-linux-amd64.sig.der <span class="se">\
</span></span></span><span class="line"><span class="cl">  dist/myapp-linux-amd64</span></span></code></pre></div></div>
  </div>
</section>


</div>


## Optional: full anchor-signed verification

The `.jwk`/`.json` response also carries `publicationPayload`, two anchor signatures, and the user's anchor public keys.

Verifying both anchor signatures over the canonical payload proves the same maintainer who registered with Revaulter authorized the publication of this key. Pinning the *anchor* (instead of the published JWK directly) lets the same trust survive signing-key rotations: the anchor is the user's long-lived identity root; the signing key is just a leaf.

This deeper verification matters for high-stakes deployments (auditable supply-chain metadata, multi-tenant servers); for a single-team setup, pinning the JWK once is usually enough.

