kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

package.c (70602B)


      1 /* Public signed-package API: the create/verify/unpack/inspect pipelines for
      2  * portable .tar.gz and native .kpkg packages, composed over the internal
      3  * dist model (src/dist/). See <kit/package.h> and doc/DISTRIBUTE.md.
      4  *
      5  * The pipelines were lifted from the kit pkg tool; the driver keeps only
      6  * argument parsing, stdout formatting, host-vtable wiring, and trusted-keys
      7  * path/pin policy. Operational errors emit through ctx->diag (kit_ctx_diagf);
      8  * arg/CLI errors stay in the driver. */
      9 
     10 #include <kit/cas.h>
     11 #include <kit/package.h>
     12 #include <stdio.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 
     16 #include "core/diag.h"
     17 #include "dist/blake2b.h"
     18 #include "dist/blob.h"
     19 #include "dist/cas.h"
     20 #include "dist/deflate.h"
     21 #include "dist/dist.h"
     22 #include "dist/kpkg.h"
     23 #include "dist/lz4.h"
     24 #include "dist/manifest.h"
     25 #include "dist/minisig.h"
     26 #include "dist/release.h"
     27 #include "dist/tar.h"
     28 #include "dist/tree.h"
     29 #include "dist/trust.h"
     30 
     31 #define PKG_PATH_BUF 1024u
     32 #define PKG_META_MANIFEST "kit/package.manifest"
     33 #define PKG_META_SIG "kit/package.manifest.minisig"
     34 #define PKG_META_PUB "kit/package.pub"
     35 #define PKG_DEFAULT_OUTPUT_ID 0u
     36 #define PKG_MAX_TAR_ENTRIES (DIST_MAX_FILES + DIST_MAX_OUTPUTS + 8u)
     37 
     38 _Static_assert(KIT_PKG_KEYID_LEN == DIST_KEYID_LEN, "keyid len");
     39 _Static_assert(KIT_PKG_PK_LEN == DIST_ED25519_PK_LEN, "pk len");
     40 _Static_assert(KIT_PKG_SK_LEN == DIST_ED25519_SK_LEN, "sk len");
     41 _Static_assert(KIT_PKG_NAME_MAX == DIST_NAME_MAX, "name max");
     42 _Static_assert(KIT_PKG_VERSION_MAX == DIST_VERSION_MAX, "version max");
     43 _Static_assert(KIT_PKG_TRUSTED_COMMENT_MAX == DIST_TRUSTED_COMMENT_MAX,
     44                "trusted comment max");
     45 _Static_assert(KIT_CAS_HASH_LEN == DIST_BLAKE2B_LEN, "hash len");
     46 _Static_assert((int)KIT_PKG_COMPRESSION_NONE == DIST_KPKG_COMP_NONE,
     47                "comp none");
     48 _Static_assert((int)KIT_PKG_COMPRESSION_LZ4_BLOCK_V1 ==
     49                    DIST_KPKG_COMP_LZ4_BLOCK_V1,
     50                "comp lz4");
     51 
     52 typedef enum PkgNativeShape {
     53   PKG_NATIVE_FAT,
     54   PKG_NATIVE_METADATA,
     55   PKG_NATIVE_THIN
     56 } PkgNativeShape;
     57 
     58 /* kit_pkg_create casts KitPkgShape straight to PkgNativeShape. */
     59 _Static_assert((int)KIT_PKG_SHAPE_FAT == PKG_NATIVE_FAT, "shape fat");
     60 _Static_assert((int)KIT_PKG_SHAPE_METADATA == PKG_NATIVE_METADATA,
     61                "shape metadata");
     62 _Static_assert((int)KIT_PKG_SHAPE_THIN == PKG_NATIVE_THIN, "shape thin");
     63 
     64 typedef struct PkgBlob {
     65   KitFileData fd;
     66   int loaded;
     67   uint8_t id[DIST_BLAKE2B_LEN];
     68   uint8_t root[DIST_BLAKE2B_LEN];
     69   uint64_t size;
     70 } PkgBlob;
     71 
     72 typedef struct PkgSource {
     73   const KitContext* ctx;
     74   const KitCasHost* host;
     75   DistTree tree;
     76   DistTreeEntry entries[DIST_MAX_FILES];
     77   PkgBlob blobs[DIST_MAX_FILES];
     78   size_t n_blobs;
     79   uint8_t tree_id[DIST_BLAKE2B_LEN];
     80   const uint8_t* tree_bytes;
     81   size_t tree_size;
     82   KitWriter* tree_mem;
     83   KitFileData tree_fd;
     84   int tree_loaded;
     85 } PkgSource;
     86 
     87 typedef struct PkgVerified {
     88   DistPackageManifest manifest;
     89   uint8_t package_id[DIST_BLAKE2B_LEN];
     90   uint8_t keyid[DIST_KEYID_LEN];
     91   uint8_t pk[DIST_ED25519_PK_LEN];
     92   char trusted[DIST_TRUSTED_COMMENT_MAX];
     93   int tofu_pin;
     94 } PkgVerified;
     95 
     96 typedef struct PkgLoadedTree {
     97   DistTree tree;
     98   DistTreeEntry entries[DIST_MAX_FILES];
     99   uint8_t id[DIST_BLAKE2B_LEN];
    100   const uint8_t* bytes;
    101   size_t size;
    102 } PkgLoadedTree;
    103 
    104 /* ---------------------------------------------------------------------- */
    105 /* shared helpers                                                         */
    106 /* ---------------------------------------------------------------------- */
    107 
    108 static int pkg_write_file(const KitContext* ctx, const char* path,
    109                           const uint8_t* data, size_t len) {
    110   KitWriter* w = NULL;
    111   int rc;
    112   if (ctx->file_io->open_writer(ctx->file_io->user, path, &w) != KIT_OK) {
    113     kit_ctx_diagf(ctx, "failed to open output: %s", path);
    114     return DIST_ERR;
    115   }
    116   rc = (len == 0 || kit_writer_write(w, data, len) == KIT_OK) ? DIST_OK
    117                                                               : DIST_ERR;
    118   if (kit_writer_status(w) != KIT_OK) rc = DIST_ERR;
    119   kit_writer_close(w);
    120   if (rc != DIST_OK) kit_ctx_diagf(ctx, "failed to write: %s", path);
    121   return rc;
    122 }
    123 
    124 static KitWriter* pkg_mem(const KitContext* ctx) {
    125   KitWriter* w = NULL;
    126   if (kit_writer_mem(ctx->heap, &w) != KIT_OK) return NULL;
    127   return w;
    128 }
    129 
    130 static void pkg_parent_dir(const char* path, char* buf, size_t cap) {
    131   const char* slash = NULL;
    132   const char* p;
    133   size_t n;
    134   for (p = path; *p; ++p)
    135     if (*p == '/') slash = p;
    136   if (!slash) {
    137     buf[0] = '\0';
    138     return;
    139   }
    140   n = (size_t)(slash - path);
    141   if (n >= cap) n = cap - 1u;
    142   memcpy(buf, path, n);
    143   buf[n] = '\0';
    144 }
    145 
    146 static int pkg_join_path(char* out, size_t cap, const char* dir,
    147                          const char* rel) {
    148   size_t dl, rl;
    149   int slash;
    150   if (!out || !cap || !dir || !rel) return DIST_ERR;
    151   dl = strlen(dir);
    152   rl = strlen(rel);
    153   slash = dl > 0 && dir[dl - 1u] != '/';
    154   if (dl + (slash ? 1u : 0u) + rl + 1u > cap) return DIST_ERR;
    155   memcpy(out, dir, dl);
    156   if (slash) out[dl++] = '/';
    157   memcpy(out + dl, rel, rl);
    158   out[dl + rl] = '\0';
    159   return DIST_OK;
    160 }
    161 
    162 static int pkg_read_file(const KitContext* ctx, const char* path,
    163                          KitFileData* out) {
    164   return ctx->file_io->read_all(ctx->file_io->user, path, out) == KIT_OK
    165              ? DIST_OK
    166              : DIST_ERR;
    167 }
    168 
    169 static const DistTarEntry* pkg_find_name(const DistTarEntry* e, size_t n,
    170                                          const char* name) {
    171   size_t i;
    172   for (i = 0; i < n; ++i)
    173     if (strcmp(e[i].name, name) == 0) return &e[i];
    174   return NULL;
    175 }
    176 
    177 static uint64_t pkg_align_up(uint64_t v, uint64_t a) {
    178   return a ? ((v + a - 1u) / a) * a : v;
    179 }
    180 
    181 static int pkg_write_pad(KitWriter* w, uint64_t target) {
    182   static const uint8_t z[64] = {0};
    183   while (kit_writer_tell(w) < target) {
    184     uint64_t left = target - kit_writer_tell(w);
    185     size_t n = left < sizeof z ? (size_t)left : sizeof z;
    186     if (kit_writer_write(w, z, n) != KIT_OK) return DIST_ERR;
    187   }
    188   return kit_writer_tell(w) == target ? DIST_OK : DIST_ERR;
    189 }
    190 
    191 static void pkg_hash(uint8_t out[DIST_BLAKE2B_LEN], const uint8_t* data,
    192                      size_t len) {
    193   dist_blake2b(out, data, len);
    194 }
    195 
    196 static int pkg_parse_id(const char* s, uint8_t out[DIST_BLAKE2B_LEN]) {
    197   if (!s || strlen(s) != 2u * DIST_BLAKE2B_LEN) return DIST_ERR;
    198   return dist_hex_decode(out, s, DIST_BLAKE2B_LEN);
    199 }
    200 
    201 static int pkg_cas_rel_path(char* out, size_t cap, const char* kind,
    202                             const uint8_t id[DIST_BLAKE2B_LEN]) {
    203   char hex[2 * DIST_BLAKE2B_LEN + 1];
    204   int n;
    205   dist_hex_encode(hex, id, DIST_BLAKE2B_LEN);
    206   n = snprintf(out, cap, "kit/cas/%s/%c%c/%s", kind, hex[0], hex[1], hex);
    207   return n > 0 && (size_t)n < cap ? DIST_OK : DIST_ERR;
    208 }
    209 
    210 static int pkg_external_id_path(char* out, size_t cap, const char* kind,
    211                                 const uint8_t id[DIST_BLAKE2B_LEN]) {
    212   if (strcmp(kind, "tree") == 0) return dist_cas_tree_relpath(out, cap, id);
    213   if (strcmp(kind, "index") == 0) return dist_cas_index_relpath(out, cap, id);
    214   if (strcmp(kind, "blob") == 0) return dist_cas_blob_relpath(out, cap, id);
    215   return DIST_ERR;
    216 }
    217 
    218 static int pkg_external_chunk_path(char* out, size_t cap,
    219                                    const uint8_t blob[DIST_BLAKE2B_LEN],
    220                                    uint64_t chunk_index) {
    221   return dist_cas_chunk_relpath(out, cap, blob, chunk_index);
    222 }
    223 
    224 static int pkg_external_path(char* out, size_t cap, const char* root,
    225                              const char* rel) {
    226   /* Reuse the canonical tree-path validator (dist/tree.h): rejects absolute,
    227    * '.'/'..', backslash, drive-colon, and newline components. */
    228   if (!dist_tree_path_valid(rel)) return DIST_ERR;
    229   return pkg_join_path(out, cap, root, rel);
    230 }
    231 
    232 static int pkg_write_external_file(const KitCasHost* host,
    233                                    const KitContext* ctx, const char* root,
    234                                    const char* rel, const uint8_t* data,
    235                                    size_t len) {
    236   char full[PKG_PATH_BUF], parent[PKG_PATH_BUF];
    237   if (!root || pkg_external_path(full, sizeof full, root, rel) != DIST_OK)
    238     return DIST_ERR;
    239   pkg_parent_dir(full, parent, sizeof parent);
    240   if (parent[0] && host->mkdir_p(host->user, parent) != 0) return DIST_ERR;
    241   return pkg_write_file(ctx, full, data, len);
    242 }
    243 
    244 static int pkg_read_external_file(const KitContext* ctx, const char* root,
    245                                   const char* rel, KitFileData* out) {
    246   char full[PKG_PATH_BUF];
    247   if (!root || pkg_external_path(full, sizeof full, root, rel) != DIST_OK)
    248     return DIST_ERR;
    249   return pkg_read_file(ctx, full, out);
    250 }
    251 
    252 static int pkg_blob_cmp(const void* ap, const void* bp) {
    253   const PkgBlob* a = (const PkgBlob*)ap;
    254   const PkgBlob* b = (const PkgBlob*)bp;
    255   return memcmp(a->id, b->id, DIST_BLAKE2B_LEN);
    256 }
    257 
    258 /* ---------------------------------------------------------------------- */
    259 /* source assembly (root walk / cas load)                                 */
    260 /* ---------------------------------------------------------------------- */
    261 
    262 static PkgBlob* pkg_source_find_blob(PkgSource* src,
    263                                      const uint8_t id[DIST_BLAKE2B_LEN]) {
    264   size_t i;
    265   for (i = 0; i < src->n_blobs; ++i)
    266     if (memcmp(src->blobs[i].id, id, DIST_BLAKE2B_LEN) == 0)
    267       return &src->blobs[i];
    268   return NULL;
    269 }
    270 
    271 static void pkg_source_init(PkgSource* src, const KitContext* ctx,
    272                             const KitCasHost* host) {
    273   memset(src, 0, sizeof *src);
    274   src->ctx = ctx;
    275   src->host = host;
    276   src->tree.entries = src->entries;
    277   src->tree.cap_entries = DIST_MAX_FILES;
    278 }
    279 
    280 static void pkg_source_release(PkgSource* src) {
    281   const KitFileIO* io;
    282   size_t i;
    283   if (!src || !src->host) return;
    284   io = src->host->file_io;
    285   for (i = 0; i < src->n_blobs; ++i) {
    286     if (src->blobs[i].loaded && io->release)
    287       io->release(io->user, &src->blobs[i].fd);
    288   }
    289   if (src->tree_loaded && io->release) io->release(io->user, &src->tree_fd);
    290   if (src->tree_mem) kit_writer_close(src->tree_mem);
    291   memset(src, 0, sizeof *src);
    292 }
    293 
    294 static int pkg_source_store_blob(PkgSource* src, KitFileData* fd,
    295                                  const DistBlobInfo* bi, int take_fd) {
    296   PkgBlob* existing = pkg_source_find_blob(src, bi->id);
    297   if (existing) {
    298     if (existing->size != bi->size ||
    299         memcmp(existing->root, bi->root, DIST_BLAKE2B_LEN) != 0)
    300       return DIST_ERR;
    301     return DIST_OK;
    302   }
    303   if (src->n_blobs >= DIST_MAX_FILES) return DIST_ERR;
    304   existing = &src->blobs[src->n_blobs++];
    305   memset(existing, 0, sizeof *existing);
    306   memcpy(existing->id, bi->id, DIST_BLAKE2B_LEN);
    307   memcpy(existing->root, bi->root, DIST_BLAKE2B_LEN);
    308   existing->size = bi->size;
    309   if (take_fd) {
    310     existing->fd = *fd;
    311     existing->loaded = 1;
    312     fd->data = NULL;
    313     fd->size = 0;
    314     fd->token = NULL;
    315   }
    316   return DIST_OK;
    317 }
    318 
    319 static int pkg_source_add_entry(PkgSource* src, const char* tree_path,
    320                                 uint8_t mode, KitFileData* fd, int take_fd) {
    321   DistBlobInfo bi;
    322   DistTreeEntry* e;
    323   if (src->tree.n_entries >= src->tree.cap_entries) {
    324     kit_ctx_diagf(src->ctx, "create: too many tree entries");
    325     return DIST_ERR;
    326   }
    327   if (!dist_tree_path_valid(tree_path)) {
    328     kit_ctx_diagf(src->ctx, "create: unsafe tree path: %s", tree_path);
    329     return DIST_ERR;
    330   }
    331   if (!dist_tree_mode_name(mode)) {
    332     kit_ctx_diagf(src->ctx, "create: bad tree mode: %s", tree_path);
    333     return DIST_ERR;
    334   }
    335   if (dist_blob_info(&bi, fd->data, fd->size, DIST_BLOB_CHUNK_SIZE_DEFAULT) !=
    336       DIST_OK) {
    337     kit_ctx_diagf(src->ctx, "create: failed to hash blob: %s", tree_path);
    338     return DIST_ERR;
    339   }
    340   if (pkg_source_store_blob(src, fd, &bi, take_fd) != DIST_OK) {
    341     kit_ctx_diagf(src->ctx, "create: failed to store blob metadata: %s",
    342                   tree_path);
    343     return DIST_ERR;
    344   }
    345   e = &src->tree.entries[src->tree.n_entries++];
    346   memset(e, 0, sizeof *e);
    347   snprintf(e->path, sizeof e->path, "%s", tree_path);
    348   e->mode = mode;
    349   e->size = bi.size;
    350   memcpy(e->blob, bi.id, DIST_BLAKE2B_LEN);
    351   memcpy(e->root, bi.root, DIST_BLAKE2B_LEN);
    352   return DIST_OK;
    353 }
    354 
    355 static int pkg_source_walk_file(void* user, const char* source_path,
    356                                 const char* tree_path, int executable) {
    357   PkgSource* src = (PkgSource*)user;
    358   const KitFileIO* io = src->host->file_io;
    359   KitFileData fd;
    360   int rc;
    361   fd.data = NULL;
    362   fd.size = 0;
    363   fd.token = NULL;
    364   if (io->read_all(io->user, source_path, &fd) != KIT_OK) {
    365     kit_ctx_diagf(src->ctx, "create: cannot read file: %s", source_path);
    366     return 1;
    367   }
    368   rc = pkg_source_add_entry(
    369       src, tree_path, executable ? DIST_TREE_MODE_EXEC : DIST_TREE_MODE_FILE,
    370       &fd, 1);
    371   if (fd.data && io->release) io->release(io->user, &fd);
    372   return rc == DIST_OK ? 0 : 1;
    373 }
    374 
    375 static int pkg_source_finish_tree(PkgSource* src) {
    376   char err[128];
    377   if (dist_tree_sort_validate(&src->tree, err, sizeof err) != DIST_OK) {
    378     kit_ctx_diagf(src->ctx, "create: %s", err);
    379     return DIST_ERR;
    380   }
    381   if (kit_writer_mem(src->ctx->heap, &src->tree_mem) != KIT_OK) return DIST_ERR;
    382   if (dist_tree_emit(&src->tree, src->tree_mem) != DIST_OK ||
    383       kit_writer_status(src->tree_mem) != KIT_OK) {
    384     kit_ctx_diagf(src->ctx, "create: failed to emit tree manifest");
    385     return DIST_ERR;
    386   }
    387   src->tree_bytes = kit_writer_mem_bytes(src->tree_mem, &src->tree_size);
    388   dist_tree_id(src->tree_id, src->tree_bytes, src->tree_size);
    389   qsort(src->blobs, src->n_blobs, sizeof src->blobs[0], pkg_blob_cmp);
    390   return DIST_OK;
    391 }
    392 
    393 static int pkg_source_from_root(PkgSource* src, const char* root) {
    394   if (src->host->walk_regular_files(src->host->user, root, pkg_source_walk_file,
    395                                     src) != 0) {
    396     kit_ctx_diagf(src->ctx, "create: failed to walk directory: %s", root);
    397     return DIST_ERR;
    398   }
    399   return pkg_source_finish_tree(src);
    400 }
    401 
    402 static void pkg_cas_init_get(DistCas* cas, const KitCasHost* host,
    403                              const char* root) {
    404   memset(cas, 0, sizeof *cas);
    405   cas->host.file_io = host->file_io;
    406   cas->host.user = host->user;
    407   cas->root = root;
    408 }
    409 
    410 static int pkg_source_load_blob_from_cas(PkgSource* src, DistCas* cas,
    411                                          const DistTreeEntry* e) {
    412   const KitFileIO* io = src->host->file_io;
    413   PkgBlob* existing = pkg_source_find_blob(src, e->blob);
    414   KitFileData fd;
    415   DistBlobInfo bi;
    416   if (existing) {
    417     if (existing->size != e->size ||
    418         memcmp(existing->root, e->root, DIST_BLAKE2B_LEN) != 0) {
    419       kit_ctx_diagf(src->ctx, "create: duplicate blob metadata mismatch: %s",
    420                     e->path);
    421       return DIST_ERR;
    422     }
    423     return DIST_OK;
    424   }
    425   fd.data = NULL;
    426   fd.size = 0;
    427   fd.token = NULL;
    428   if (dist_cas_get_blob(cas, e->blob, &fd) != DIST_OK) {
    429     kit_ctx_diagf(src->ctx, "create: missing or corrupt blob for: %s", e->path);
    430     return DIST_ERR;
    431   }
    432   if (dist_blob_info(&bi, fd.data, fd.size, DIST_BLOB_CHUNK_SIZE_DEFAULT) !=
    433           DIST_OK ||
    434       bi.size != e->size || memcmp(bi.root, e->root, DIST_BLAKE2B_LEN) != 0) {
    435     if (io->release) io->release(io->user, &fd);
    436     kit_ctx_diagf(src->ctx, "create: blob root mismatch for: %s", e->path);
    437     return DIST_ERR;
    438   }
    439   if (pkg_source_store_blob(src, &fd, &bi, 1) != DIST_OK) {
    440     if (fd.data && io->release) io->release(io->user, &fd);
    441     return DIST_ERR;
    442   }
    443   return DIST_OK;
    444 }
    445 
    446 static int pkg_source_from_cas(PkgSource* src, const char* cas_dir,
    447                                const char* tree_s) {
    448   DistCas cas;
    449   char err[128];
    450   size_t i;
    451   if (pkg_parse_id(tree_s, src->tree_id) != DIST_OK) {
    452     kit_ctx_diagf(src->ctx, "create: bad tree id: %s", tree_s);
    453     return DIST_ERR;
    454   }
    455   pkg_cas_init_get(&cas, src->host, cas_dir);
    456   src->tree_fd.data = NULL;
    457   src->tree_fd.size = 0;
    458   src->tree_fd.token = NULL;
    459   if (dist_cas_get_tree(&cas, src->tree_id, &src->tree_fd) != DIST_OK) {
    460     kit_ctx_diagf(src->ctx, "create: missing or corrupt tree: %s", tree_s);
    461     return DIST_ERR;
    462   }
    463   src->tree_loaded = 1;
    464   src->tree_bytes = src->tree_fd.data;
    465   src->tree_size = src->tree_fd.size;
    466   if (dist_tree_parse(src->tree_bytes, src->tree_size, &src->tree, err,
    467                       sizeof err) != DIST_OK) {
    468     kit_ctx_diagf(src->ctx, "create: tree: %s", err);
    469     return DIST_ERR;
    470   }
    471   for (i = 0; i < src->tree.n_entries; ++i) {
    472     if (pkg_source_load_blob_from_cas(src, &cas, &src->tree.entries[i]) !=
    473         DIST_OK)
    474       return DIST_ERR;
    475   }
    476   qsort(src->blobs, src->n_blobs, sizeof src->blobs[0], pkg_blob_cmp);
    477   return DIST_OK;
    478 }
    479 
    480 static int pkg_manifest_from_source(const char* name, const char* version,
    481                                     const char* desc, const PkgSource* src,
    482                                     DistPackageManifest* m) {
    483   size_t i;
    484   memset(m, 0, sizeof *m);
    485   snprintf(m->name, sizeof m->name, "%s", name);
    486   snprintf(m->version, sizeof m->version, "%s", version);
    487   if (desc) snprintf(m->description, sizeof m->description, "%s", desc);
    488   m->n_outputs = 1;
    489   m->outputs[0].id = PKG_DEFAULT_OUTPUT_ID;
    490   snprintf(m->outputs[0].name, sizeof m->outputs[0].name, "%s", "default");
    491   memcpy(m->outputs[0].tree, src->tree_id, DIST_BLAKE2B_LEN);
    492   m->outputs[0].is_default = 1;
    493   for (i = 0; i < src->tree.n_entries; ++i) {
    494     DistPackageArtifact* a;
    495     if (m->n_artifacts >= DIST_MAX_ARTIFACTS) return DIST_ERR;
    496     a = &m->artifacts[m->n_artifacts++];
    497     a->output_id = PKG_DEFAULT_OUTPUT_ID;
    498     snprintf(a->path, sizeof a->path, "%s", src->tree.entries[i].path);
    499     snprintf(a->kind, sizeof a->kind, "%s", "data");
    500     a->entry = 1;
    501   }
    502   return dist_package_manifest_validate(m, NULL, 0);
    503 }
    504 
    505 static int pkg_sign(KitWriter* out, const KitContext* ctx, const uint8_t* data,
    506                     size_t len, const DistKeypair* kp,
    507                     const uint8_t pkgid[DIST_BLAKE2B_LEN], const char* what) {
    508   char tcomment[DIST_TRUSTED_COMMENT_MAX];
    509   char pkgid_hex[2 * DIST_BLAKE2B_LEN + 1];
    510   dist_hex_encode(pkgid_hex, pkgid, DIST_BLAKE2B_LEN);
    511   snprintf(tcomment, sizeof tcomment, "created=%lld pkgid=%s",
    512            (long long)(ctx->now > 0 ? ctx->now : 0), pkgid_hex);
    513   return dist_minisig_sign(out, data, len, kp->sk, kp->keyid, what, tcomment);
    514 }
    515 
    516 /* ---------------------------------------------------------------------- */
    517 /* create                                                                 */
    518 /* ---------------------------------------------------------------------- */
    519 
    520 static int pkg_create_targz(const KitContext* ctx, const char* out,
    521                             const PkgSource* src, const uint8_t* man,
    522                             size_t man_len, const uint8_t* sig, size_t sig_len,
    523                             const uint8_t* pub, size_t pub_len) {
    524   KitWriter *tar = NULL, *gz = NULL;
    525   const uint8_t *tb, *gb;
    526   size_t tl, gl, i;
    527   char path[PKG_PATH_BUF];
    528   int rc = DIST_ERR;
    529   tar = pkg_mem(ctx);
    530   gz = pkg_mem(ctx);
    531   if (!tar || !gz) goto done;
    532   if (dist_tar_append(tar, PKG_META_MANIFEST, man, man_len) != DIST_OK ||
    533       dist_tar_append(tar, PKG_META_SIG, sig, sig_len) != DIST_OK ||
    534       dist_tar_append(tar, PKG_META_PUB, pub, pub_len) != DIST_OK)
    535     goto done;
    536   if (pkg_cas_rel_path(path, sizeof path, "tree", src->tree_id) != DIST_OK ||
    537       dist_tar_append(tar, path, src->tree_bytes, src->tree_size) != DIST_OK)
    538     goto done;
    539   for (i = 0; i < src->n_blobs; ++i) {
    540     if (pkg_cas_rel_path(path, sizeof path, "blob", src->blobs[i].id) !=
    541             DIST_OK ||
    542         dist_tar_append(tar, path, src->blobs[i].fd.data,
    543                         (size_t)src->blobs[i].size) != DIST_OK)
    544       goto done;
    545   }
    546   if (dist_tar_finish(tar) != DIST_OK) goto done;
    547   tb = kit_writer_mem_bytes(tar, &tl);
    548   if (dist_gz_compress(gz, tb, tl) != DIST_OK) goto done;
    549   gb = kit_writer_mem_bytes(gz, &gl);
    550   rc = pkg_write_file(ctx, out, gb, gl);
    551 done:
    552   if (gz) kit_writer_close(gz);
    553   if (tar) kit_writer_close(tar);
    554   return rc;
    555 }
    556 
    557 static int pkg_build_native_regions(const KitCasHost* host,
    558                                     const KitContext* ctx, const PkgSource* src,
    559                                     uint32_t compression,
    560                                     const char* external_dir, int embed_content,
    561                                     KitWriter** index_out,
    562                                     KitWriter** content_out) {
    563   KitWriter* index = pkg_mem(ctx);
    564   KitWriter* content = pkg_mem(ctx);
    565   size_t bi;
    566   if (!index || !content) goto fail;
    567   for (bi = 0; bi < src->n_blobs; ++bi) {
    568     const PkgBlob* blob = &src->blobs[bi];
    569     size_t off = 0, ci = 0;
    570     if (blob->size == 0) continue;
    571     while (off < blob->size) {
    572       uint8_t recbuf[DIST_KPKG3_INDEX_RECORD_SIZE];
    573       DistKpkg3IndexRecord r;
    574       const uint8_t* raw = blob->fd.data + off;
    575       size_t raw_len = (size_t)blob->size - off;
    576       if (raw_len > DIST_KPKG3_CHUNK_SIZE_DEFAULT)
    577         raw_len = DIST_KPKG3_CHUNK_SIZE_DEFAULT;
    578       memset(&r, 0, sizeof r);
    579       memcpy(r.blob_id, blob->id, DIST_BLAKE2B_LEN);
    580       r.chunk_index = (uint64_t)ci;
    581       r.content_offset = embed_content ? kit_writer_tell(content) : 0;
    582       r.raw_size = raw_len;
    583       r.compression = compression;
    584       pkg_hash(r.raw_hash, raw, raw_len);
    585       dist_blob_leaf_hash(r.leaf_hash, r.chunk_index, raw, raw_len);
    586       if (compression == DIST_KPKG_COMP_NONE) {
    587         r.stored_size = raw_len;
    588         pkg_hash(r.stored_hash, raw, raw_len);
    589         if (embed_content && kit_writer_write(content, raw, raw_len) != KIT_OK)
    590           goto fail;
    591         if (!embed_content) {
    592           char rel[PKG_PATH_BUF];
    593           if (pkg_external_chunk_path(rel, sizeof rel, blob->id,
    594                                       r.chunk_index) != DIST_OK ||
    595               pkg_write_external_file(host, ctx, external_dir, rel, raw,
    596                                       raw_len) != DIST_OK)
    597             goto fail;
    598         }
    599       } else {
    600         uint8_t tmp[DIST_KPKG3_CHUNK_SIZE_DEFAULT + 1024u];
    601         size_t stored_len = 0;
    602         if (dist_lz4_compress_block(tmp, sizeof tmp, &stored_len, raw,
    603                                     raw_len) != DIST_OK) {
    604           kit_ctx_diagf(ctx, "create: lz4-block-v1 compression failed");
    605           goto fail;
    606         }
    607         r.stored_size = stored_len;
    608         pkg_hash(r.stored_hash, tmp, stored_len);
    609         if (embed_content &&
    610             kit_writer_write(content, tmp, stored_len) != KIT_OK)
    611           goto fail;
    612         if (!embed_content) {
    613           char rel[PKG_PATH_BUF];
    614           if (pkg_external_chunk_path(rel, sizeof rel, blob->id,
    615                                       r.chunk_index) != DIST_OK ||
    616               pkg_write_external_file(host, ctx, external_dir, rel, tmp,
    617                                       stored_len) != DIST_OK)
    618             goto fail;
    619         }
    620       }
    621       dist_kpkg3_encode_index_record(recbuf, &r);
    622       if (kit_writer_write(index, recbuf, sizeof recbuf) != KIT_OK) goto fail;
    623       off += raw_len;
    624       ++ci;
    625     }
    626   }
    627   *index_out = index;
    628   *content_out = content;
    629   return DIST_OK;
    630 fail:
    631   if (content) kit_writer_close(content);
    632   if (index) kit_writer_close(index);
    633   return DIST_ERR;
    634 }
    635 
    636 static int pkg_create_kpkg(const KitCasHost* host, const KitContext* ctx,
    637                            const char* out, const DistKeypair* kp,
    638                            const PkgSource* src, const uint8_t* man,
    639                            size_t man_len, const uint8_t* sig, size_t sig_len,
    640                            const uint8_t* pub, size_t pub_len,
    641                            const uint8_t pkgid[DIST_BLAKE2B_LEN],
    642                            uint32_t compression, PkgNativeShape shape,
    643                            const char* external_dir) {
    644   KitWriter *index = NULL, *content = NULL, *descw = NULL, *descsigw = NULL,
    645             *pkg = NULL;
    646   const uint8_t *index_b, *content_b, *desc_b = NULL, *descsig_b = NULL;
    647   size_t index_l, content_l, desc_l = 0, descsig_l = 0;
    648   uint8_t tree_root[DIST_BLAKE2B_LEN], index_root[DIST_BLAKE2B_LEN],
    649       content_root[DIST_BLAKE2B_LEN];
    650   DistKpkg3Header h;
    651   uint64_t tree_offset = 0, index_offset = 0, content_offset = 0;
    652   int embed_tree = shape != PKG_NATIVE_THIN;
    653   int embed_index = shape != PKG_NATIVE_THIN;
    654   int embed_content = shape == PKG_NATIVE_FAT;
    655   int stable = 0, iter, rc = DIST_ERR;
    656   char tree_url[PKG_PATH_BUF], index_url[PKG_PATH_BUF];
    657 
    658   tree_url[0] = '\0';
    659   index_url[0] = '\0';
    660   if (shape != PKG_NATIVE_FAT && !external_dir) {
    661     kit_ctx_diagf(
    662         ctx, "create: --external DIR is required for non-fat native packages");
    663     goto done;
    664   }
    665   if (pkg_external_id_path(tree_url, sizeof tree_url, "tree", src->tree_id) !=
    666       DIST_OK)
    667     goto done;
    668 
    669   if (pkg_build_native_regions(host, ctx, src, compression, external_dir,
    670                                embed_content, &index, &content) != DIST_OK)
    671     goto done;
    672   index_b = kit_writer_mem_bytes(index, &index_l);
    673   content_b = kit_writer_mem_bytes(content, &content_l);
    674   dist_kpkg3_region_root(tree_root, "tree", embed_tree ? src->tree_bytes : NULL,
    675                          embed_tree ? src->tree_size : 0);
    676   dist_kpkg3_region_root(index_root, "index", index_b, index_l);
    677   dist_kpkg3_region_root(content_root, "content",
    678                          embed_content ? content_b : NULL,
    679                          embed_content ? content_l : 0);
    680   if (!embed_tree &&
    681       pkg_write_external_file(host, ctx, external_dir, tree_url,
    682                               src->tree_bytes, src->tree_size) != DIST_OK)
    683     goto done;
    684   if (!embed_index) {
    685     if (pkg_external_id_path(index_url, sizeof index_url, "index",
    686                              index_root) != DIST_OK ||
    687         pkg_write_external_file(host, ctx, external_dir, index_url, index_b,
    688                                 index_l) != DIST_OK)
    689       goto done;
    690   }
    691 
    692   memset(&h, 0, sizeof h);
    693   for (iter = 0; iter < 8; ++iter) {
    694     DistKpkg3Descriptor d;
    695     uint64_t old_desc_l = desc_l, old_descsig_l = descsig_l;
    696     if (descw) kit_writer_close(descw);
    697     if (descsigw) kit_writer_close(descsigw);
    698     descw = pkg_mem(ctx);
    699     descsigw = pkg_mem(ctx);
    700     if (!descw || !descsigw) goto done;
    701     h.manifest_offset = DIST_KPKG3_HEADER_SIZE;
    702     h.manifest_size = man_len;
    703     h.signature_offset = h.manifest_offset + h.manifest_size;
    704     h.signature_size = sig_len;
    705     h.descriptor_offset = h.signature_offset + h.signature_size;
    706     h.descriptor_size = old_desc_l;
    707     h.descriptor_signature_offset = h.descriptor_offset + h.descriptor_size;
    708     h.descriptor_signature_size = old_descsig_l;
    709     h.pubkey_offset =
    710         h.descriptor_signature_offset + h.descriptor_signature_size;
    711     h.pubkey_size = pub_len;
    712     tree_offset = embed_tree ? pkg_align_up(h.pubkey_offset + h.pubkey_size,
    713                                             DIST_KPKG3_ALIGNMENT)
    714                              : 0;
    715     index_offset =
    716         embed_index
    717             ? pkg_align_up((embed_tree ? tree_offset + src->tree_size
    718                                        : h.pubkey_offset + h.pubkey_size),
    719                            DIST_KPKG3_ALIGNMENT)
    720             : 0;
    721     content_offset =
    722         embed_content
    723             ? pkg_align_up(
    724                   (embed_index
    725                        ? index_offset + index_l
    726                        : (embed_tree ? tree_offset + src->tree_size
    727                                      : h.pubkey_offset + h.pubkey_size)),
    728                   DIST_KPKG3_ALIGNMENT)
    729             : 0;
    730 
    731     memset(&d, 0, sizeof d);
    732     memcpy(d.package_id, pkgid, DIST_BLAKE2B_LEN);
    733     d.chunk_size = DIST_KPKG3_CHUNK_SIZE_DEFAULT;
    734     d.alignment = DIST_KPKG3_ALIGNMENT;
    735     d.tree_offset = tree_offset;
    736     d.tree_size = embed_tree ? src->tree_size : 0;
    737     memcpy(d.tree_root, tree_root, DIST_BLAKE2B_LEN);
    738     d.index_offset = index_offset;
    739     d.index_size = embed_index ? index_l : 0;
    740     d.index_bytes = index_l;
    741     memcpy(d.index_root, index_root, DIST_BLAKE2B_LEN);
    742     if (!embed_index)
    743       snprintf(d.index_url, sizeof d.index_url, "%s", index_url);
    744     d.content_offset = content_offset;
    745     d.content_size = embed_content ? content_l : 0;
    746     memcpy(d.content_root, content_root, DIST_BLAKE2B_LEN);
    747     d.n_trees = 1;
    748     memcpy(d.trees[0].tree, src->tree_id, DIST_BLAKE2B_LEN);
    749     if (embed_tree) {
    750       d.trees[0].offset = 0;
    751       d.trees[0].size = src->tree_size;
    752       d.trees[0].embedded = 1;
    753     } else {
    754       snprintf(d.trees[0].url, sizeof d.trees[0].url, "%s", tree_url);
    755     }
    756     memcpy(d.trees[0].blake2b, src->tree_id, DIST_BLAKE2B_LEN);
    757     d.n_chunk_sources = 1;
    758     if (embed_content) {
    759       d.chunk_sources[0].kind = DIST_KPKG3_CHUNK_SOURCE_EMBEDDED;
    760     } else {
    761       d.chunk_sources[0].kind = DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE;
    762       snprintf(d.chunk_sources[0].tmpl, sizeof d.chunk_sources[0].tmpl, "%s",
    763                "chunk/{blob-prefix}/{blob}/{chunk}");
    764     }
    765     if (dist_kpkg3_descriptor_emit(descw, &d) != DIST_OK) goto done;
    766     desc_b = kit_writer_mem_bytes(descw, &desc_l);
    767     if (pkg_sign(descsigw, ctx, desc_b, desc_l, kp, pkgid,
    768                  "kit kpkg encoding descriptor") != DIST_OK)
    769       goto done;
    770     descsig_b = kit_writer_mem_bytes(descsigw, &descsig_l);
    771     if (desc_l == old_desc_l && descsig_l == old_descsig_l) {
    772       stable = 1;
    773       break;
    774     }
    775   }
    776   if (!stable) goto done;
    777 
    778   pkg = pkg_mem(ctx);
    779   if (!pkg) goto done;
    780   if (dist_kpkg3_write_header(pkg, &h) != DIST_OK ||
    781       kit_writer_write(pkg, man, man_len) != KIT_OK ||
    782       kit_writer_write(pkg, sig, sig_len) != KIT_OK ||
    783       kit_writer_write(pkg, desc_b, desc_l) != KIT_OK ||
    784       kit_writer_write(pkg, descsig_b, descsig_l) != KIT_OK ||
    785       kit_writer_write(pkg, pub, pub_len) != KIT_OK ||
    786       (embed_tree &&
    787        (pkg_write_pad(pkg, tree_offset) != DIST_OK ||
    788         kit_writer_write(pkg, src->tree_bytes, src->tree_size) != KIT_OK)) ||
    789       (embed_index && (pkg_write_pad(pkg, index_offset) != DIST_OK ||
    790                        kit_writer_write(pkg, index_b, index_l) != KIT_OK)) ||
    791       (embed_content &&
    792        (pkg_write_pad(pkg, content_offset) != DIST_OK ||
    793         kit_writer_write(pkg, content_b, content_l) != KIT_OK)))
    794     goto done;
    795   {
    796     const uint8_t* bytes;
    797     size_t len;
    798     bytes = kit_writer_mem_bytes(pkg, &len);
    799     rc = pkg_write_file(ctx, out, bytes, len);
    800   }
    801 
    802 done:
    803   if (pkg) kit_writer_close(pkg);
    804   if (descsigw) kit_writer_close(descsigw);
    805   if (descw) kit_writer_close(descw);
    806   if (content) kit_writer_close(content);
    807   if (index) kit_writer_close(index);
    808   return rc;
    809 }
    810 
    811 KitStatus kit_pkg_create(const KitContext* ctx, const KitCasHost* host,
    812                          const KitPkgCreateOptions* opts,
    813                          KitPkgCreateResult* result) {
    814   DistKeypair kp;
    815   DistPackageManifest m;
    816   PkgSource src;
    817   KitWriter *manw = NULL, *sigw = NULL, *pubw = NULL;
    818   const uint8_t *man_b, *sig_b, *pub_b;
    819   size_t man_l, sig_l, pub_l;
    820   uint8_t pkgid[DIST_BLAKE2B_LEN];
    821   int rc = DIST_ERR;
    822   if (!ctx || !host || !opts || !result || !opts->sk || !opts->keyid ||
    823       !opts->out_path)
    824     return KIT_INVALID;
    825 
    826   memset(&kp, 0, sizeof kp);
    827   memcpy(kp.sk, opts->sk, DIST_ED25519_SK_LEN);
    828   memcpy(kp.keyid, opts->keyid, DIST_KEYID_LEN);
    829   memcpy(kp.pk, kp.sk + DIST_ED25519_SEED_LEN, DIST_ED25519_PK_LEN);
    830 
    831   pkg_source_init(&src, ctx, host);
    832   if (opts->root_dir) {
    833     if (pkg_source_from_root(&src, opts->root_dir) != DIST_OK) goto done;
    834   } else {
    835     if (pkg_source_from_cas(&src, opts->cas_dir, opts->tree_id) != DIST_OK)
    836       goto done;
    837   }
    838   if (pkg_manifest_from_source(opts->name, opts->version, opts->description,
    839                                &src, &m) != DIST_OK) {
    840     kit_ctx_diagf(ctx, "create: failed to build package manifest");
    841     goto done;
    842   }
    843 
    844   manw = pkg_mem(ctx);
    845   sigw = pkg_mem(ctx);
    846   pubw = pkg_mem(ctx);
    847   if (!manw || !sigw || !pubw) goto done;
    848   if (dist_package_manifest_emit(&m, manw) != DIST_OK) goto done;
    849   man_b = kit_writer_mem_bytes(manw, &man_l);
    850   pkg_hash(pkgid, man_b, man_l);
    851   if (pkg_sign(sigw, ctx, man_b, man_l, &kp, pkgid, "signature from kit pkg") !=
    852       DIST_OK)
    853     goto done;
    854   sig_b = kit_writer_mem_bytes(sigw, &sig_l);
    855   if (dist_minisig_emit_pubkey(pubw, &kp) != DIST_OK) goto done;
    856   pub_b = kit_writer_mem_bytes(pubw, &pub_l);
    857 
    858   if (opts->format == KIT_PKG_FORMAT_TARGZ)
    859     rc = pkg_create_targz(ctx, opts->out_path, &src, man_b, man_l, sig_b, sig_l,
    860                           pub_b, pub_l);
    861   else
    862     rc = pkg_create_kpkg(
    863         host, ctx, opts->out_path, &kp, &src, man_b, man_l, sig_b, sig_l, pub_b,
    864         pub_l, pkgid, (uint32_t)opts->compression,
    865         (PkgNativeShape)opts->native_shape, opts->external_dir);
    866   if (rc == DIST_OK) {
    867     result->n_files = src.tree.n_entries;
    868     memcpy(result->package_id, pkgid, DIST_BLAKE2B_LEN);
    869   }
    870 
    871 done:
    872   if (pubw) kit_writer_close(pubw);
    873   if (sigw) kit_writer_close(sigw);
    874   if (manw) kit_writer_close(manw);
    875   pkg_source_release(&src);
    876   return rc == DIST_OK ? KIT_OK : KIT_ERR;
    877 }
    878 
    879 /* ---------------------------------------------------------------------- */
    880 /* key resolution + manifest verification                                 */
    881 /* ---------------------------------------------------------------------- */
    882 
    883 static int pkg_resolve_key(const KitContext* ctx,
    884                            const uint8_t keyid[DIST_KEYID_LEN],
    885                            const uint8_t* bundled_pub, size_t bundled_pub_size,
    886                            const KitPkgVerifyOptions* opts,
    887                            uint8_t pk[DIST_ED25519_PK_LEN], int* tofu_pin) {
    888   uint8_t kid_chk[DIST_KEYID_LEN];
    889   *tofu_pin = 0;
    890   if (opts->pubkey_bytes) {
    891     if (dist_minisig_parse_pubkey(opts->pubkey_bytes, opts->pubkey_len, pk,
    892                                   kid_chk) != DIST_OK ||
    893         memcmp(kid_chk, keyid, DIST_KEYID_LEN) != 0) {
    894       kit_ctx_diagf(ctx, "public key id does not match signature");
    895       return DIST_ERR;
    896     }
    897     return DIST_OK;
    898   }
    899   if (opts->trusted_keys &&
    900       dist_trust_lookup(opts->trusted_keys, opts->trusted_keys_len, keyid,
    901                         pk) == DIST_OK)
    902     return DIST_OK;
    903   if (!opts->tofu) {
    904     char hex[2 * DIST_KEYID_LEN + 1];
    905     dist_hex_encode(hex, keyid, DIST_KEYID_LEN);
    906     kit_ctx_diagf(ctx, "untrusted signer (key id %s)", hex);
    907     return DIST_ERR;
    908   }
    909   if (!bundled_pub || bundled_pub_size == 0 ||
    910       dist_minisig_parse_pubkey(bundled_pub, bundled_pub_size, pk, kid_chk) !=
    911           DIST_OK ||
    912       memcmp(kid_chk, keyid, DIST_KEYID_LEN) != 0) {
    913     kit_ctx_diagf(ctx, "--tofu: bundled public key is missing or mismatched");
    914     return DIST_ERR;
    915   }
    916   *tofu_pin = 1;
    917   return DIST_OK;
    918 }
    919 
    920 static int pkg_verify_manifest(const KitContext* ctx, const uint8_t* man,
    921                                size_t man_len, const uint8_t* sig,
    922                                size_t sig_len, const uint8_t* pub,
    923                                size_t pub_len, const KitPkgVerifyOptions* opts,
    924                                PkgVerified* out) {
    925   char err[128], pkgid_hex[2 * DIST_BLAKE2B_LEN + 1];
    926   const char* pidp;
    927   memset(out, 0, sizeof *out);
    928   if (dist_minisig_sig_keyid(sig, sig_len, out->keyid) != DIST_OK) {
    929     kit_ctx_diagf(ctx, "malformed signature");
    930     return DIST_ERR;
    931   }
    932   if (pkg_resolve_key(ctx, out->keyid, pub, pub_len, opts, out->pk,
    933                       &out->tofu_pin) != DIST_OK)
    934     return DIST_ERR;
    935   if (dist_minisig_verify(sig, sig_len, man, man_len, out->pk, out->trusted,
    936                           sizeof out->trusted) != DIST_OK) {
    937     kit_ctx_diagf(ctx, "signature verification FAILED");
    938     return DIST_ERR;
    939   }
    940   pkg_hash(out->package_id, man, man_len);
    941   dist_hex_encode(pkgid_hex, out->package_id, DIST_BLAKE2B_LEN);
    942   pidp = strstr(out->trusted, "pkgid=");
    943   if (!pidp || strncmp(pidp + 6, pkgid_hex, 2 * DIST_BLAKE2B_LEN) != 0) {
    944     kit_ctx_diagf(ctx, "trusted comment does not match package id");
    945     return DIST_ERR;
    946   }
    947   if (dist_package_manifest_parse(man, man_len, &out->manifest, err,
    948                                   sizeof err) != DIST_OK) {
    949     kit_ctx_diagf(ctx, "manifest: %s", err);
    950     return DIST_ERR;
    951   }
    952   return DIST_OK;
    953 }
    954 
    955 static const DistPackageOutput* pkg_default_output(
    956     const DistPackageManifest* m) {
    957   size_t i;
    958   for (i = 0; i < m->n_outputs; ++i)
    959     if (m->outputs[i].is_default) return &m->outputs[i];
    960   return m->n_outputs ? &m->outputs[0] : NULL;
    961 }
    962 
    963 static int pkg_parse_tree_object(const KitContext* ctx, PkgLoadedTree* out,
    964                                  const uint8_t id[DIST_BLAKE2B_LEN],
    965                                  const uint8_t* data, size_t len,
    966                                  const char* label) {
    967   uint8_t got[DIST_BLAKE2B_LEN];
    968   char err[128];
    969   memset(out, 0, sizeof *out);
    970   dist_tree_id(got, data, len);
    971   if (memcmp(got, id, DIST_BLAKE2B_LEN) != 0) {
    972     kit_ctx_diagf(ctx, "tree id mismatch: %s", label);
    973     return DIST_ERR;
    974   }
    975   out->tree.entries = out->entries;
    976   out->tree.cap_entries = DIST_MAX_FILES;
    977   if (dist_tree_parse(data, len, &out->tree, err, sizeof err) != DIST_OK) {
    978     kit_ctx_diagf(ctx, "tree: %s", err);
    979     return DIST_ERR;
    980   }
    981   memcpy(out->id, id, DIST_BLAKE2B_LEN);
    982   out->bytes = data;
    983   out->size = len;
    984   return DIST_OK;
    985 }
    986 
    987 static int pkg_verify_artifact_overlays(const KitContext* ctx,
    988                                         const DistPackageManifest* m,
    989                                         const DistPackageOutput* out,
    990                                         const DistTree* tree) {
    991   size_t i;
    992   for (i = 0; i < m->n_artifacts; ++i) {
    993     const DistPackageArtifact* a = &m->artifacts[i];
    994     if (a->output_id != out->id) continue;
    995     if (!dist_tree_find(tree, a->path)) {
    996       kit_ctx_diagf(ctx, "artifact path not in output tree: %s", a->path);
    997       return DIST_ERR;
    998     }
    999   }
   1000   return DIST_OK;
   1001 }
   1002 
   1003 static int pkg_write_output_file(const KitCasHost* host, const KitContext* ctx,
   1004                                  const char* out_dir, const DistTreeEntry* e,
   1005                                  const uint8_t* data, size_t len) {
   1006   char full[PKG_PATH_BUF], parent[PKG_PATH_BUF];
   1007   if (pkg_join_path(full, sizeof full, out_dir, e->path) != DIST_OK) {
   1008     kit_ctx_diagf(ctx, "output path too long: %s", e->path);
   1009     return DIST_ERR;
   1010   }
   1011   pkg_parent_dir(full, parent, sizeof parent);
   1012   if (parent[0] && host->mkdir_p(host->user, parent) != 0) return DIST_ERR;
   1013   if (pkg_write_file(ctx, full, data, len) != DIST_OK) return DIST_ERR;
   1014   if (e->mode == DIST_TREE_MODE_EXEC &&
   1015       host->mark_executable(host->user, full) != 0)
   1016     return DIST_ERR;
   1017   return DIST_OK;
   1018 }
   1019 
   1020 /* ---------------------------------------------------------------------- */
   1021 /* portable (.tar.gz) verify / unpack                                     */
   1022 /* ---------------------------------------------------------------------- */
   1023 
   1024 static const DistTarEntry* pkg_portable_find_cas(
   1025     const DistTarEntry* entries, size_t ne, const char* kind,
   1026     const uint8_t id[DIST_BLAKE2B_LEN]) {
   1027   char path[PKG_PATH_BUF];
   1028   if (pkg_cas_rel_path(path, sizeof path, kind, id) != DIST_OK) return NULL;
   1029   return pkg_find_name(entries, ne, path);
   1030 }
   1031 
   1032 static int pkg_verify_blob_bytes(const DistTreeEntry* e, const uint8_t* data,
   1033                                  size_t len) {
   1034   DistBlobInfo bi;
   1035   if (dist_blob_info(&bi, data, len, DIST_BLOB_CHUNK_SIZE_DEFAULT) != DIST_OK)
   1036     return DIST_ERR;
   1037   return bi.size == e->size && memcmp(bi.id, e->blob, DIST_BLAKE2B_LEN) == 0 &&
   1038                  memcmp(bi.root, e->root, DIST_BLAKE2B_LEN) == 0
   1039              ? DIST_OK
   1040              : DIST_ERR;
   1041 }
   1042 
   1043 static int pkg_verify_portable_tree(const KitCasHost* host,
   1044                                     const KitContext* ctx, const PkgVerified* v,
   1045                                     const DistPackageOutput* out,
   1046                                     const DistTarEntry* entries, size_t ne,
   1047                                     const char* out_dir) {
   1048   const DistTarEntry* te =
   1049       pkg_portable_find_cas(entries, ne, "tree", out->tree);
   1050   PkgLoadedTree tree;
   1051   size_t i;
   1052   if (!te) {
   1053     kit_ctx_diagf(ctx, "portable package missing tree object");
   1054     return DIST_ERR;
   1055   }
   1056   if (pkg_parse_tree_object(ctx, &tree, out->tree, te->data, te->size,
   1057                             out->name) != DIST_OK)
   1058     return DIST_ERR;
   1059   if (pkg_verify_artifact_overlays(ctx, &v->manifest, out, &tree.tree) !=
   1060       DIST_OK)
   1061     return DIST_ERR;
   1062   for (i = 0; i < tree.tree.n_entries; ++i) {
   1063     const DistTreeEntry* e = &tree.tree.entries[i];
   1064     const DistTarEntry* be =
   1065         pkg_portable_find_cas(entries, ne, "blob", e->blob);
   1066     if (!be) {
   1067       kit_ctx_diagf(ctx, "portable package missing blob: %s", e->path);
   1068       return DIST_ERR;
   1069     }
   1070     if (pkg_verify_blob_bytes(e, be->data, be->size) != DIST_OK) {
   1071       kit_ctx_diagf(ctx, "blob hash mismatch: %s", e->path);
   1072       return DIST_ERR;
   1073     }
   1074     if (out_dir && pkg_write_output_file(host, ctx, out_dir, e, be->data,
   1075                                          be->size) != DIST_OK)
   1076       return DIST_ERR;
   1077   }
   1078   return DIST_OK;
   1079 }
   1080 
   1081 static int pkg_load_portable(const KitContext* ctx, const uint8_t* data,
   1082                              size_t len, KitWriter** inflated_out,
   1083                              DistTarEntry* entries, size_t* ne) {
   1084   KitWriter* inflated = NULL;
   1085   const uint8_t* bytes;
   1086   size_t ilen;
   1087   inflated = pkg_mem(ctx);
   1088   if (!inflated || dist_gz_decompress(inflated, data, len) != DIST_OK) {
   1089     kit_ctx_diagf(ctx, "malformed portable package");
   1090     if (inflated) kit_writer_close(inflated);
   1091     return DIST_ERR;
   1092   }
   1093   bytes = kit_writer_mem_bytes(inflated, &ilen);
   1094   if (dist_tar_iter(bytes, ilen, entries, PKG_MAX_TAR_ENTRIES, ne) != DIST_OK) {
   1095     kit_ctx_diagf(ctx, "malformed portable tar");
   1096     kit_writer_close(inflated);
   1097     return DIST_ERR;
   1098   }
   1099   *inflated_out = inflated;
   1100   return DIST_OK;
   1101 }
   1102 
   1103 static int pkg_verify_portable(const KitContext* ctx, const KitCasHost* host,
   1104                                const KitPkgVerifyOptions* opts,
   1105                                PkgVerified* v) {
   1106   KitWriter* inflated = NULL;
   1107   DistTarEntry entries[PKG_MAX_TAR_ENTRIES];
   1108   size_t ne = 0, oi;
   1109   const DistTarEntry *man, *sig, *pub;
   1110   const DistPackageOutput* def;
   1111   int rc = DIST_ERR;
   1112   if (pkg_load_portable(ctx, opts->pkg_data, opts->pkg_len, &inflated, entries,
   1113                         &ne) != DIST_OK)
   1114     return DIST_ERR;
   1115   man = pkg_find_name(entries, ne, PKG_META_MANIFEST);
   1116   sig = pkg_find_name(entries, ne, PKG_META_SIG);
   1117   pub = pkg_find_name(entries, ne, PKG_META_PUB);
   1118   if (!man || !sig) {
   1119     kit_ctx_diagf(ctx, "package missing manifest or signature");
   1120     goto done;
   1121   }
   1122   if (pkg_verify_manifest(ctx, man->data, man->size, sig->data, sig->size,
   1123                           pub ? pub->data : NULL, pub ? pub->size : 0, opts,
   1124                           v) != DIST_OK)
   1125     goto done;
   1126   def = pkg_default_output(&v->manifest);
   1127   if (!def) goto done;
   1128   for (oi = 0; oi < v->manifest.n_outputs; ++oi) {
   1129     const DistPackageOutput* out = &v->manifest.outputs[oi];
   1130     if (pkg_verify_portable_tree(host, ctx, v, out, entries, ne,
   1131                                  out == def ? opts->unpack_dir : NULL) !=
   1132         DIST_OK)
   1133       goto done;
   1134   }
   1135   rc = DIST_OK;
   1136 done:
   1137   if (inflated) kit_writer_close(inflated);
   1138   return rc;
   1139 }
   1140 
   1141 /* ---------------------------------------------------------------------- */
   1142 /* native (.kpkg) verify / unpack                                        */
   1143 /* ---------------------------------------------------------------------- */
   1144 
   1145 static int pkg_bounds3(const DistKpkg3Header* h, size_t len) {
   1146   uint64_t ranges[][2] = {
   1147       {h->manifest_offset, h->manifest_size},
   1148       {h->signature_offset, h->signature_size},
   1149       {h->descriptor_offset, h->descriptor_size},
   1150       {h->descriptor_signature_offset, h->descriptor_signature_size},
   1151       {h->pubkey_offset, h->pubkey_size}};
   1152   size_t i;
   1153   for (i = 0; i < sizeof ranges / sizeof ranges[0]; ++i)
   1154     if (ranges[i][0] > len || ranges[i][1] > len - ranges[i][0])
   1155       return DIST_ERR;
   1156   return DIST_OK;
   1157 }
   1158 
   1159 static int pkg_range_ok(uint64_t off, uint64_t size, size_t len) {
   1160   return off <= len && size <= len - off;
   1161 }
   1162 
   1163 static const DistKpkg3TreeObject* pkg_descriptor_find_tree(
   1164     const DistKpkg3Descriptor* d, const uint8_t id[DIST_BLAKE2B_LEN]) {
   1165   size_t i;
   1166   for (i = 0; i < d->n_trees; ++i)
   1167     if (memcmp(d->trees[i].tree, id, DIST_BLAKE2B_LEN) == 0)
   1168       return &d->trees[i];
   1169   return NULL;
   1170 }
   1171 
   1172 static int pkg_descriptor_has_embedded_chunks(const DistKpkg3Descriptor* d) {
   1173   size_t i;
   1174   for (i = 0; i < d->n_chunk_sources; ++i)
   1175     if (d->chunk_sources[i].kind == DIST_KPKG3_CHUNK_SOURCE_EMBEDDED) return 1;
   1176   return 0;
   1177 }
   1178 
   1179 static const char* pkg_descriptor_chunk_template(const DistKpkg3Descriptor* d) {
   1180   size_t i;
   1181   for (i = 0; i < d->n_chunk_sources; ++i)
   1182     if (d->chunk_sources[i].kind == DIST_KPKG3_CHUNK_SOURCE_URL_TEMPLATE)
   1183       return d->chunk_sources[i].tmpl;
   1184   return NULL;
   1185 }
   1186 
   1187 static int pkg_render_chunk_template(char* out, size_t cap, const char* tmpl,
   1188                                      const uint8_t blob[DIST_BLAKE2B_LEN],
   1189                                      uint64_t chunk_index) {
   1190   char blob_hex[2 * DIST_BLAKE2B_LEN + 1];
   1191   char blob_prefix[3];
   1192   char chunk_dec[24];
   1193   size_t oi = 0, i;
   1194   dist_hex_encode(blob_hex, blob, DIST_BLAKE2B_LEN);
   1195   blob_prefix[0] = blob_hex[0];
   1196   blob_prefix[1] = blob_hex[1];
   1197   blob_prefix[2] = '\0';
   1198   snprintf(chunk_dec, sizeof chunk_dec, "%llu",
   1199            (unsigned long long)chunk_index);
   1200   for (i = 0; tmpl[i];) {
   1201     const char* repl = NULL;
   1202     size_t repl_len = 0;
   1203     if (strncmp(tmpl + i, "{blob}", 6) == 0) {
   1204       repl = blob_hex;
   1205       repl_len = strlen(blob_hex);
   1206       i += 6;
   1207     } else if (strncmp(tmpl + i, "{blob-prefix}", 13) == 0) {
   1208       repl = blob_prefix;
   1209       repl_len = strlen(blob_prefix);
   1210       i += 13;
   1211     } else if (strncmp(tmpl + i, "{chunk}", 7) == 0) {
   1212       repl = chunk_dec;
   1213       repl_len = strlen(chunk_dec);
   1214       i += 7;
   1215     } else {
   1216       if (oi + 1u >= cap) return DIST_ERR;
   1217       out[oi++] = tmpl[i++];
   1218       continue;
   1219     }
   1220     if (oi + repl_len >= cap) return DIST_ERR;
   1221     memcpy(out + oi, repl, repl_len);
   1222     oi += repl_len;
   1223   }
   1224   if (oi >= cap) return DIST_ERR;
   1225   out[oi] = '\0';
   1226   return dist_tree_path_valid(out) ? DIST_OK : DIST_ERR;
   1227 }
   1228 
   1229 static int pkg_verify_native_index_sorted(const uint8_t* index_b,
   1230                                           size_t index_l,
   1231                                           const DistKpkg3Descriptor* d) {
   1232   DistKpkg3IndexRecord prev;
   1233   size_t off;
   1234   int have_prev = 0;
   1235   int embedded_chunks = pkg_descriptor_has_embedded_chunks(d);
   1236   if (index_l != d->index_bytes || index_l % DIST_KPKG3_INDEX_RECORD_SIZE != 0)
   1237     return DIST_ERR;
   1238   memset(&prev, 0, sizeof prev);
   1239   for (off = 0; off < index_l; off += DIST_KPKG3_INDEX_RECORD_SIZE) {
   1240     DistKpkg3IndexRecord r;
   1241     int cmp;
   1242     if (dist_kpkg3_decode_index_record(
   1243             index_b + off, DIST_KPKG3_INDEX_RECORD_SIZE, &r) != DIST_OK)
   1244       return DIST_ERR;
   1245     if (r.raw_size == 0 || r.raw_size > d->chunk_size ||
   1246         !dist_kpkg_compression_name(r.compression))
   1247       return DIST_ERR;
   1248     if (embedded_chunks) {
   1249       if (r.content_offset > d->content_size ||
   1250           r.stored_size > d->content_size - r.content_offset)
   1251         return DIST_ERR;
   1252     } else if (r.content_offset != 0) {
   1253       return DIST_ERR;
   1254     }
   1255     if (!have_prev) {
   1256       if (r.chunk_index != 0) return DIST_ERR;
   1257     } else {
   1258       cmp = memcmp(prev.blob_id, r.blob_id, DIST_BLAKE2B_LEN);
   1259       if (cmp > 0) return DIST_ERR;
   1260       if (cmp == 0) {
   1261         if (r.chunk_index <= prev.chunk_index) return DIST_ERR;
   1262       } else if (r.chunk_index != 0) {
   1263         return DIST_ERR;
   1264       }
   1265     }
   1266     prev = r;
   1267     have_prev = 1;
   1268   }
   1269   return DIST_OK;
   1270 }
   1271 
   1272 static int pkg_native_load_tree(const KitContext* ctx, const uint8_t* data,
   1273                                 size_t len, const DistKpkg3Descriptor* d,
   1274                                 const DistPackageOutput* out,
   1275                                 const char* external_dir, PkgLoadedTree* tree) {
   1276   const DistKpkg3TreeObject* obj = pkg_descriptor_find_tree(d, out->tree);
   1277   const uint8_t* bytes;
   1278   uint8_t h[DIST_BLAKE2B_LEN];
   1279   (void)len;
   1280   if (!obj || !obj->embedded) {
   1281     KitFileData fd;
   1282     char rel[PKG_PATH_BUF];
   1283     int rc;
   1284     if (!obj || !external_dir) {
   1285       kit_ctx_diagf(ctx, "external tree object is missing");
   1286       return DIST_ERR;
   1287     }
   1288     if (obj->url[0])
   1289       snprintf(rel, sizeof rel, "%s", obj->url);
   1290     else if (pkg_external_id_path(rel, sizeof rel, "tree", out->tree) !=
   1291              DIST_OK)
   1292       return DIST_ERR;
   1293     fd.data = NULL;
   1294     fd.size = 0;
   1295     fd.token = NULL;
   1296     if (pkg_read_external_file(ctx, external_dir, rel, &fd) != DIST_OK) {
   1297       kit_ctx_diagf(ctx, "missing external tree object: %s", rel);
   1298       return DIST_ERR;
   1299     }
   1300     pkg_hash(h, fd.data, fd.size);
   1301     if (memcmp(h, obj->blake2b, DIST_BLAKE2B_LEN) != 0 ||
   1302         memcmp(h, out->tree, DIST_BLAKE2B_LEN) != 0) {
   1303       ctx->file_io->release(ctx->file_io->user, &fd);
   1304       kit_ctx_diagf(ctx, "tree object hash mismatch");
   1305       return DIST_ERR;
   1306     }
   1307     rc = pkg_parse_tree_object(ctx, tree, out->tree, fd.data, fd.size,
   1308                                out->name);
   1309     ctx->file_io->release(ctx->file_io->user, &fd);
   1310     tree->bytes = NULL;
   1311     tree->size = 0;
   1312     return rc;
   1313   }
   1314   if (obj->offset > d->tree_size || obj->size > d->tree_size - obj->offset)
   1315     return DIST_ERR;
   1316   bytes = data + d->tree_offset + obj->offset;
   1317   pkg_hash(h, bytes, (size_t)obj->size);
   1318   if (memcmp(h, obj->blake2b, DIST_BLAKE2B_LEN) != 0 ||
   1319       memcmp(h, out->tree, DIST_BLAKE2B_LEN) != 0) {
   1320     kit_ctx_diagf(ctx, "tree object hash mismatch");
   1321     return DIST_ERR;
   1322   }
   1323   return pkg_parse_tree_object(ctx, tree, out->tree, bytes, (size_t)obj->size,
   1324                                out->name);
   1325 }
   1326 
   1327 static int pkg_native_load_index(const KitContext* ctx, const uint8_t* data,
   1328                                  const DistKpkg3Descriptor* d,
   1329                                  const char* external_dir, KitFileData* fd,
   1330                                  const uint8_t** index_b, size_t* index_l) {
   1331   uint8_t root[DIST_BLAKE2B_LEN];
   1332   fd->data = NULL;
   1333   fd->size = 0;
   1334   fd->token = NULL;
   1335   if (d->index_size != 0) {
   1336     if (d->index_size != d->index_bytes) return DIST_ERR;
   1337     *index_b = data + d->index_offset;
   1338     *index_l = (size_t)d->index_size;
   1339   } else {
   1340     char rel[PKG_PATH_BUF];
   1341     if (!external_dir) {
   1342       kit_ctx_diagf(ctx, "external index is missing");
   1343       return DIST_ERR;
   1344     }
   1345     if (d->index_url[0])
   1346       snprintf(rel, sizeof rel, "%s", d->index_url);
   1347     else if (pkg_external_id_path(rel, sizeof rel, "index", d->index_root) !=
   1348              DIST_OK)
   1349       return DIST_ERR;
   1350     if (pkg_read_external_file(ctx, external_dir, rel, fd) != DIST_OK) {
   1351       kit_ctx_diagf(ctx, "missing external index: %s", rel);
   1352       return DIST_ERR;
   1353     }
   1354     *index_b = fd->data;
   1355     *index_l = fd->size;
   1356   }
   1357   if (*index_l != d->index_bytes) return DIST_ERR;
   1358   dist_kpkg3_region_root(root, "index", *index_b, *index_l);
   1359   return memcmp(root, d->index_root, DIST_BLAKE2B_LEN) == 0 ? DIST_OK
   1360                                                             : DIST_ERR;
   1361 }
   1362 
   1363 static int pkg_decode_native_chunk(KitWriter* raww, const uint8_t* stored,
   1364                                    size_t stored_len,
   1365                                    const DistKpkg3Descriptor* d,
   1366                                    const DistKpkg3IndexRecord* r) {
   1367   uint8_t sh[DIST_BLAKE2B_LEN], rh[DIST_BLAKE2B_LEN], leaf[DIST_BLAKE2B_LEN];
   1368   if (r->raw_size == 0 || r->raw_size > d->chunk_size ||
   1369       r->stored_size != stored_len)
   1370     return DIST_ERR;
   1371   pkg_hash(sh, stored, (size_t)r->stored_size);
   1372   if (memcmp(sh, r->stored_hash, DIST_BLAKE2B_LEN) != 0) return DIST_ERR;
   1373   if (r->compression == DIST_KPKG_COMP_NONE) {
   1374     if (r->raw_size != r->stored_size) return DIST_ERR;
   1375     pkg_hash(rh, stored, (size_t)r->stored_size);
   1376     dist_blob_leaf_hash(leaf, r->chunk_index, stored, (size_t)r->stored_size);
   1377     if (memcmp(rh, r->raw_hash, DIST_BLAKE2B_LEN) != 0 ||
   1378         memcmp(leaf, r->leaf_hash, DIST_BLAKE2B_LEN) != 0 ||
   1379         kit_writer_write(raww, stored, (size_t)r->stored_size) != KIT_OK)
   1380       return DIST_ERR;
   1381   } else if (r->compression == DIST_KPKG_COMP_LZ4_BLOCK_V1) {
   1382     uint8_t tmp[DIST_KPKG3_CHUNK_SIZE_DEFAULT];
   1383     if (r->raw_size > sizeof tmp ||
   1384         dist_lz4_decompress_block(tmp, (size_t)r->raw_size, stored,
   1385                                   (size_t)r->stored_size) != DIST_OK)
   1386       return DIST_ERR;
   1387     pkg_hash(rh, tmp, (size_t)r->raw_size);
   1388     dist_blob_leaf_hash(leaf, r->chunk_index, tmp, (size_t)r->raw_size);
   1389     if (memcmp(rh, r->raw_hash, DIST_BLAKE2B_LEN) != 0 ||
   1390         memcmp(leaf, r->leaf_hash, DIST_BLAKE2B_LEN) != 0 ||
   1391         kit_writer_write(raww, tmp, (size_t)r->raw_size) != KIT_OK)
   1392       return DIST_ERR;
   1393   } else {
   1394     return DIST_ERR;
   1395   }
   1396   return DIST_OK;
   1397 }
   1398 
   1399 static int pkg_native_load_stored_chunk(
   1400     const KitContext* ctx, const uint8_t* data, const DistKpkg3Descriptor* d,
   1401     const DistKpkg3IndexRecord* r, const char* external_dir,
   1402     const char* chunk_template, KitFileData* fd, const uint8_t** stored,
   1403     size_t* stored_len) {
   1404   fd->data = NULL;
   1405   fd->size = 0;
   1406   fd->token = NULL;
   1407   if (pkg_descriptor_has_embedded_chunks(d)) {
   1408     if (r->content_offset > d->content_size ||
   1409         r->stored_size > d->content_size - r->content_offset)
   1410       return DIST_ERR;
   1411     *stored = data + d->content_offset + r->content_offset;
   1412     *stored_len = (size_t)r->stored_size;
   1413     return DIST_OK;
   1414   }
   1415   {
   1416     char rel[PKG_PATH_BUF];
   1417     if (!external_dir) return DIST_ERR;
   1418     if (chunk_template) {
   1419       if (pkg_render_chunk_template(rel, sizeof rel, chunk_template, r->blob_id,
   1420                                     r->chunk_index) != DIST_OK)
   1421         return DIST_ERR;
   1422     } else if (dist_cas_chunk_relpath(rel, sizeof rel, r->blob_id,
   1423                                       r->chunk_index) != DIST_OK) {
   1424       return DIST_ERR;
   1425     }
   1426     if (pkg_read_external_file(ctx, external_dir, rel, fd) != DIST_OK) {
   1427       kit_ctx_diagf(ctx, "missing external chunk: %s", rel);
   1428       return DIST_ERR;
   1429     }
   1430     *stored = fd->data;
   1431     *stored_len = fd->size;
   1432     return DIST_OK;
   1433   }
   1434 }
   1435 
   1436 static int pkg_native_reconstruct_blob(
   1437     const KitContext* ctx, const uint8_t* data, const uint8_t* index_b,
   1438     size_t index_l, const DistKpkg3Descriptor* d, const DistTreeEntry* e,
   1439     const char* external_dir, const char* chunk_template,
   1440     KitWriter** raww_out) {
   1441   KitWriter* raww = pkg_mem(ctx);
   1442   uint64_t want_chunk = 0;
   1443   size_t off;
   1444   int saw = 0;
   1445   if (!raww) return DIST_ERR;
   1446   if (index_l % DIST_KPKG3_INDEX_RECORD_SIZE != 0) goto fail;
   1447   for (off = 0; off < index_l; off += DIST_KPKG3_INDEX_RECORD_SIZE) {
   1448     DistKpkg3IndexRecord r;
   1449     KitFileData chunk_fd;
   1450     const uint8_t* stored;
   1451     size_t stored_len;
   1452     int cmp;
   1453     if (dist_kpkg3_decode_index_record(
   1454             index_b + off, DIST_KPKG3_INDEX_RECORD_SIZE, &r) != DIST_OK)
   1455       goto fail;
   1456     cmp = memcmp(r.blob_id, e->blob, DIST_BLAKE2B_LEN);
   1457     if (cmp < 0) continue;
   1458     if (cmp > 0 && saw) break;
   1459     if (cmp > 0) continue;
   1460     saw = 1;
   1461     if (r.chunk_index != want_chunk++) goto fail;
   1462     if (pkg_native_load_stored_chunk(ctx, data, d, &r, external_dir,
   1463                                      chunk_template, &chunk_fd, &stored,
   1464                                      &stored_len) != DIST_OK)
   1465       goto fail;
   1466     if (pkg_decode_native_chunk(raww, stored, stored_len, d, &r) != DIST_OK) {
   1467       if (chunk_fd.data && ctx->file_io->release)
   1468         ctx->file_io->release(ctx->file_io->user, &chunk_fd);
   1469       goto fail;
   1470     }
   1471     if (chunk_fd.data && ctx->file_io->release)
   1472       ctx->file_io->release(ctx->file_io->user, &chunk_fd);
   1473   }
   1474   *raww_out = raww;
   1475   return DIST_OK;
   1476 fail:
   1477   kit_writer_close(raww);
   1478   return DIST_ERR;
   1479 }
   1480 
   1481 static int pkg_verify_native_tree(
   1482     const KitCasHost* host, const KitContext* ctx, const uint8_t* data,
   1483     size_t len, const uint8_t* index_b, size_t index_l,
   1484     const DistKpkg3Descriptor* d, const PkgVerified* v,
   1485     const DistPackageOutput* out, const char* external_dir,
   1486     const char* chunk_template, const char* out_dir) {
   1487   PkgLoadedTree tree;
   1488   size_t i;
   1489   if (pkg_native_load_tree(ctx, data, len, d, out, external_dir, &tree) !=
   1490       DIST_OK)
   1491     return DIST_ERR;
   1492   if (pkg_verify_artifact_overlays(ctx, &v->manifest, out, &tree.tree) !=
   1493       DIST_OK)
   1494     return DIST_ERR;
   1495   for (i = 0; i < tree.tree.n_entries; ++i) {
   1496     const DistTreeEntry* e = &tree.tree.entries[i];
   1497     KitWriter* raww = NULL;
   1498     const uint8_t* rawb;
   1499     size_t rawl;
   1500     if (pkg_native_reconstruct_blob(ctx, data, index_b, index_l, d, e,
   1501                                     external_dir, chunk_template,
   1502                                     &raww) != DIST_OK) {
   1503       kit_ctx_diagf(ctx, "native chunk verification failed: %s", e->path);
   1504       return DIST_ERR;
   1505     }
   1506     rawb = kit_writer_mem_bytes(raww, &rawl);
   1507     if (pkg_verify_blob_bytes(e, rawb, rawl) != DIST_OK) {
   1508       kit_writer_close(raww);
   1509       kit_ctx_diagf(ctx, "blob hash mismatch: %s", e->path);
   1510       return DIST_ERR;
   1511     }
   1512     if (out_dir &&
   1513         pkg_write_output_file(host, ctx, out_dir, e, rawb, rawl) != DIST_OK) {
   1514       kit_writer_close(raww);
   1515       return DIST_ERR;
   1516     }
   1517     kit_writer_close(raww);
   1518   }
   1519   return DIST_OK;
   1520 }
   1521 
   1522 static int pkg_verify_native(const KitContext* ctx, const KitCasHost* host,
   1523                              const KitPkgVerifyOptions* opts, PkgVerified* v) {
   1524   const uint8_t* data = opts->pkg_data;
   1525   size_t len = opts->pkg_len;
   1526   const char* external_dir = opts->external_dir;
   1527   KitFileData index_fd = {0};
   1528   DistKpkg3Header h;
   1529   DistKpkg3Descriptor d;
   1530   char err[128];
   1531   uint8_t desc_keyid[DIST_KEYID_LEN], tree_root[DIST_BLAKE2B_LEN],
   1532       index_root[DIST_BLAKE2B_LEN], content_root[DIST_BLAKE2B_LEN];
   1533   char desc_trusted[DIST_TRUSTED_COMMENT_MAX];
   1534   const DistPackageOutput* def;
   1535   const uint8_t* index_b = NULL;
   1536   size_t index_l = 0;
   1537   const char* chunk_template = NULL;
   1538   size_t oi;
   1539   int rc = DIST_ERR;
   1540   if (dist_kpkg3_read_header(data, len, &h) != DIST_OK ||
   1541       pkg_bounds3(&h, len) != DIST_OK) {
   1542     kit_ctx_diagf(ctx, "malformed native package");
   1543     return DIST_ERR;
   1544   }
   1545   if (pkg_verify_manifest(ctx, data + h.manifest_offset,
   1546                           (size_t)h.manifest_size, data + h.signature_offset,
   1547                           (size_t)h.signature_size, data + h.pubkey_offset,
   1548                           (size_t)h.pubkey_size, opts, v) != DIST_OK)
   1549     return DIST_ERR;
   1550   if (dist_minisig_sig_keyid(data + h.descriptor_signature_offset,
   1551                              (size_t)h.descriptor_signature_size,
   1552                              desc_keyid) != DIST_OK ||
   1553       memcmp(desc_keyid, v->keyid, DIST_KEYID_LEN) != 0) {
   1554     kit_ctx_diagf(ctx, "encoding descriptor signer mismatch");
   1555     return DIST_ERR;
   1556   }
   1557   if (dist_minisig_verify(data + h.descriptor_signature_offset,
   1558                           (size_t)h.descriptor_signature_size,
   1559                           data + h.descriptor_offset, (size_t)h.descriptor_size,
   1560                           v->pk, desc_trusted,
   1561                           sizeof desc_trusted) != DIST_OK) {
   1562     kit_ctx_diagf(ctx, "encoding descriptor signature FAILED");
   1563     return DIST_ERR;
   1564   }
   1565   if (dist_kpkg3_descriptor_parse(data + h.descriptor_offset,
   1566                                   (size_t)h.descriptor_size, &d, err,
   1567                                   sizeof err) != DIST_OK) {
   1568     kit_ctx_diagf(ctx, "encoding descriptor: %s", err);
   1569     return DIST_ERR;
   1570   }
   1571   if (memcmp(d.package_id, v->package_id, DIST_BLAKE2B_LEN) != 0 ||
   1572       d.chunk_size != DIST_KPKG3_CHUNK_SIZE_DEFAULT ||
   1573       d.alignment != DIST_KPKG3_ALIGNMENT ||
   1574       !pkg_range_ok(d.tree_offset, d.tree_size, len) ||
   1575       !pkg_range_ok(d.index_offset, d.index_size, len) ||
   1576       !pkg_range_ok(d.content_offset, d.content_size, len)) {
   1577     kit_ctx_diagf(ctx, "encoding descriptor does not match package layout");
   1578     return DIST_ERR;
   1579   }
   1580   dist_kpkg3_region_root(tree_root, "tree", data + d.tree_offset,
   1581                          (size_t)d.tree_size);
   1582   dist_kpkg3_region_root(content_root, "content", data + d.content_offset,
   1583                          (size_t)d.content_size);
   1584   if (pkg_native_load_index(ctx, data, &d, external_dir, &index_fd, &index_b,
   1585                             &index_l) != DIST_OK) {
   1586     kit_ctx_diagf(ctx, "native package index verification failed");
   1587     goto done;
   1588   }
   1589   dist_kpkg3_region_root(index_root, "index", index_b, index_l);
   1590   if (!pkg_descriptor_has_embedded_chunks(&d))
   1591     chunk_template = pkg_descriptor_chunk_template(&d);
   1592   if (d.index_bytes && !pkg_descriptor_has_embedded_chunks(&d) &&
   1593       !external_dir) {
   1594     kit_ctx_diagf(ctx, "external native chunks are missing");
   1595     goto done;
   1596   }
   1597   if (memcmp(tree_root, d.tree_root, DIST_BLAKE2B_LEN) != 0 ||
   1598       memcmp(index_root, d.index_root, DIST_BLAKE2B_LEN) != 0 ||
   1599       memcmp(content_root, d.content_root, DIST_BLAKE2B_LEN) != 0) {
   1600     kit_ctx_diagf(ctx, "native package region hash mismatch");
   1601     goto done;
   1602   }
   1603   if (pkg_verify_native_index_sorted(index_b, index_l, &d) != DIST_OK) {
   1604     kit_ctx_diagf(ctx, "native chunk index is malformed");
   1605     goto done;
   1606   }
   1607   def = pkg_default_output(&v->manifest);
   1608   if (!def) goto done;
   1609   for (oi = 0; oi < v->manifest.n_outputs; ++oi) {
   1610     const DistPackageOutput* out = &v->manifest.outputs[oi];
   1611     if (pkg_verify_native_tree(host, ctx, data, len, index_b, index_l, &d, v,
   1612                                out, external_dir, chunk_template,
   1613                                out == def ? opts->unpack_dir : NULL) != DIST_OK)
   1614       goto done;
   1615   }
   1616   rc = DIST_OK;
   1617 done:
   1618   if (index_fd.token || index_fd.data)
   1619     ctx->file_io->release(ctx->file_io->user, &index_fd);
   1620   return rc;
   1621 }
   1622 
   1623 /* ---------------------------------------------------------------------- */
   1624 /* public verbs + primitives                                              */
   1625 /* ---------------------------------------------------------------------- */
   1626 
   1627 KitStatus kit_pkg_verify(const KitContext* ctx, const KitCasHost* host,
   1628                          const KitPkgVerifyOptions* opts,
   1629                          KitPkgVerifyResult* result) {
   1630   PkgVerified v;
   1631   int rc;
   1632   if (!ctx || !host || !opts || !result || !opts->pkg_data) return KIT_INVALID;
   1633   if (opts->format == KIT_PKG_FORMAT_TARGZ)
   1634     rc = pkg_verify_portable(ctx, host, opts, &v);
   1635   else
   1636     rc = pkg_verify_native(ctx, host, opts, &v);
   1637   if (rc != DIST_OK) return KIT_ERR;
   1638   memset(result, 0, sizeof *result);
   1639   snprintf(result->name, sizeof result->name, "%s", v.manifest.name);
   1640   snprintf(result->version, sizeof result->version, "%s", v.manifest.version);
   1641   snprintf(result->trusted, sizeof result->trusted, "%s", v.trusted);
   1642   memcpy(result->keyid, v.keyid, DIST_KEYID_LEN);
   1643   result->tofu_pin = v.tofu_pin;
   1644   memcpy(result->tofu_pk, v.pk, DIST_ED25519_PK_LEN);
   1645   return KIT_OK;
   1646 }
   1647 
   1648 KitStatus kit_pkg_inspect(const KitContext* ctx, const uint8_t* pkg_data,
   1649                           size_t pkg_len, KitPkgFormat format,
   1650                           int show_encoding, KitWriter* out) {
   1651   if (!ctx || !pkg_data || !out) return KIT_INVALID;
   1652   if (format == KIT_PKG_FORMAT_TARGZ) {
   1653     KitWriter* inflated = NULL;
   1654     DistTarEntry entries[PKG_MAX_TAR_ENTRIES];
   1655     size_t ne = 0;
   1656     const DistTarEntry* man;
   1657     KitStatus st = KIT_ERR;
   1658     if (pkg_load_portable(ctx, pkg_data, pkg_len, &inflated, entries, &ne) ==
   1659             DIST_OK &&
   1660         (man = pkg_find_name(entries, ne, PKG_META_MANIFEST)) != NULL) {
   1661       if (man->size && kit_writer_write(out, man->data, man->size) != KIT_OK)
   1662         st = KIT_IO;
   1663       else
   1664         st = kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
   1665     }
   1666     if (inflated) kit_writer_close(inflated);
   1667     return st;
   1668   } else {
   1669     DistKpkg3Header h;
   1670     const uint8_t* region;
   1671     size_t region_len;
   1672     if (dist_kpkg3_read_header(pkg_data, pkg_len, &h) != DIST_OK ||
   1673         pkg_bounds3(&h, pkg_len) != DIST_OK) {
   1674       kit_ctx_diagf(ctx, "malformed native package");
   1675       return KIT_MALFORMED;
   1676     }
   1677     if (show_encoding) {
   1678       region = pkg_data + h.descriptor_offset;
   1679       region_len = (size_t)h.descriptor_size;
   1680     } else {
   1681       region = pkg_data + h.manifest_offset;
   1682       region_len = (size_t)h.manifest_size;
   1683     }
   1684     if (region_len && kit_writer_write(out, region, region_len) != KIT_OK)
   1685       return KIT_IO;
   1686     return kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
   1687   }
   1688 }
   1689 
   1690 KitStatus kit_release_index_emit(const KitContext* ctx,
   1691                                  const KitReleaseIndex* index, KitWriter* out) {
   1692   char err[128];
   1693   if (!ctx || !index || !out) return KIT_INVALID;
   1694   /* Validate first so an invalid index is reported with a clear reason; the
   1695    * emit re-validates internally but does not surface the message. */
   1696   if (dist_release_index_validate(index, err, sizeof err) != DIST_OK) {
   1697     kit_ctx_diagf(ctx, "%s", err);
   1698     return KIT_ERR;
   1699   }
   1700   if (dist_release_index_emit(index, out) != DIST_OK) {
   1701     if (kit_writer_status(out) != KIT_OK) return KIT_IO;
   1702     kit_ctx_diagf(ctx, "release index emit failed");
   1703     return KIT_ERR;
   1704   }
   1705   return kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
   1706 }
   1707 
   1708 KitStatus kit_release_index_parse(const KitContext* ctx, const uint8_t* data,
   1709                                   size_t len, KitReleaseIndex* out) {
   1710   char err[128];
   1711   if (!ctx || !data || !out) return KIT_INVALID;
   1712   if (dist_release_index_parse(data, len, out, err, sizeof err) != DIST_OK) {
   1713     kit_ctx_diagf(ctx, "%s", err);
   1714     return KIT_MALFORMED;
   1715   }
   1716   return KIT_OK;
   1717 }
   1718 
   1719 KitStatus kit_calver_compare(const char* a, const char* b, int* cmp) {
   1720   if (!a || !b || !cmp) return KIT_INVALID;
   1721   return dist_calver_compare(a, b, cmp) == DIST_OK ? KIT_OK : KIT_INVALID;
   1722 }
   1723 
   1724 KitStatus kit_pkg_sign_detached(const KitContext* ctx, const uint8_t* msg,
   1725                                 size_t msglen, const uint8_t* seckey_bytes,
   1726                                 size_t seckey_len, const char* comment,
   1727                                 KitWriter* out) {
   1728   uint8_t sk[DIST_ED25519_SK_LEN], keyid[DIST_KEYID_LEN];
   1729   int rc;
   1730   const char* tc = (comment && *comment) ? comment : "kit detached signature";
   1731   if (!ctx || (!msg && msglen) || !seckey_bytes || !out) return KIT_INVALID;
   1732   rc = dist_minisig_parse_seckey(seckey_bytes, seckey_len, sk, keyid);
   1733   if (rc == DIST_ENCRYPTED) {
   1734     kit_ctx_diagf(ctx, "encrypted secret keys need scrypt");
   1735     return KIT_UNSUPPORTED;
   1736   }
   1737   if (rc != DIST_OK) {
   1738     kit_ctx_diagf(ctx, "malformed secret key");
   1739     return KIT_MALFORMED;
   1740   }
   1741   if (dist_minisig_sign(out, msg, msglen, sk, keyid, "kit signature", tc) !=
   1742       DIST_OK) {
   1743     kit_ctx_diagf(ctx, "could not produce signature");
   1744     return KIT_ERR;
   1745   }
   1746   return kit_writer_status(out) == KIT_OK ? KIT_OK : KIT_IO;
   1747 }
   1748 
   1749 KitStatus kit_pkg_verify_detached(
   1750     const KitContext* ctx, const KitPkgDetachedVerifyOptions* opts,
   1751     KitPkgDetachedVerifyResult* result) {
   1752   uint8_t pk[DIST_ED25519_PK_LEN];
   1753   uint8_t keyid_check[DIST_KEYID_LEN];
   1754   if (!ctx || !opts || !result || (!opts->data && opts->data_len) ||
   1755       !opts->signature)
   1756     return KIT_INVALID;
   1757   memset(result, 0, sizeof *result);
   1758   if (dist_minisig_sig_keyid(opts->signature, opts->signature_len,
   1759                              result->keyid) != DIST_OK) {
   1760     kit_ctx_diagf(ctx, "malformed detached signature");
   1761     return KIT_MALFORMED;
   1762   }
   1763   if (opts->pubkey_bytes) {
   1764     if (dist_minisig_parse_pubkey(opts->pubkey_bytes, opts->pubkey_len, pk,
   1765                                   keyid_check) != DIST_OK) {
   1766       kit_ctx_diagf(ctx, "malformed public key");
   1767       return KIT_MALFORMED;
   1768     }
   1769     if (memcmp(keyid_check, result->keyid, DIST_KEYID_LEN) != 0) {
   1770       kit_ctx_diagf(ctx, "public key id does not match detached signature");
   1771       return KIT_ERR;
   1772     }
   1773   } else if (!opts->trusted_keys ||
   1774              dist_trust_lookup(opts->trusted_keys, opts->trusted_keys_len,
   1775                                result->keyid, pk) != DIST_OK) {
   1776     char hex[2 * DIST_KEYID_LEN + 1];
   1777     dist_hex_encode(hex, result->keyid, DIST_KEYID_LEN);
   1778     kit_ctx_diagf(ctx, "untrusted detached signer (key id %s)", hex);
   1779     return KIT_ERR;
   1780   }
   1781   if (dist_minisig_verify(opts->signature, opts->signature_len, opts->data,
   1782                           opts->data_len, pk, result->trusted,
   1783                           sizeof result->trusted) != DIST_OK) {
   1784     kit_ctx_diagf(ctx, "detached signature verification FAILED");
   1785     return KIT_ERR;
   1786   }
   1787   return KIT_OK;
   1788 }
   1789 
   1790 KitStatus kit_pkg_keygen(const KitContext* ctx, KitPkgRandomFn rng,
   1791                          void* rng_user, KitWriter* pub_out, KitWriter* sec_out,
   1792                          uint8_t out_keyid[KIT_PKG_KEYID_LEN]) {
   1793   uint8_t seed[DIST_ED25519_SEED_LEN], keyid[DIST_KEYID_LEN];
   1794   DistKeypair kp;
   1795   if (!rng || !pub_out || !sec_out) return KIT_INVALID;
   1796   if (rng(rng_user, seed, sizeof seed) != 0 ||
   1797       rng(rng_user, keyid, sizeof keyid) != 0) {
   1798     kit_ctx_diagf(ctx, "keygen: failed to read system randomness");
   1799     return KIT_ERR;
   1800   }
   1801   dist_minisig_keygen(&kp, seed, keyid);
   1802   if (dist_minisig_emit_pubkey(pub_out, &kp) != DIST_OK ||
   1803       kit_writer_status(pub_out) != KIT_OK)
   1804     return KIT_IO;
   1805   if (dist_minisig_emit_seckey(sec_out, &kp) != DIST_OK ||
   1806       kit_writer_status(sec_out) != KIT_OK)
   1807     return KIT_IO;
   1808   if (out_keyid) memcpy(out_keyid, kp.keyid, DIST_KEYID_LEN);
   1809   return KIT_OK;
   1810 }
   1811 
   1812 KitStatus kit_minisig_parse_pubkey(const uint8_t* data, size_t len,
   1813                                    uint8_t pk_out[KIT_PKG_PK_LEN],
   1814                                    uint8_t keyid_out[KIT_PKG_KEYID_LEN]) {
   1815   return dist_minisig_parse_pubkey(data, len, pk_out, keyid_out) == DIST_OK
   1816              ? KIT_OK
   1817              : KIT_MALFORMED;
   1818 }
   1819 
   1820 KitStatus kit_minisig_parse_seckey(const uint8_t* data, size_t len,
   1821                                    uint8_t sk_out[KIT_PKG_SK_LEN],
   1822                                    uint8_t keyid_out[KIT_PKG_KEYID_LEN]) {
   1823   int rc = dist_minisig_parse_seckey(data, len, sk_out, keyid_out);
   1824   if (rc == DIST_OK) return KIT_OK;
   1825   if (rc == DIST_ENCRYPTED) return KIT_UNSUPPORTED;
   1826   return KIT_MALFORMED;
   1827 }
   1828 
   1829 KitStatus kit_trust_lookup(const uint8_t* file, size_t len,
   1830                            const uint8_t keyid[KIT_PKG_KEYID_LEN],
   1831                            uint8_t pk_out[KIT_PKG_PK_LEN]) {
   1832   return dist_trust_lookup(file, len, keyid, pk_out) == DIST_OK ? KIT_OK
   1833                                                                 : KIT_NOT_FOUND;
   1834 }
   1835 
   1836 KitStatus kit_trust_format_entry(char* out, size_t cap,
   1837                                  const uint8_t keyid[KIT_PKG_KEYID_LEN],
   1838                                  const uint8_t pk[KIT_PKG_PK_LEN],
   1839                                  const char* label) {
   1840   return dist_trust_format_entry(out, cap, keyid, pk, label) == DIST_OK
   1841              ? KIT_OK
   1842              : KIT_ERR;
   1843 }