Signing packages
keygen creates a creator key pair, sign embeds a signature in a package, and verify checks one.
Examples
Section titled “Examples”macrodeck-plugin verify ./downloads/com.example.hello-deck-1.0.0-linux-x64.macroDeckPlugininvalid: signature-missing: The manifest carries no 'signature' object.Revocation was not checked; this is cryptographic verification only.Check a downloaded artifact offline. This one is unsigned, so it fails with exit 1.
macrodeck-plugin verify ./artifacts/com.example.hello-deck-1.0.0-linux-x64.macroDeckPlugin --output json{ "valid": false, "format": null, "certificateId": null, "issuerCertificateId": null, "rootAnchored": true, "revocationChecked": false, "problems": [ { "code": "signature-missing", "message": "The manifest carries no 'signature' object." } ]}The same verdict as a document, for a pipeline that needs more than the exit code.
macrodeck-plugin keygen --output ~/.macrodeck/keysPublic key: ~/.macrodeck/keys/macrodeck-creator.publicPrivate key: ~/.macrodeck/keys/macrodeck-creator.privatePublic key (base64): 3ay/DcXb90fbA+uH8jh5iMvx6ORF3AfUfOyA/0CPRPY=Submit the public key above to the Creator Portal for certificate issuance - this command does not issue certificates.Create a key pair outside your repository. Read Private-key handling first.
macrodeck-plugin sign ./artifacts/com.example.hello-deck-1.0.0-linux-x64.macroDeckPlugin \ --output ./signed/com.example.hello-deck-1.0.0-linux-x64.macroDeckPlugin \ --certificate certificate.json \ --certificate-signature certificate.sig \ --private-key ~/.macrodeck/keys/macrodeck-creator.privateSign a package for distribution outside the Store, with a certificate the Creator Portal issued. If the
certificate names an issuer, add --issuer-certificate issuer.json --issuer-certificate-signature issuer.sig.
keygen
Section titled “keygen”Generates a creator Ed25519 key pair. It never issues a certificate; only the Creator Portal can turn a public key into a certificate signed by the Macro Deck root.
| Option | Default | Description |
|---|---|---|
--output <dir> |
. |
Directory to write the key pair into. |
--key-name <name> |
macrodeck-creator |
Base file name for the key pair. |
- Writes
<key-name>.publicand<key-name>.private. - Never overwrites: an existing file at either path fails with
output-existsbefore anything is written. root,macrodeck-root,registryandmacrodeck-registryfail withreserved-key-name:keygenonly ever makes creator keys.
$ macrodeck-plugin keygen --key-name rooterror reserved-key-name: 'root' is reserved for Macro Deck's own trust anchors and cannot be used as a creator key name.Exit codes: 0 on success, 2 for output-exists or reserved-key-name.
Signs a .macroDeckPlugin, .macroDeckIconPack, .macroDeckProfile, .macroDeckFolder or
.macroDeckWidget package. There is no detached signature: the signature goes into the artifact’s own
manifest, and the certificate into the archive root as certificate.json and certificate.sig, plus
issuer.json and issuer.sig when an issuer certificate signed it, so the signed artifact verifies on its
own.
| Option | Default | Description |
|---|---|---|
<package> (argument) |
- | The package to sign. |
--output <path> |
- | Required. Where to write the signed artifact; never overwritten. |
--certificate <path> |
- | Required. The signing certificate (certificate.json). |
--certificate-signature <path> |
- | Required. The root’s signature over the certificate (certificate.sig). |
--private-key <path> |
- | Required. The base64-encoded private key keygen wrote. |
--issuer-certificate <path> |
- | The issuer certificate (issuer.json) that signed the certificate. Required exactly when the certificate names an issuer; give it together with --issuer-certificate-signature. |
--issuer-certificate-signature <path> |
- | The root’s signature over the issuer certificate (issuer.sig). |
--root-public <path> |
pinned Macro Deck root | Verify the certificate against this root instead; testing only. |
sign runs these steps in order and stops at the first failure:
- Declared-file validation, as
validate --artifactdoes it: every file infiles[]checked by SHA-256 and size. - Certificate chain verification against the pinned root (or
--root-public), through the issuer certificate when the certificate names one. The certificate needs exclusivepackagekey usage, and it and its issuer must be valid now. See the certificate chain. - The certificate’s public key must match
--private-key. - The format’s canonical digest (
macro-deck-plugin/1,macro-deck-iconpack/1ormacro-deck-portable/1), then the signature. - Write
--output, then re-verify it exactly asverifywould. An artifact that does not verify is a failure.
An already-signed package fails with already-signed; sign never replaces a signature.
$ macrodeck-plugin sign app.macroDeckPlugin --output signed.macroDeckPlugin --certificate certificate.json ...error certificate-untrusted: The certificate signature does not verify against the root key.| Exit code | Failures |
|---|---|
| 1 | Certificate malformed, untrusted, wrong-purpose, not yet valid or expired; issuer certificate missing (certificate-issuer-missing), not the one the certificate names (certificate-issuer-mismatch), or ending before the certificate (certificate-outlives-issuer). Private key malformed or not matching the certificate. Package already signed; manifest missing, malformed or too large (8 MiB, or 32 MiB for an icon pack’s pack.json); an icon pack with more than 30,000 entries once signed (too-many-entries); declared files not matching (digest or size mismatch, undeclared file, missing declared file, unsafe entry). |
| 2 | Unsupported package format, --output already exists, or only one of the two --issuer-certificate options given (issuer-options-incomplete). |
| 3 | The package, certificate, certificate signature, issuer certificate, issuer signature, private key or --root-public file could not be read. |
| 4 | Ctrl-C. |
| 70 | Writing the output failed, or the written artifact failed its own re-verification. |
verify
Section titled “verify”Checks a signed package’s embedded signature and certificate against the pinned Macro Deck root (or
--root-public), with the same
MacroDeck.Signing
checks sign self-verifies with:
- the certificate chain and its
packagekey usage, through the archive’sissuer.jsonandissuer.sigwhen the certificate names an issuer; - the certificate’s validity, and its issuer’s, at the signature’s
signedAt, not at verify time - a package signed while its certificate was valid still verifies after the certificate expires; - the format’s canonical digest, and every declared file’s SHA-256 and size.
| Option | Default | Description |
|---|---|---|
<package> (argument) |
- | The package to verify. |
--root-public <path> |
pinned Macro Deck root | Verify against this root public key instead; testing only. |
--output <text|json> |
text |
How to render the result. |
- Text: one line with the format, the signing certificate id and, when there is one, the issuer’s
certificate id, or
invalid: <code>: <message>. - JSON:
{ valid, format, certificateId, issuerCertificateId, rootAnchored, revocationChecked, problems[] }.issuerCertificateIdisnullfor a certificate the root signed directly; older CLI versions omit it. - Every run ends by stating that revocation was not checked;
revocationCheckedis alwaysfalse. This is local cryptographic verification only. - With a
--root-publicthat is not the pinned root,rootAnchoredisfalseand awarning non-production-rootline prints, in both formats.
| Exit code | When |
|---|---|
| 0 | Valid, and anchored to the pinned root (or to --root-public). |
| 1 | Unsigned, tampered or invalid: signature missing, malformed, algorithm-mismatched or not verifying; certificate malformed, untrusted, wrong-purpose or not valid at signedAt; issuer certificate missing, mismatched or ending before the certificate; declared files not matching; manifest malformed. |
| 2 | The extension is not a signable package format. |
| 3 | The package or the --root-public file could not be read. |
| 4 | Ctrl-C. |
verify writes nothing, so it never returns 70.
Private-key handling
Section titled “Private-key handling”This applies only if you run keygen and sign. Publishing to the Store never puts a private key in
your hands or your CI.
The private key is the only thing that can produce a valid signature under your certificate; sign asks
for nothing else to prove who you are.
echo '*.private' >> .gitignore # before running keygen, not after- Never commit it. Ignore
*.private(or your key name) before you runkeygen. - On Unix,
keygenwrites it with mode0600(owner read/write only). Windows has no in-band equivalent:keygenprintsrestrictive-file-mode-unavailable, and restricting the file with an NTFS ACL is up to you. - A leaked key taints every artifact ever signed with it, and the certificate must be revoked. Report
the leak so the certificate can be revoked, run
keygenfor a fresh pair, and get a new certificate for the new public key. - The CLI cannot issue, re-issue or revoke certificates; that lives in the Creator Portal.
See also
Section titled “See also”- Publishing to the Store - how Store artifacts are signed.
- CI and automation -
verifyas a pipeline gate. - Security model - the trust model
verifychecks against. - Certificate schema (v1, v2) and package signature schema.