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).
- Building the canonical payload (manifest + sorted file hashes).
- Signing the canonical encoding of the payload via
Crypto.sign/2. - Writing a binary
priv/mob_plugin.sigcontaining the signature.
Pure helpers are exposed for tests: compute_file_hashes/2 and
build_payload/2 are deterministic given their inputs.
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(map() | nil, file_hashes()) :: map()
Builds the canonical payload term that gets signed.
Shape:
%{
manifest: <the loaded mob_plugin manifest>,
file_hashes: [{rel_path, sha256}, ...],
envelope_version: 1
}Authoritative for what's inside the signature — any new field added here needs both author and host updates.
@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.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, computes
file hashes, builds the payload, signs it, wraps the signature in the
envelope binary, and writes the file. Returns :ok on success or
{:error, reason} if the manifest is missing/invalid.
Relative path inside a plugin dir where the signature lives.