MobDev.Plugin.Sign (mob_dev v0.5.14)

Copy Markdown View Source

Author-side signing workflow for mob plugins.

Produces priv/mob_plugin.sig for a plugin directory by:

  1. Loading the manifest (priv/mob_plugin.exs).
  2. Computing SHA-256 hashes for every file the manifest references (Swift sources, Android bridge/JNI sources, NIF native_dir contents).
  3. Building the canonical payload (manifest + sorted file hashes).
  4. Signing the canonical encoding of the payload via Crypto.sign/2.
  5. Writing a binary priv/mob_plugin.sig containing 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

file_hash()

@type file_hash() :: binary()

SHA-256 digest of a single file (raw 32-byte binary).

file_hashes()

@type file_hashes() :: [{rel_path(), file_hash()}]

Sorted list of {relative_path, sha256} tuples.

rel_path()

@type rel_path() :: String.t()

Relative path inside the plugin directory.

Functions

build_payload(manifest, file_hashes)

@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.

compute_file_hashes(plugin_dir, manifest)

@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_kt and manifest.android.jni_source — single paths each.
  • manifest.nifs[].native_dir — recursive over .c, .h, .cpp, .zig files 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.

envelope_version()

@spec envelope_version() :: integer()

Current signing envelope version.

manifest_path(plugin_dir)

@spec manifest_path(Path.t()) :: Path.t()

Relative path inside a plugin dir where the manifest lives.

sign_plugin(plugin_dir, priv_key)

@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.

signature_path(plugin_dir)

@spec signature_path(Path.t()) :: Path.t()

Relative path inside a plugin dir where the signature lives.