remaestro-extensions

Signing

Two keys, doing two different jobs, and the difference between them is the whole trust design.

  Held by Signs What it proves
Publisher key you, the publisher your archives that this update came from whoever published version one
Index key this project, in CI the generated index that the list of names, versions, URLs and digests is the one we published

We never sign your plugin. A signature from this project over a third party’s binary is read as a warranty — that is the social meaning of code signing — and under a model where a plugin runs with the hub’s own privileges there is no warranty to give. So the split above is not an accident of tooling. It is the point.

Your publisher key

ECDSA P-256, SHA-256, DER — the same shape the hub already verifies release manifests with.

Make one. It never leaves your keeping, and it never goes in a repository, a CI secret you share, or this registry:

openssl ecparam -name prime256v1 -genkey -noout -out remaestro-publisher.pem
chmod 600 remaestro-publisher.pem

The public half, which is what you put in publishers/<you>.json:

openssl ec -in remaestro-publisher.pem -pubout -outform DER | base64

Sign each archive — the archive bytes themselves, not the digest, not the manifest:

openssl dgst -sha256 -sign remaestro-publisher.pem -out lamp.sig.der lamp-1.1.0-linux-arm64.tar.gz
base64 < lamp.sig.der          # this goes in the manifest as "signature"

Check it before you open the pull request. CI will do exactly this, and finding out here costs you a minute rather than a review round trip:

openssl ec -in remaestro-publisher.pem -pubout -out pub.pem
openssl dgst -sha256 -verify pub.pem -signature lamp.sig.der lamp-1.1.0-linux-arm64.tar.gz

Pinning, and what it is actually worth

Your key is pinned on your first publication. Every later version of every plugin you publish must be signed by a key already in your publisher record, or CI refuses the submission.

What that buys, exactly: a plugin’s updates come from whoever published its first version. It is a narrow guarantee, and it is the most valuable one available here, because the realistic attack on a marketplace is not a forged listing — it is a popular plugin’s account being taken over and pushing a malicious update to everyone who already trusted it.

What it does not buy: anything at all about the first install. That decision is the user’s, made on the publisher name, the source link, and the sentence at the top of the README.

Rotation

Add a new key entry; never edit or delete an old one. Retire the old one by setting its status to revoked, which keeps the history readable — a hub that meets an archive signed by a revoked key should be able to say which key it was.

A rotation needs a maintainer’s key-rotation label before CI will accept it, and that is deliberate: an account takeover and a legitimate new key are the same diff. The label is a person asserting they know which one this is. Expect to be asked, out of band, to confirm it is you.

Lost your key with no rotation possible? Then you cannot publish an update to that plugin, and the answer is a new plugin id under the same publisher. That is unpleasant and it is the correct behaviour: the alternative is a registry where losing a key and stealing an account look the same.

The index key

Held by this project, used only by .github/workflows/publish-index.yml.

The standing rule for this project is that no signing key lives in CI, for any channel — the keys that sign a hub, an OS image or a driver are used by a person, on their own machine, twelve or so times a year. The index key is an explicit, argued exception, and the argument is worth keeping where it can be checked:

How it is provisioned, and the rules around it. There is nothing here to copy — no key material is in this repository, in any script, or on any developer machine as a matter of routine.

Verifying the feed yourself

Once the feed is live — the domain below is settled but not yet provisioned, see index-format.md:

curl -sO https://extensions.remaestro.app/plugins/com.acme.lamp.json
curl -sO https://extensions.remaestro.app/plugins/com.acme.lamp.json.sig

{ echo "-----BEGIN PUBLIC KEY-----"; fold -w64 keys/index-1.pub; echo; echo "-----END PUBLIC KEY-----"; } > index.pem
base64 -d < com.acme.lamp.json.sig > sig.der
openssl dgst -sha256 -verify index.pem -signature sig.der com.acme.lamp.json

The transparency log nobody had to build

Every (plugin, version, digest, publisher key) this registry has ever offered is in this repository’s own git history, publicly, with the pull request that introduced it. That means a bad artifact served to one hub stays detectable afterwards. It is a property of keeping the registry in git rather than a feature anyone wrote, and it is worth stating as one.