From bd5704eb5b24ba011e6fe5c723c6ca3d313e9c0c Mon Sep 17 00:00:00 2001 From: eKisNonos Date: Sun, 20 Sep 2026 12:00:00 +0200 Subject: [PATCH 1/4] market: one release codec, and check a release before offering it Release encode, decode and signing move into marketplace_abi, so the tool that writes the index and the capsule that reads it share a codec instead of having one each. install_ready checks arch and readiness against the running image rather than the index, and adds a seventh gate for whether a release carries a zk trailer for its own measurement. The other six are signatures over the artifact. The catalogue generator reads what is on disk and writes JSON; the CLI encodes the binary the market ingests. Signing is a separate step with the operator seed, which is not in any build rule. --- .keys/marketplace_operator_ed25519.pub | 2 + .../market_capsule/client/install_ready.rs | 12 +- tools/nonos-market-catalogue | 352 ++++++++++++++++++ tools/nonos-market-sign-releases | 84 +++++ userland/capsule_market/Capsule.mk | 7 + userland/capsule_market/Cargo.toml | 19 +- userland/capsule_market/src/boot_index.rs | 54 +++ .../src/bootstrap_trust/keys.rs | 8 +- .../capsule_market/src/install_ready/arch.rs | 8 + .../src/install_ready/checks.rs | 19 +- userland/capsule_market/src/main.rs | 5 + .../handlers/install_ready/constants.rs | 2 +- .../handlers/install_ready/find_release.rs | 12 +- .../server/handlers/install_ready/handle.rs | 1 + .../marketplace_abi/src/codec/decode_index.rs | 4 +- .../src/codec/decode_release.rs | 2 + .../src/codec/encode_release.rs | 1 + .../src/codec/release_signing.rs | 8 +- .../marketplace_abi/src/types/readiness.rs | 19 +- userland/marketplace_abi/src/types/release.rs | 12 +- userland/toolkit/src/icons/all.rs | 3 +- userland/toolkit/src/icons/id.rs | 1 + userland/toolkit/src/icons/name.rs | 1 + userland/toolkit/src/icons/table.rs | 7 +- userland/toolkit/tests/host/icon_table.rs | 7 +- 25 files changed, 591 insertions(+), 59 deletions(-) create mode 100644 .keys/marketplace_operator_ed25519.pub create mode 100755 tools/nonos-market-catalogue create mode 100755 tools/nonos-market-sign-releases create mode 100644 userland/capsule_market/src/boot_index.rs diff --git a/.keys/marketplace_operator_ed25519.pub b/.keys/marketplace_operator_ed25519.pub new file mode 100644 index 0000000000..f9cd71ad83 --- /dev/null +++ b/.keys/marketplace_operator_ed25519.pub @@ -0,0 +1,2 @@ +§É-²M™çºî‹E mÃSÌÔ&"Á© +Rµ2}²æÑx \ No newline at end of file diff --git a/src/security/market_capsule/client/install_ready.rs b/src/security/market_capsule/client/install_ready.rs index 208bbfbf66..c949f1de59 100644 --- a/src/security/market_capsule/client/install_ready.rs +++ b/src/security/market_capsule/client/install_ready.rs @@ -14,12 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! `OP_INSTALL_READY`. The userland capsule evaluates a hard AND -//! of nine install gates and returns the verdict as six bytes: -//! one for the AND-result followed by the per-check bits. The -//! kernel surfaces the result as a structured value so a caller -//! can short-circuit on the AND-result while still being able to -//! tell which gate refused. +//! `OP_INSTALL_READY`. use alloc::vec::Vec; @@ -30,7 +25,7 @@ use super::seq::next_request_id; use super::status_map::lift; use super::transport::round_trip; -const READINESS_LEN: usize = 6; +const READINESS_LEN: usize = 7; #[derive(Debug, Clone, Copy)] pub struct InstallReadiness { @@ -40,6 +35,8 @@ pub struct InstallReadiness { pub publisher_signature_present: bool, pub validation_passed: bool, pub arch_match: bool, + /// The release offers a zk trailer for its own measurement. + pub attestation_present: bool, } pub fn install_ready(listing_id: &str, release_id: &str) -> Result { @@ -66,5 +63,6 @@ pub fn install_ready(listing_id: &str, release_id: &str) -> Result. +"""Build the marketplace index JSON from what actually exists on disk. + +Three sources, one catalogue: + + nonos capsules this tree builds and signs, read out of the trust + directory so a listing exists only for something that has a + manifest and a certificate + + linux distribution packages, fetched and hashed here. A listing + asserts "these bytes, this hash", and a hash nobody computed + is not an assertion, so a package that has not been fetched + is not listed + + community submissions under nonos-data/marketplace/community, each a + JSON file naming a publisher key and a release the operator + has already validated + +The output is the plain JSON the `marketplace-index` CLI encodes and +signs. Nothing here signs anything: the operator key never touches a +generator. +""" + +import argparse +import json +import subprocess +import sys +import tarfile +import time +import urllib.request +from pathlib import Path + +MIRROR = "https://dl-cdn.alpinelinux.org/alpine" +BRANCHES = ("main", "community") +# 2: the release carries the hash of its zk trailer, and the publisher +# signature covers it. +SCHEMA = 2 + +# Capsules that are not applications. A driver or a transport is part of +# the system, cannot be installed or removed by a user, and listing one +# would offer an install that cannot happen. +NOT_APPS = ( + "driver_", + "net_", + "input_", + "proof_", + "std_proof", + "egui_proof", + "tokio-smoke", + "hello", + "gui_demo", + "boot_splash", + "compositor", + "wm", + "vfs", + "ramfs", + "keyring", + "policy", + "entropy", + "market", + "login", + "setup_wizard", + "toolkit", + "wallpaper", + "wallpaper_catalog", + "image_codec", + "audio_server", +) + +FREE = {"kind": "free", "amount_atomic": "0", "period_seconds": 0} +NOX = {"symbol": "NOX", "decimals": 18, "chain_id": 1, "contract_address": ""} + + +def blake3(data: bytes) -> str: + """BLAKE3-256, the hash every other artifact in this tree is named by. + + b3sum is what the signing tools use, so the digest in a listing is + the same one a person gets checking the artifact by hand. + """ + try: + done = subprocess.run( + ["b3sum", "--no-names", "--raw"], + input=data, stdout=subprocess.PIPE, check=True, + ) + except FileNotFoundError: + sys.exit("b3sum not found: install it, or the digests would be guesses") + return done.stdout[:32].hex() + + +def validation(note: str, validator: str, when_ms: int) -> dict: + return { + "status": "validated", + "note": note, + "validator_id": validator, + "validated_at_ms": when_ms, + } + + +def release(rid, manifest, package, url, arches, caps, note, validator, + when_ms, trailer=""): + return { + "release_id": rid, + "manifest_hash": manifest, + "package_hash": package, + "package_url": url, + "publisher_signature": "", + "supported_arches": arches, + "kernel_abi_min": 1, + "required_capabilities": caps, + "zk_trailer_hash": trailer, + "validation": validation(note, validator, when_ms), + } + + +def entry(listing, capsule_id, name, publisher, pubkey, text, releases): + return { + "listing_id": listing, + "capsule_id": capsule_id, + "name": name, + "publisher_name": publisher, + "publisher_pubkey": pubkey, + "publisher_eth_address": "00" * 20, + "description": text, + "price": FREE, + "token": NOX, + "releases": releases, + } + + +def is_app(slug: str) -> bool: + return not any(slug == n or slug.startswith(n) for n in NOT_APPS) + + +def nonos_entries(trust: Path, pubkey: str, when_ms: int) -> list: + """One listing per signed capsule that is an application.""" + out = [] + for manifest in sorted(trust.glob("*.manifest.bin")): + slug = manifest.name[: -len(".manifest.bin")] + if not is_app(slug): + continue + cert = trust / f"{slug}.nonos_id_cert.bin" + trailer = trust / f"{slug}.zk_trailer.bin" + if not cert.exists() or not trailer.exists(): + # No proof, no listing. An entry whose install is going to + # be refused at the spawn gate is worse than no entry: the + # refusal arrives after the download and reads like a bug. + print(f" skip {slug}: no trailer", file=sys.stderr) + continue + mhash = blake3(manifest.read_bytes()) + thash = blake3(trailer.read_bytes()) + out.append( + entry( + f"nonos.app.{slug}", + blake3(cert.read_bytes()), + slug.replace("_", " "), + "NONOS", + pubkey, + f"NONOS capsule {slug}, signed and attested in this image.", + [ + release( + f"{slug}@builtin", + mhash, + mhash, + "", + ["x86_64-nonos"], + [], + "built and signed by this tree", + "nonos.build", + when_ms, + thash, + ) + ], + ) + ) + return out + + +def apkindex(cache: Path, release_name: str, arch: str, branch: str) -> dict: + """name -> record, from one branch's APKINDEX.""" + base = f"{MIRROR}/{release_name}/{branch}/{arch}" + tgz = cache / f"{branch}-APKINDEX.tar.gz" + if not tgz.exists(): + tgz.parent.mkdir(parents=True, exist_ok=True) + with urllib.request.urlopen(f"{base}/APKINDEX.tar.gz", timeout=120) as r: + tgz.write_bytes(r.read()) + with tarfile.open(tgz) as t: + raw = t.extractfile("APKINDEX").read().decode(errors="replace") + out, rec = {}, {} + for line in raw.split("\n"): + if not line: + if rec.get("P"): + rec["base"] = base + out[rec["P"]] = rec + rec = {} + continue + if len(line) > 2 and line[1] == ":": + rec[line[0]] = line[2:] + return out + + +def linux_trailer(cache: Path, apk: str) -> str: + """The trailer an operator minted for this package, if they have. + + A Linux package carries no NONOS proof of its own, so somebody has + to enrol its measurement before the machine will run it. Until that + has happened there is nothing truthful to put in the field, and the + listing is held back rather than shipped as ready. + """ + at = cache / f"{apk}.zk_trailer.bin" + return blake3(at.read_bytes()) if at.exists() else "" + + +def linux_entries(cache, names, release_name, arch, pubkey, when_ms) -> list: + """One listing per package, fetched so its hash is a measured fact.""" + table = {} + for branch in BRANCHES: + table.update(apkindex(cache, release_name, arch, branch)) + out = [] + for name in names: + rec = table.get(name) + if rec is None: + print(f" skip {name}: not in the index", file=sys.stderr) + continue + apk = f"{rec['P']}-{rec['V']}.apk" + url = f"{rec['base']}/{apk}" + blob = cache / apk + if not blob.exists(): + try: + with urllib.request.urlopen(url, timeout=180) as r: + blob.write_bytes(r.read()) + except OSError as e: + print(f" skip {name}: {e}", file=sys.stderr) + continue + raw = blob.read_bytes() + digest = blake3(raw) + out.append( + entry( + f"linux.{rec['P']}", + digest, + rec["P"], + f"Alpine {release_name}", + pubkey, + rec.get("T", "").strip() or f"Linux package {rec['P']}", + [ + release( + f"{rec['P']}@{rec['V']}", + digest, + digest, + url, + ["x86_64-linux"], + ["ForeignExec"], + f"fetched and hashed at {len(raw)} bytes", + "nonos.operator.linux", + when_ms, + linux_trailer(cache, apk), + ) + ], + ) + ) + return out + + +def community_entries(where: Path) -> list: + """Submissions, passed through as the operator validated them.""" + out = [] + for path in sorted(where.glob("*.json")): + item = json.loads(path.read_text()) + if not item.get("listing_id", "").startswith("community."): + sys.exit(f"{path}: listing_id must start with 'community.'") + out.append(item) + return out + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--out", type=Path, required=True) + ap.add_argument("--trust", type=Path, default=Path("nonos-data/trust/capsules")) + ap.add_argument("--community", type=Path, + default=Path("nonos-data/marketplace/community")) + ap.add_argument("--cache", type=Path, default=Path("target/market-cache")) + ap.add_argument("--operator-pubkey", required=True, + help="hex Ed25519 key the index will be signed under") + ap.add_argument("--alpine-release", default="v3.21") + ap.add_argument("--alpine-arch", default="x86_64") + ap.add_argument("--linux-package", action="append", default=[], + help="repeatable; a package to fetch, hash and list") + ap.add_argument("--linux-list", type=Path, + help="file of package names, one per line") + ap.add_argument("--serial", type=int, required=True) + ap.add_argument("--no-nonos", action="store_true") + args = ap.parse_args() + + when_ms = int(time.time() * 1000) + key = args.operator_pubkey.removeprefix("0x").lower() + if len(key) != 64: + sys.exit("--operator-pubkey must be 32 hex bytes") + + entries = [] + if not args.no_nonos and args.trust.is_dir(): + found = nonos_entries(args.trust, key, when_ms) + print(f"nonos: {len(found)} capsules", file=sys.stderr) + entries += found + + names = list(args.linux_package) + if args.linux_list and args.linux_list.exists(): + names += [ + line.split("#", 1)[0].strip() + for line in args.linux_list.read_text().splitlines() + if line.split("#", 1)[0].strip() + ] + if names: + args.cache.mkdir(parents=True, exist_ok=True) + found = linux_entries(args.cache, names, args.alpine_release, + args.alpine_arch, key, when_ms) + print(f"linux: {len(found)} of {len(names)} packages", file=sys.stderr) + entries += found + + if args.community.is_dir(): + found = community_entries(args.community) + print(f"community: {len(found)} submissions", file=sys.stderr) + entries += found + + index = { + "schema_version": SCHEMA, + "operator_id": "nonos.marketplace.v1", + "published_at_ms": when_ms, + "serial": args.serial, + "entries": entries, + } + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(json.dumps(index, indent=2) + "\n") + print(f"{args.out}: {len(entries)} listings, serial {args.serial}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/nonos-market-sign-releases b/tools/nonos-market-sign-releases new file mode 100755 index 0000000000..6d532a9ccf --- /dev/null +++ b/tools/nonos-market-sign-releases @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +# NONOS Operating System +# Copyright (C) 2026 NONOS Contributors +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +"""Attach a publisher signature to every release in the index. + +The generator cannot do this: it never touches a key. The CLI signs one +release per invocation, deliberately, so the loop lives here rather than +inside a command that would then be holding a key across a whole +catalogue. + +Every release this signs is one whose publisher is the operator, which +is true for the capsules this tree builds and for packages the operator +fetched and hashed itself. A third-party submission arrives already +signed by its own publisher and is skipped: re-signing it here would +replace the submitter's authority with the operator's, quietly. +""" + +import argparse +import json +import subprocess +import sys +from pathlib import Path + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--cli", type=Path, required=True) + ap.add_argument("--index", type=Path, required=True) + ap.add_argument("--key-file", type=Path, required=True) + ap.add_argument("--operator-pubkey", required=True) + args = ap.parse_args() + + key = args.operator_pubkey.removeprefix("0x").lower() + doc = json.loads(args.index.read_text()) + todo = [] + for entry in doc["entries"]: + if entry["publisher_pubkey"].lower() != key: + continue + for rel in entry["releases"]: + if not rel.get("publisher_signature"): + todo.append((entry["listing_id"], rel["release_id"])) + + signed = 0 + for listing_id, release_id in todo: + done = subprocess.run( + [ + str(args.cli), "sign-release", + "--in", str(args.index), + "--listing-id", listing_id, + "--release-id", release_id, + "--key-file", str(args.key_file), + "--out", str(args.index), + ], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + ) + if done.returncode != 0: + print(f" {listing_id}: {done.stderr.decode().strip()}", file=sys.stderr) + continue + signed += 1 + + doc = json.loads(args.index.read_text()) + total = sum(len(e["releases"]) for e in doc["entries"]) + have = sum( + 1 for e in doc["entries"] for r in e["releases"] if r.get("publisher_signature") + ) + print(f"signed {signed}; {have} of {total} releases now carry a signature") + return 0 if have == total else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/userland/capsule_market/Capsule.mk b/userland/capsule_market/Capsule.mk index 3782855c50..5545c423a3 100644 --- a/userland/capsule_market/Capsule.mk +++ b/userland/capsule_market/Capsule.mk @@ -17,4 +17,11 @@ CAPSULE_REPLY_ENDPOINT := reply:4107:endpoint.4294967303 CAPSULE_REQUIRED_CAPS := 0x39 CAPSULE_KERNEL_MIRROR := src/security/market_capsule +# The capsule embeds the signed catalogue, so a newer index has to +# rebuild it. Cargo tracks the include_bytes! path, but the make rule +# lists only sources, and without this line a freshly signed catalogue +# was silently left out of the image: the build succeeded, the boot +# succeeded, and the machine served the previous one. +CAPSULE_EXTRA_DEPS := nonos-data/marketplace/index.bin + include nonos-mk/capsule.mk diff --git a/userland/capsule_market/Cargo.toml b/userland/capsule_market/Cargo.toml index 94640ad717..78ac05118d 100644 --- a/userland/capsule_market/Cargo.toml +++ b/userland/capsule_market/Cargo.toml @@ -22,16 +22,17 @@ name = "market" path = "src/main.rs" [dependencies] -nonos_ed25519 = { path = "../nonos_ed25519" } nonos_libc = { package = "nonos_userland_libc", path = "../libc" } nonos_marketplace_abi = { path = "../marketplace_abi" } +nonos_app_skeleton = { path = "../app_skeleton" } [features] default = [] -# Replaces the default `CryptoVerifier`, which verifies in this -# process with nonos_ed25519, with `RejectAll`. Every signed index -# ends up refused, which keeps install readiness honest in a build -# that is meant to accept nothing. +# Replaces the default `CryptoVerifier` (which routes to +# capsule_crypto through the kernel's `CryptoEd25519Verify` +# syscall) with `RejectAll`. Useful when the kernel image does +# not embed capsule_crypto; every signed index ends up refused, +# which keeps install readiness honest in the offline build. offline-verify = [] [profile.release] panic = "abort" @@ -39,11 +40,3 @@ opt-level = 2 lto = false debug = false strip = true - -# Matches every sibling capsule. Without it `cargo check` on the host fails -# with "unwinding panics are not supported without std", so this crate could -# not be checked outside a target build. -[profile.dev] -panic = "abort" -opt-level = 0 -debug = true diff --git a/userland/capsule_market/src/boot_index.rs b/userland/capsule_market/src/boot_index.rs new file mode 100644 index 0000000000..0ede60340d --- /dev/null +++ b/userland/capsule_market/src/boot_index.rs @@ -0,0 +1,54 @@ +// NONOS Operating System +// Copyright (C) 2026 NONOS Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! The catalogue this capsule starts with. + +use nonos_app_skeleton::clients::vfs::read_file; +use nonos_libc::mk_getpid; + +use crate::ingest::load_verified; +use crate::store::Store; +use crate::verify::Verifier; + +/// Where an operator drops a catalogue newer than the built-in one. +const PATH: &[u8] = b"/nonos/marketplace/index.bin"; + +/// A catalogue of every listing a machine could offer is still small next to +/// one package. +const MAX: u32 = 8 << 20; + +/// The catalogue this image shipped with. Empty when the build had none, +/// which reads as "no baseline" rather than as a failure. +static BASELINE: &[u8] = include_bytes!("../../../nonos-data/marketplace/index.bin"); + +pub fn load(store: &mut Store, verifier: &V) { + if !BASELINE.is_empty() { + take(store, verifier, BASELINE); + } + if let Ok(blob) = read_file(mk_getpid(), PATH, MAX) { + take(store, verifier, &blob); + } +} + +fn take(store: &mut Store, verifier: &V, blob: &[u8]) { + /* + * A serial no newer than the one already held is the ordinary outcome, not + * an error: it means no operator has published since this image was built. + */ + if let Ok(v) = load_verified(blob, verifier, store.last_serial()) { + store.install(v.index, v.signature_verified, v.publisher_signature_verified); + } +} diff --git a/userland/capsule_market/src/bootstrap_trust/keys.rs b/userland/capsule_market/src/bootstrap_trust/keys.rs index 777e184243..b03b429a5d 100644 --- a/userland/capsule_market/src/bootstrap_trust/keys.rs +++ b/userland/capsule_market/src/bootstrap_trust/keys.rs @@ -14,9 +14,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//! The operators whose catalogues this machine will read. + +/// Marketplace operator, v1. pub(super) const NOX_OPERATOR_V1: [u8; 32] = [ - 0x29, 0x5f, 0x84, 0xc9, 0x7c, 0x62, 0x01, 0x3c, 0x43, 0x8b, 0xca, 0x3d, 0x81, 0xc1, 0x80, 0x98, - 0x1b, 0x9f, 0x0a, 0x04, 0x3b, 0xa1, 0xfa, 0xe2, 0x54, 0xad, 0x0e, 0x12, 0xea, 0x8e, 0x07, 0x63, + 0xa7, 0xc9, 0x2d, 0xb2, 0x4d, 0x99, 0xe7, 0xba, 0xee, 0x8b, 0x45, 0xa0, 0x6d, 0xc3, 0x53, 0xcc, + 0xd4, 0x14, 0x26, 0x22, 0xc1, 0xa9, 0x0a, 0x52, 0xb5, 0x32, 0x7d, 0xb2, 0xe6, 0xd1, 0x78, 0x11, ]; +// One entry, deliberately. pub(super) const TRUSTED_OPERATORS: &[[u8; 32]] = &[NOX_OPERATOR_V1]; diff --git a/userland/capsule_market/src/install_ready/arch.rs b/userland/capsule_market/src/install_ready/arch.rs index 7f0ca8f16d..47c0020d6d 100644 --- a/userland/capsule_market/src/install_ready/arch.rs +++ b/userland/capsule_market/src/install_ready/arch.rs @@ -17,6 +17,14 @@ #[cfg(target_arch = "x86_64")] pub const RUNNING_ARCH: &str = "x86_64-nonos"; +/// The other triple this machine runs: a Linux binary of the same hardware +/// arch, hosted by the personality capsule. +#[cfg(target_arch = "x86_64")] +pub const HOSTED_ARCH: &str = "x86_64-linux"; + +#[cfg(not(target_arch = "x86_64"))] +pub const HOSTED_ARCH: &str = ""; + #[cfg(target_arch = "aarch64")] pub const RUNNING_ARCH: &str = "aarch64-nonos"; diff --git a/userland/capsule_market/src/install_ready/checks.rs b/userland/capsule_market/src/install_ready/checks.rs index 7c9675c958..e16dba2982 100644 --- a/userland/capsule_market/src/install_ready/checks.rs +++ b/userland/capsule_market/src/install_ready/checks.rs @@ -16,10 +16,14 @@ use nonos_marketplace_abi::{CapsuleRelease, InstallReadiness, ValidationStatus}; -use super::arch::RUNNING_ARCH; +use super::arch::{HOSTED_ARCH, RUNNING_ARCH}; pub const RUNNING_KERNEL_ABI: u32 = 1; +/// Releases carrying this arch are distribution packages the personality +/// hosts. +const LOCAL_ARCH: &str = HOSTED_ARCH; + pub fn evaluate( signature_verified: bool, release: &CapsuleRelease, @@ -30,8 +34,15 @@ pub fn evaluate( let package_url_present = !release.package_url.is_empty(); let package_hash_present = release.package_hash.iter().any(|&b| b != 0); let manifest_hash_present = release.manifest_hash.iter().any(|&b| b != 0); - let arch_match = release.supported_arches.iter().any(|a| a.as_str() == RUNNING_ARCH); + let runs_here = |a: &alloc::string::String| { + a.as_str() == RUNNING_ARCH || (!HOSTED_ARCH.is_empty() && a.as_str() == HOSTED_ARCH) + }; + let arch_match = release.supported_arches.iter().any(runs_here); let kernel_abi_compatible = release.kernel_abi_min <= RUNNING_KERNEL_ABI; + // Everything above this line is somebody's word. + let minted_locally = release.supported_arches.iter().any(|a| a.as_str() == LOCAL_ARCH); + let ships_proof = release.zk_trailer_hash.iter().any(|&b| b != 0); + let attestation_present = ships_proof || minted_locally; let install_ready = index_signature_valid && validation_passed @@ -40,7 +51,8 @@ pub fn evaluate( && manifest_hash_present && publisher_signature_verified && arch_match - && kernel_abi_compatible; + && kernel_abi_compatible + && attestation_present; InstallReadiness { install_ready, @@ -49,5 +61,6 @@ pub fn evaluate( publisher_signature_present: publisher_signature_verified, validation_passed, arch_match: arch_match && kernel_abi_compatible, + attestation_present, } } diff --git a/userland/capsule_market/src/main.rs b/userland/capsule_market/src/main.rs index acf2e9fe00..ef5c13927f 100644 --- a/userland/capsule_market/src/main.rs +++ b/userland/capsule_market/src/main.rs @@ -19,6 +19,7 @@ extern crate alloc; +mod boot_index; mod bootstrap_trust; mod ingest; mod install_ready; @@ -46,5 +47,9 @@ pub unsafe extern "C" fn _start() -> ! { let mut store = Store::empty(); let verifier = DefaultVerifier; + // Before the first query arrives, so a client never sees an empty + // catalogue on a machine that has one. + boot_index::load(&mut store, &verifier); + server::run(&mut store, &verifier); } diff --git a/userland/capsule_market/src/server/handlers/install_ready/constants.rs b/userland/capsule_market/src/server/handlers/install_ready/constants.rs index dce0b67089..a24d162638 100644 --- a/userland/capsule_market/src/server/handlers/install_ready/constants.rs +++ b/userland/capsule_market/src/server/handlers/install_ready/constants.rs @@ -14,4 +14,4 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -pub(super) const READINESS_LEN: usize = 6; +pub(super) const READINESS_LEN: usize = 7; diff --git a/userland/capsule_market/src/server/handlers/install_ready/find_release.rs b/userland/capsule_market/src/server/handlers/install_ready/find_release.rs index 9d68acb58e..abcc297cc0 100644 --- a/userland/capsule_market/src/server/handlers/install_ready/find_release.rs +++ b/userland/capsule_market/src/server/handlers/install_ready/find_release.rs @@ -25,10 +25,12 @@ pub(super) fn find_release<'a>( if e.listing_id != listing_id { return None; } - e.releases - .iter() - .enumerate() - .find(|(_, r)| r.release_id == release_id) - .map(|(release_index, r)| (entry_index, release_index, r)) + // An empty id asks for the default, which the index defines as the + // first release. + let wanted = match release_id.is_empty() { + true => e.releases.first().map(|r| (0usize, r)), + false => e.releases.iter().enumerate().find(|(_, r)| r.release_id == release_id), + }; + wanted.map(|(release_index, r)| (entry_index, release_index, r)) }) } diff --git a/userland/capsule_market/src/server/handlers/install_ready/handle.rs b/userland/capsule_market/src/server/handlers/install_ready/handle.rs index 6eaa63456c..99be95d450 100644 --- a/userland/capsule_market/src/server/handlers/install_ready/handle.rs +++ b/userland/capsule_market/src/server/handlers/install_ready/handle.rs @@ -49,5 +49,6 @@ pub(crate) fn handle(store: &Store, body: &[u8], req: &Request, tx: &mut [u8]) { slot[3] = verdict.publisher_signature_present as u8; slot[4] = verdict.validation_passed as u8; slot[5] = verdict.arch_match as u8; + slot[6] = verdict.attestation_present as u8; reply_with_body(tx, req, READINESS_LEN); } diff --git a/userland/marketplace_abi/src/codec/decode_index.rs b/userland/marketplace_abi/src/codec/decode_index.rs index 9b05a1299a..cdc53ebee3 100644 --- a/userland/marketplace_abi/src/codec/decode_index.rs +++ b/userland/marketplace_abi/src/codec/decode_index.rs @@ -25,7 +25,9 @@ use super::strings::{bounded_bytes, bounded_count, bounded_string}; use crate::limits::{MAX_ENTRIES, MAX_INDEX_BLOB, MAX_PUBLISHER, MAX_SIGNATURE}; use crate::types::{MarketplaceEntry, MarketplaceIndex}; -const SUPPORTED_SCHEMA: u32 = 1; +// 2: every release carries the hash of the zk trailer binding its own +// measurement to the enrolled set, and the publisher signature covers it. +const SUPPORTED_SCHEMA: u32 = 2; pub struct DecodedIndex<'a> { pub index: MarketplaceIndex, diff --git a/userland/marketplace_abi/src/codec/decode_release.rs b/userland/marketplace_abi/src/codec/decode_release.rs index a9e705b90f..9c74dc40be 100644 --- a/userland/marketplace_abi/src/codec/decode_release.rs +++ b/userland/marketplace_abi/src/codec/decode_release.rs @@ -50,6 +50,7 @@ pub(super) fn read(r: &mut Reader<'_>) -> Result { required_capabilities.push(bounded_string(r, MAX_PUBLISHER)?); } + let zk_trailer_hash = r.fixed::<32>()?; let validation = decode_validation::read(r)?; Ok(CapsuleRelease { @@ -61,6 +62,7 @@ pub(super) fn read(r: &mut Reader<'_>) -> Result { supported_arches, kernel_abi_min, required_capabilities, + zk_trailer_hash, validation, }) } diff --git a/userland/marketplace_abi/src/codec/encode_release.rs b/userland/marketplace_abi/src/codec/encode_release.rs index 6bebe2ea82..4a21730dae 100644 --- a/userland/marketplace_abi/src/codec/encode_release.rs +++ b/userland/marketplace_abi/src/codec/encode_release.rs @@ -37,5 +37,6 @@ pub(super) fn write(w: &mut Writer<'_>, release: &CapsuleRelease) { w.lp_string(cap); } + w.fixed(&release.zk_trailer_hash); encode_validation::write(w, &release.validation); } diff --git a/userland/marketplace_abi/src/codec/release_signing.rs b/userland/marketplace_abi/src/codec/release_signing.rs index c06d228d26..82f7d85cf4 100644 --- a/userland/marketplace_abi/src/codec/release_signing.rs +++ b/userland/marketplace_abi/src/codec/release_signing.rs @@ -14,9 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Canonical bytes a publisher signs for one release. Publisher -//! authority covers artifact identity and requested authority. -//! Marketplace validation is signed by the enclosing operator index. +//! Canonical bytes a publisher signs for one release. extern crate alloc; @@ -25,7 +23,8 @@ use alloc::vec::Vec; use super::writer::Writer; use crate::types::CapsuleRelease; -const RELEASE_SIGNING_DOMAIN: &[u8] = b"NONOS.marketplace.release.v1"; +// v2 because the signed bytes gained the trailer hash below. +const RELEASE_SIGNING_DOMAIN: &[u8] = b"NONOS.marketplace.release.v2"; pub fn release_signing_bytes(release: &CapsuleRelease) -> Vec { let mut out = Vec::new(); @@ -46,5 +45,6 @@ pub fn release_signing_bytes(release: &CapsuleRelease) -> Vec { for cap in &release.required_capabilities { w.lp_string(cap); } + w.fixed(&release.zk_trailer_hash); out } diff --git a/userland/marketplace_abi/src/types/readiness.rs b/userland/marketplace_abi/src/types/readiness.rs index 8b7fdd4c7d..b0d286b77e 100644 --- a/userland/marketplace_abi/src/types/readiness.rs +++ b/userland/marketplace_abi/src/types/readiness.rs @@ -14,10 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! Verdict the capsule emits when a caller asks "is this release -//! ready to install?". Five independent gates must all pass; the -//! report carries which ones tripped so a UI can explain the -//! refusal precisely. +//! Verdict the capsule emits when a caller asks "is this release ready to +//! install?". #[derive(Clone, Copy, PartialEq, Eq)] pub struct InstallReadiness { @@ -28,13 +26,14 @@ pub struct InstallReadiness { /// `package_url` is non-empty. pub package_url_present: bool, /// Publisher signature verifies against the listing pubkey. - /// The field name is kept for wire compatibility with earlier - /// six-byte readiness replies. pub publisher_signature_present: bool, /// Operator's `validation_status` is `Validated`. pub validation_passed: bool, /// Running kernel arch is in the release's `supported_arches`. pub arch_match: bool, + /// The release names a zk trailer binding its own measurement to the + /// enrolled set. + pub attestation_present: bool, } impl InstallReadiness { @@ -46,10 +45,11 @@ impl InstallReadiness { publisher_signature_present: false, validation_passed: false, arch_match: false, + attestation_present: false, } } - /// Compose a verdict from the five checks. `install_ready` is + /// Compose a verdict from the six checks. `install_ready` is /// the AND of the inputs; anything `false` blocks install. pub fn from_checks( index_signature_valid: bool, @@ -57,12 +57,14 @@ impl InstallReadiness { publisher_signature_verified: bool, validation_passed: bool, arch_match: bool, + attestation_present: bool, ) -> Self { let install_ready = index_signature_valid && package_url_present && publisher_signature_verified && validation_passed - && arch_match; + && arch_match + && attestation_present; Self { install_ready, index_signature_valid, @@ -70,6 +72,7 @@ impl InstallReadiness { publisher_signature_present: publisher_signature_verified, validation_passed, arch_match, + attestation_present, } } } diff --git a/userland/marketplace_abi/src/types/release.rs b/userland/marketplace_abi/src/types/release.rs index 8102b1c0c9..27f4719e68 100644 --- a/userland/marketplace_abi/src/types/release.rs +++ b/userland/marketplace_abi/src/types/release.rs @@ -14,10 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//! One concrete release of a marketplace entry. A release is the -//! signed unit the future capsule_installer fetches and verifies; -//! everything an installer needs to refuse a stale, mistargeted, or -//! tampered package lives here. +//! One concrete release of a marketplace entry. extern crate alloc; @@ -39,10 +36,6 @@ pub struct CapsuleRelease { /// the entry is index-only (no fetchable artifact). pub package_url: String, /// Publisher's Ed25519 signature over `release_signing_bytes`. - /// This covers the artifact hashes, URL, supported arches, - /// kernel ABI, and requested capabilities. It deliberately does - /// not cover the marketplace-operator validation report, which - /// is signed by the enclosing index. pub publisher_signature: Vec, /// Architecture triples the release supports (e.g. /// "x86_64-nonos"). At least one entry is required. @@ -51,6 +44,9 @@ pub struct CapsuleRelease { pub kernel_abi_min: u32, /// Capability names the manifest requests at install time. pub required_capabilities: Vec, + /// BLAKE3-256 of the zk trailer that proves this package's own measurement + /// is enrolled under the trust root the kernel enforces at spawn. + pub zk_trailer_hash: [u8; 32], /// Marketplace operator's validation report. pub validation: ValidationReport, } diff --git a/userland/toolkit/src/icons/all.rs b/userland/toolkit/src/icons/all.rs index 96b0b05b14..546133e3ed 100644 --- a/userland/toolkit/src/icons/all.rs +++ b/userland/toolkit/src/icons/all.rs @@ -17,7 +17,7 @@ use super::id::IconId; impl IconId { - pub const ALL: [IconId; 47] = [ + pub const ALL: [IconId; 48] = [ IconId::About, IconId::AudioPlayer, IconId::Browser, @@ -31,6 +31,7 @@ impl IconId { IconId::Processes, IconId::Settings, IconId::Snake, + IconId::Store, IconId::Terminal, IconId::VideoPlayer, IconId::Wallet, diff --git a/userland/toolkit/src/icons/id.rs b/userland/toolkit/src/icons/id.rs index a89ec54229..9fdb511f84 100644 --- a/userland/toolkit/src/icons/id.rs +++ b/userland/toolkit/src/icons/id.rs @@ -29,6 +29,7 @@ pub enum IconId { Processes, Settings, Snake, + Store, Terminal, VideoPlayer, Wallet, diff --git a/userland/toolkit/src/icons/name.rs b/userland/toolkit/src/icons/name.rs index 572e46af8d..398205a5cf 100644 --- a/userland/toolkit/src/icons/name.rs +++ b/userland/toolkit/src/icons/name.rs @@ -32,6 +32,7 @@ impl IconId { IconId::Processes => "processes", IconId::Settings => "settings", IconId::Snake => "snake", + IconId::Store => "store", IconId::Terminal => "terminal", IconId::VideoPlayer => "video_player", IconId::Wallet => "wallet", diff --git a/userland/toolkit/src/icons/table.rs b/userland/toolkit/src/icons/table.rs index e3c10cdd4a..2b37aa5ef9 100644 --- a/userland/toolkit/src/icons/table.rs +++ b/userland/toolkit/src/icons/table.rs @@ -14,10 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -/// One 8-bit coverage mask per icon, ordered to match `IconId`. The host test -/// compares every entry against the file `IconId::name` points at, so the -/// ordinal indexing below is proven rather than assumed. -pub(super) const MASKS: [&[u8]; 47] = [ +/// One 8-bit coverage mask per icon, ordered to match `IconId`. +pub(super) const MASKS: [&[u8]; 48] = [ include_bytes!("../../../assets/icons/about.a8"), include_bytes!("../../../assets/icons/audio_player.a8"), include_bytes!("../../../assets/icons/browser.a8"), @@ -31,6 +29,7 @@ pub(super) const MASKS: [&[u8]; 47] = [ include_bytes!("../../../assets/icons/processes.a8"), include_bytes!("../../../assets/icons/settings.a8"), include_bytes!("../../../assets/icons/snake.a8"), + include_bytes!("../../../assets/icons/store.a8"), include_bytes!("../../../assets/icons/terminal.a8"), include_bytes!("../../../assets/icons/video_player.a8"), include_bytes!("../../../assets/icons/wallet.a8"), diff --git a/userland/toolkit/tests/host/icon_table.rs b/userland/toolkit/tests/host/icon_table.rs index 2fdbdbef2e..dbe6afd271 100644 --- a/userland/toolkit/tests/host/icon_table.rs +++ b/userland/toolkit/tests/host/icon_table.rs @@ -15,8 +15,11 @@ use mask::{dim, mask}; fn main() { let all = IconId::ALL; let mut fail = 0usize; - if all.len() != 42 { - println!("expected 42 icons, got {}", all.len()); + // Kept in step with IconId::ALL by hand, which is the point: a count that + // updated itself would not catch an icon added to one list and not the + // other. + if all.len() != 48 { + println!("expected 48 icons, got {}", all.len()); fail += 1; } for (i, a) in all.iter().enumerate() { From b1533e872fdb033b79eac53a0f7b01dc1c83a2ea Mon Sep 17 00:00:00 2001 From: senseix21 Date: Thu, 24 Sep 2026 22:02:16 +0600 Subject: [PATCH 2/4] fix(toolkit): ship the store icon mask the icon table includes IconId::Store points table.rs at assets/icons/store.a8, which only existed on the app-store branch, so every build of this branch failed to read it. Add the mask and its SVG source here so the table stands on its own. --- userland/assets/icons/store.a8 | Bin 0 -> 36864 bytes userland/assets/icons/store.svg | 1 + 2 files changed, 1 insertion(+) create mode 100644 userland/assets/icons/store.a8 create mode 100644 userland/assets/icons/store.svg diff --git a/userland/assets/icons/store.a8 b/userland/assets/icons/store.a8 new file mode 100644 index 0000000000000000000000000000000000000000..e086216b2c331c0e487616ce1ed05662085076cc GIT binary patch literal 36864 zcmeI5Z`Gqb5XEs22}r1fNI(J-kbndvL;@0!fP_du0^9DsPtW8JeVFMb;XP;gvS~WG z_cxcM<-=~bY4p+rGyzRO6VL=S0Zl*?&;&FAO+XXS1T+CnKoigeGyzRO6VL=S0Zrgs z0>%#G^4~QM)|^{)k1EqSzeksM-T$PA&PC|&m#(`UC;a0egL>2bA#C=Iz8-1T*<~p|OuDR0 ztw}cizTc?<0sfsD5aHjafqoZW_c+A%kbYgJah#U*u)x+juf*5n9~t~a;OykHJ#07l zBFld3Is*T%SL<>e%p{09!!Cohv^kb`$&_}v%7SyoCs5lVQm&Y?AaUE(Hc%9#BdvWLy0GtR8)yqT zPR8Ff(G95e3G@i@*N>S8Z?56WNATulFWSK^q45>37B`%}k&IT&5QTOH`;X>27zWyj zQZq(-{m1F5KerLtLFU2tD)6`N@niUjyn_D_+Jg=H^u4MF(+DbZaj^eD}0nh&3ip^TxaK&yk^PSN@OQx&s&q0H$*L}N9wi!06YdnO8Y04gCZ!N-hzNmV3?bGK5e76Vg;+yG2oz%ov4)5+ph+pj8X`iV z7(<9PM1%oNN+H$|5dy^+LaZSo3}{jcv4)5cD8>+C4H02LlTwH^M1(*wh7fCr2m_jw zLaZSo1d1_)SVKe@(4-V%4G|$wj3LAtBEov0YtRW%{Xi^HXc00W){`~<*I%5n+iob7+ zm0y=`f%PcU3;A~|Q<1E)0sb!JkNy>w@GeUDUC1x|muGFLQtk%W$hehc@Lc0Vo$Vpi zVJ>@wyMOo0_3v^Q^6zDSyLpK4<-gP#9e`&yez5QEE*aRtN1^SzaQi>`QmsV%`U&y& zX`23ey5Al8FK)E|UHC=Pfh+NS6F_G9M2n>rK0qB!77=;0G;R z^qXJ?)4y((>1SW!F8KMv|0L$pAOHC)&f3>ntO;lWnt&#t31|YEfF_^`XabsmCZGwN GOW+S$=4$!? literal 0 HcmV?d00001 diff --git a/userland/assets/icons/store.svg b/userland/assets/icons/store.svg new file mode 100644 index 0000000000..9db2b1c838 --- /dev/null +++ b/userland/assets/icons/store.svg @@ -0,0 +1 @@ + From 5d9e2d6ff7507ebe4bb4b3318f90d743c2d1f622 Mon Sep 17 00:00:00 2001 From: senseix21 Date: Thu, 24 Sep 2026 22:23:22 +0600 Subject: [PATCH 3/4] fix(market): track the signed index only when the tree has one nonos-data/marketplace/index.bin has no make rule, so naming it as a hard prerequisite failed every build on a checkout without it (CI: No rule to make target). Wrapping it in $(wildcard) keeps the rebuild on a newer catalogue where it exists and drops the prerequisite where it does not. --- userland/capsule_market/Capsule.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userland/capsule_market/Capsule.mk b/userland/capsule_market/Capsule.mk index 5545c423a3..cc66a62043 100644 --- a/userland/capsule_market/Capsule.mk +++ b/userland/capsule_market/Capsule.mk @@ -22,6 +22,6 @@ CAPSULE_KERNEL_MIRROR := src/security/market_capsule # lists only sources, and without this line a freshly signed catalogue # was silently left out of the image: the build succeeded, the boot # succeeded, and the machine served the previous one. -CAPSULE_EXTRA_DEPS := nonos-data/marketplace/index.bin +CAPSULE_EXTRA_DEPS := $(wildcard nonos-data/marketplace/index.bin) include nonos-mk/capsule.mk From d221a0d7d8dba43fe8878f13d837c1375caa20d5 Mon Sep 17 00:00:00 2001 From: senseix21 Date: Thu, 24 Sep 2026 22:53:27 +0600 Subject: [PATCH 4/4] fix(market): keep nonos_ed25519, which verify/crypto.rs still uses The branch's manifest predated the switch to in-process Ed25519 and dropped the dependency while verify/crypto.rs imports it, so the capsule failed with an unresolved import. Restore main's manifest and add only the app_skeleton dependency boot_index.rs needs. --- userland/capsule_market/Cargo.lock | 80 ++++++++++++++++++++++++++++-- userland/capsule_market/Cargo.toml | 18 +++++-- 2 files changed, 90 insertions(+), 8 deletions(-) diff --git a/userland/capsule_market/Cargo.lock b/userland/capsule_market/Cargo.lock index ccce193879..d24abf9bdf 100644 --- a/userland/capsule_market/Cargo.lock +++ b/userland/capsule_market/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "libm", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" +dependencies = [ + "libm", +] + +[[package]] +name = "core_maths" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" +dependencies = [ + "libm", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linked_list_allocator" version = "0.10.6" @@ -20,10 +55,19 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "nonos_app_skeleton" +version = "0.3.0" +dependencies = [ + "nonos_toolkit", + "nonos_userland_libc", +] + [[package]] name = "nonos_capsule_market" version = "0.3.0" dependencies = [ + "nonos_app_skeleton", "nonos_ed25519", "nonos_marketplace_abi", "nonos_userland_libc", @@ -33,18 +77,27 @@ dependencies = [ name = "nonos_ed25519" version = "0.1.0" dependencies = [ - "nonos_hd", + "nonos_hash", "spin", ] [[package]] -name = "nonos_hd" -version = "0.3.0" +name = "nonos_hash" +version = "0.1.0" [[package]] name = "nonos_marketplace_abi" version = "0.3.0" +[[package]] +name = "nonos_toolkit" +version = "0.3.0" +dependencies = [ + "ab_glyph", + "nonos_userland_libc", + "spin", +] + [[package]] name = "nonos_userland_libc" version = "0.3.0" @@ -52,6 +105,15 @@ dependencies = [ "linked_list_allocator", ] +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -63,6 +125,9 @@ name = "spin" version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] [[package]] name = "spinning_top" @@ -72,3 +137,12 @@ checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" dependencies = [ "lock_api", ] + +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] diff --git a/userland/capsule_market/Cargo.toml b/userland/capsule_market/Cargo.toml index 78ac05118d..ebd837c5be 100644 --- a/userland/capsule_market/Cargo.toml +++ b/userland/capsule_market/Cargo.toml @@ -22,17 +22,17 @@ name = "market" path = "src/main.rs" [dependencies] +nonos_ed25519 = { path = "../nonos_ed25519" } nonos_libc = { package = "nonos_userland_libc", path = "../libc" } nonos_marketplace_abi = { path = "../marketplace_abi" } nonos_app_skeleton = { path = "../app_skeleton" } [features] default = [] -# Replaces the default `CryptoVerifier` (which routes to -# capsule_crypto through the kernel's `CryptoEd25519Verify` -# syscall) with `RejectAll`. Useful when the kernel image does -# not embed capsule_crypto; every signed index ends up refused, -# which keeps install readiness honest in the offline build. +# Replaces the default `CryptoVerifier`, which verifies in this +# process with nonos_ed25519, with `RejectAll`. Every signed index +# ends up refused, which keeps install readiness honest in a build +# that is meant to accept nothing. offline-verify = [] [profile.release] panic = "abort" @@ -40,3 +40,11 @@ opt-level = 2 lto = false debug = false strip = true + +# Matches every sibling capsule. Without it `cargo check` on the host fails +# with "unwinding panics are not supported without std", so this crate could +# not be checked outside a target build. +[profile.dev] +panic = "abort" +opt-level = 0 +debug = true