Revaulter GitHub

Signing a release manifest with JWS

You can use Revaulter’s sign operation with --format jws to produce a passkey-approved compact JWS. ES256 is a standard JOSE algorithm, so the output is verifiable by any JWT/JOSE library without any hand-rolled signature conversion.

Setup#

Sign in to the Revaulter web UI, open the signing keys section, and publish a key under a label like release-signing. Note the published key ID — it’s the URL you’ll share with verifiers:

https://revaulter.example.com/v2/signing-keys/<KEY_ID>.jwk

Signing a release#

# Build a small JSON manifest describing the release
jq -n --arg v v1.2.3 --arg d "$(date -u +%FT%TZ)" \
  '{version:$v, releasedAt:$d, artifacts:[inputs]}' \
  <(sha256sum dist/*.tar.gz dist/*.zip) \
  > dist/manifest.json

# Produce a compact JWS
# The CLI builds the protected header, base64url-encodes manifest.json as the payload, and requests a signature — a maintainer approves in-browser.
revaulter-cli sign \
  --server https://revaulter.example.com \
  --request-key AbCdEf0123456789GhIj \
  --key-label release-signing \
  --algorithm ES256 \
  --input dist/manifest.json \
  --format jws \
  --jws-header '{"kid":"<KEY_ID>","typ":"JWT"}' \
  --output dist/manifest.jws \
  --note "release v1.2.3"

Publish dist/manifest.jws alongside the artifacts. It’s self-contained: the payload (manifest) and signature are both in the JWS.

Verifying a release#

Any JOSE library works. First, fetch the published JWK:

KEY_ID="<key-id-from-publisher>"
curl -fsSL "https://revaulter.example.com/v2/signing-keys/$KEY_ID.jwk" \
  | jq '.jwk' > release-signing.jwk

Then verify dist/manifest.jws with the verifier of your choice:

Python (jwcrypto)
pip install jwcrypto

python3 - <<'PY'
import json
from jwcrypto import jwk, jws
key = jwk.JWK(**json.load(open("release-signing.jwk")))
tok = jws.JWS()
tok.deserialize(open("dist/manifest.jws").read().strip())
tok.verify(key)
print(json.loads(tok.payload))
PY
Node.js (jose)
npm install jose
import { readFile } from 'node:fs/promises'
import { importJWK, compactVerify } from 'jose'

const jwk = JSON.parse(await readFile('release-signing.jwk', 'utf8'))
const key = await importJWK(jwk, 'ES256')
const token = (await readFile('dist/manifest.jws', 'utf8')).trim()
const { payload, protectedHeader } = await compactVerify(token, key)
console.log(protectedHeader)
console.log(JSON.parse(new TextDecoder().decode(payload)))
jose CLI
jose jws ver -i dist/manifest.jws -k release-signing.jwk -O -

Once the JWS verifies, you have an authenticated manifest; check the artifact hashes against it with sha256sum -c.

Why this pattern works#

  • The signing key never leaves the browser: CI cannot sign on its own: each release requires a live passkey approval from a maintainer.
  • Small, fixed-size payloads: only the 32-byte SHA-256 digest of the JWS signing input is transmitted end-to-end, regardless of artifact size.
  • Stable key ID: the signing key is derived deterministically from the maintainer’s primary key, so the published JWK (and its ID) survives passkey rotations and password changes — downstream verifiers can pin it.
  • Standard verification: ES256 is a JOSE algorithm, so any JWT/JOSE library verifies the output out of the box.
Edit this page on GitHub