Author-side signing workflow for mob plugins.
Produces priv/mob_plugin.sig for a plugin directory by:
- Loading the manifest (
priv/mob_plugin.exs). - Computing SHA-256 hashes for every file the manifest references (Swift sources, Android bridge/JNI sources, NIF native_dir contents, and the manifest bytes themselves).
- Building the canonical payload (sorted file hashes + envelope version).
- Signing the canonical encoding of the payload via
Crypto.sign/2. - Writing a binary
priv/mob_plugin.sigcontaining the signature and the signed file_hashes list, so verifiers can check integrity without needing toCode.eval_filethe manifest first (see MOB-74).
Pure helpers are exposed for tests: compute_file_hashes/2 and
build_payload/1 are deterministic given their inputs.
Envelope versions
- v1 (deprecated, MOB-74) — payload was
%{manifest: <map>, file_hashes: [...]}and the envelope on disk carried only the signature. Verifiers needed the eval'd manifest map to rebuild the payload, so the eval had to run before verification could — letting a maliciouspriv/mob_plugin.exsexecute arbitrary code at build time. Refused byMobDev.Plugin.Verifysince mob_dev 0.7.2, except for Hex plugins accepted under the MOB-287 transition rule (MobDev.Plugin.V1Transition). Never produced any more. - v2 (current) — payload is
%{file_hashes: [...], envelope_version: 2};file_hashesincludespriv/mob_plugin.exs; envelope on disk embedsfile_hashesalongside the signature. Verifiers can check every on-disk file against the signed hashes without touching the manifest map, so verification is safe to run before eval.
Summary
Types
SHA-256 digest of a single file (raw 32-byte binary).
Sorted list of {relative_path, sha256} tuples.
Relative path inside the plugin directory.
Functions
Builds the canonical payload term that gets signed.
Returns the relative-path-sorted list of {relative_path, sha256}
tuples for every file the manifest references.
Current signing envelope version.
Relative path inside a plugin dir where the manifest lives.
Signs plugin_dir and writes priv/mob_plugin.sig.
Relative path inside a plugin dir where the signature lives.
Types
Functions
@spec build_payload(file_hashes()) :: map()
Builds the canonical payload term that gets signed.
Shape:
%{
file_hashes: [{rel_path, sha256}, ...],
envelope_version: 2
}Authoritative for what's inside the signature — any new field added here needs both author and host updates.
Note that the payload no longer includes the manifest term itself
(see MOB-74). The manifest is one of the files hashed in
file_hashes, so its bytes are covered — and dropping the map from
the payload lets Verify recompute the payload without eval'ing
the manifest first.
@spec compute_file_hashes(Path.t(), map() | nil) :: file_hashes()
Returns the relative-path-sorted list of {relative_path, sha256}
tuples for every file the manifest references.
Pure given the plugin dir + manifest. The set covers:
manifest.ios.swift_files— single files (list of paths).manifest.android.bridge_ktandmanifest.android.jni_source— single paths each.manifest.android.res_files— the resource files copied verbatim into the appres/tree (list of paths).manifest.nifs[].native_dir— recursive over.c,.h,.cpp,.zigfiles inside. This is the only case where a directory is expanded.
Other manifest fields are either name-only (component atoms,
swift_struct) or pure data (plist keys, permission strings,
framework names) and are covered by the manifest term itself being
part of the signed payload.
Missing files are skipped silently — Validator.validate_plugin/3
is responsible for refusing to publish a plugin with missing
declared paths, so the signing surface assumes paths that exist.
@spec envelope_version() :: integer()
Current signing envelope version.
Relative path inside a plugin dir where the manifest lives.
@spec sign_plugin(Path.t(), MobDev.Plugin.Crypto.priv_key()) :: :ok | {:error, term()}
Signs plugin_dir and writes priv/mob_plugin.sig.
Orchestrates the full author workflow: loads the manifest (to know
which files it references), computes file hashes (including the
manifest bytes themselves), builds the v2 payload, signs it, wraps
the signature and the file_hashes list in the envelope binary,
and writes the file. Returns :ok on success or {:error, reason}
if the manifest is missing/invalid.
The envelope carries file_hashes on disk so Verify.verify_plugin/1
can check tampering without needing to Code.eval_file the manifest
first — see MOB-74.
Relative path inside a plugin dir where the signature lives.