kit

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

package.c (66972B)


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